Skip to content

Commit

Permalink
🔖 v3.0.0 (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
alankan886 authored Jun 23, 2024
1 parent 80affaf commit 6de5b77
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 192 deletions.
25 changes: 14 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,42 @@
name: CI
on:
push:
branches: ["master"]
branches: ["main"]
pull_request:
branches: ["master"]
branches: ["main"]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Generate coverage report
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -r requirements.txt
pytest
upload_coverage:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: "3.10"
- name: Generate coverage report
run: |
python -m pip install --upgrade pip
pip install pytest-cov
pip install -r requirements.txt
pytest --cov=supermemo2 --cov-report=xml
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
91 changes: 42 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SuperMemo2
![Python](https://img.shields.io/badge/python-3+-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a)
![Python](https://img.shields.io/badge/python-3.8+-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a)
[![Version](https://img.shields.io/pypi/v/supermemo2?logo=pypi&logoColor=white&style=flat-square&colorA=4c566a&colorB=90A2BC)](https://pypi.org/project/supermemo2/)
[![Build](https://img.shields.io/github/workflow/status/alankan886/SuperMemo2/CI?logo=github-actions&logoColor=white&style=flat-square&colorA=4c566a&colorB=90BCA8)](https://github.com/alankan886/SuperMemo2/actions?query=workflow%3ACI)
[![Coverage](https://img.shields.io/codecov/c/github/alankan886/SuperMemo2?logo=codecov&logoColor=white&style=flat-square&colorA=4c566a&colorB=90BCA8)](https://codecov.io/gh/alankan886/SuperMemo2)
Expand Down Expand Up @@ -35,7 +35,7 @@ The goal was to have an efficient way to calculate the next review date for stud
Install and upate the package using [pip](https://pip.pypa.io/en/stable/quickstart/):

```bash
pip3 install -U supermemo2
pip install -U supermemo2
```

<a name="download">
Expand All @@ -49,26 +49,26 @@ git clone https://github.com/alankan886/SuperMemo2.git

Install dependencies to run the code:
```bash
pip3 install -r requirements.txt
pip install -r requirements.txt
```

supermemo2 supports Python 3+
supermemo2 supports Python 3.8+

<a name="example">

## A Simple Example

```python
from supermemo2 import SMTwo
from supermemo2 import first_review, review

# first review
# using quality=4 as an example, read below for what each value from 0 to 5 represents
# review date would default to date.today() if not provided
review = SMTwo.first_review(4, "2021-3-14")
# review prints SMTwo(easiness=2.36, interval=1, repetitions=1, review_date=datetime.date(2021, 3, 15))
# review date would default to datetime.utcnow() (UTC timezone) if not provided
first_review = first_review(4, "2024-06-22")
# review prints { "easiness": 2.36, "interval": 1, "repetitions": 1, "review_datetime": "2024-06-23 01:06:02"))

# second review
review = SMTwo(review.easiness, review.interval, review.repetitions).review(4, "2021-3-14")
second_review = review(4, first_review["easiness"], first_review["interval"], first_review["repetitions"], first_review["review_datetime"])
# review prints similar to example above.
```

Expand Down Expand Up @@ -104,78 +104,67 @@ The values are the:
<a name="code">

## Code Reference
### *class* supermemo2.SMTwo(easiness, interval, repetitions)
**first_review(** quality, review_datetime=None**)**

**Parameters:**
- easiness (float) - the easiness determines the interval.
- interval (int) - the interval between the latest review date and the next review date.
- repetitions (int) - the count of consecutive reviews with quality larger than 2.

<br>

**first_review(** quality, review_date=None, date_fmt=None **)**

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Static method that calcualtes the next review date for the first review without having to know the initial values, and returns a dictionary containing the new values.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;function that calcualtes the next review datetime for the your first review without having to know the initial values, and returns a dictionary containing the new values.

**Parameters:**
- quality (int) - the recall quality of the review.
- review_date (str or datetime.date) - optional parameter, the date of the review.
- date_fmt (string) - optional parameter, the format of the review_date. Formats like `year_mon_day`, `mon_day_year` and `day_mon_year`.
- review_datetime (str or datetime.datetime) - optional parameter, the datetime in ISO format up to seconds in UTC timezone of the review.

**Returns:** dictionary containing values like quality, easiness, interval, repetitions and review_date.
**Returns:** dictionary containing values like quality, easiness, interval, repetitions and review_datetime.

**Return Type:** Dict

**Usage:**
```python
from supermemo2 import SMTwo, mon_day_year
# using default date date.today()
SMTwo.first_review(3)
from supermemo2 import first_review
# using default datetime.utcnow() if you just reviewed it
first_review(3)

# providing string date in Year-Month-Day format
SMTwo.first_review(3, "2021-12-01")

# providing string date in Month-Day-Year format
SMTwo.first_review(3, "12-01-2021", mon_day_year)
first_review(3, "2024-06-22")

# providing date object date
from datetime import date
d = date(2021, 12, 1)
SMTwo.first_review(3, d)
from datetime import datetime
d = datetime(2024, 1, 1)
first_review(3, d)
```

**review(** quality, review_date=None, date_fmt=None **)**
**review(** quality, easiness, interval, repetitions, review_datetime=None **)**

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calcualtes the next review date based on previous values, and returns a dictionary containing the new values.

**Parameters:**
- quality (int) - the recall quality of the review.
- review_date (str or datetime.date) - optional parameter, the date of the review.
- date_fmt (string) - optional parameter, the format of the review_date. Formats like `year_mon_day`, `mon_day_year` and `day_mon_year`.
- easiness (float) - the easiness determines the interval.
- interval (int) - the interval between the latest review date and the next review date.
- repetitions (int) - the count of consecutive reviews with quality larger than 2.
- review_datetime (str or datetime.datetime) - optional parameter, the datetime in ISO format up to seconds in UTC timezone of the review.

**Returns:** dictionary containing values like quality, easiness, interval, repetitions and review_date.
**Returns:** dictionary containing values like quality, easiness, interval, repetitions and review_datetime.

**Return Type:** Dict

**Usage:**
```python
from supermemo2 import SMTwo, mon_day_year
from supermemo2 import first_review, review
# using previous values from first_review call
r = SMTwo.first_review(3)
r = first_review(3)

# using default date date.today()
SMTwo(r.easiness, r.interval, r.repetitions).review(3)
# using default datetime.utcnow() if you just reviewed it
review(3, r["easiness"], r["interval"], r["repetitions"])

# providing string date in Year-Month-Day format
SMTwo(r.easiness, r.interval, r.repetitions).review(3, "2021-12-01")
# providing review_datetime from previous review
review(3, r["easiness"], r["interval"], r["repetitions"], r["review_datetime"])

# providing string date in Month-Day-Year format
SMTwo(r.easiness, r.interval, r.repetitions).review(3, "12-01-2021", mon_day_year)
# providing string review_datetime
review(3, r["easiness"], r["interval"], r["repetitions"], "2024-01-01")

# providing date object date
from datetime import date
d = date(2021, 12, 1)
SMTwo(r.easiness, r.interval, r.repetitions).review(3, d)
# providing datetime object review_datetime
from datetime import datetime
d = datetime(2024, 1, 1)
review(3, r["easiness"], r["interval"], r["repetitions"], d)
```

<a name="testing">
Expand All @@ -198,6 +187,10 @@ Check coverage on [Codecov](https://codecov.io/gh/alankan886/SuperMemo2).
<a name="changelog">

## Changelog
3.0.0 (2024-06-22): Major changes/rebuild, Update recommended
- Rewrote the code to remove the class structure, simplfying the code and usability.
- Update to provide datetime instead of just date, more specific with when to review.

2.0.0 (2021-03-28): Major changes/rebuild, Update recommended
- Rebuilt and simplfied the package.

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pytest-cov==2.10.1

freezegun==1.5.1
100 changes: 42 additions & 58 deletions supermemo2/sm_two.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,53 @@
from math import ceil
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta
from typing import Optional, Union, Dict

import attr

def review(
quality: int,
easiness: float,
interval: int,
repetitions: int,
review_datetime: Optional[Union[datetime, str]] = None,
) -> Dict:
if not review_datetime:
review_datetime = datetime.utcnow().isoformat(sep=" ", timespec="seconds")

if isinstance(review_datetime, str):
review_datetime = datetime.fromisoformat(review_datetime).replace(microsecond=0)

if quality < 3:
interval = 1
repetitions = 0
else:
if repetitions == 0:
interval = 1
elif repetitions == 1:
interval = 6
else:
interval = ceil(interval * easiness)

year_mon_day = "%Y-%m-%d"
mon_day_year = "%m-%d-%Y"
day_mon_year = "%d-%m-%Y"


@attr.s
class SMTwo:
easiness = attr.ib(validator=attr.validators.instance_of(float))
interval = attr.ib(validator=attr.validators.instance_of(int))
repetitions = attr.ib(validator=attr.validators.instance_of(int))
review_date = attr.ib(init=False)

@staticmethod
def first_review(
quality: int,
review_date: Optional[Union[date, str]] = None,
date_fmt: Optional[str] = None,
) -> "SMTwo":
if not review_date:
review_date = date.today()

if not date_fmt:
date_fmt = year_mon_day

return SMTwo(2.5, 0, 0).review(quality, review_date, date_fmt)

def review(
self,
quality: int,
review_date: Optional[Union[date, str]] = None,
date_fmt: Optional[str] = None,
) -> "SMTwo":
if not review_date:
review_date = date.today()

if not date_fmt:
date_fmt = year_mon_day
repetitions += 1

if isinstance(review_date, str):
review_date = datetime.strptime(review_date, date_fmt).date()
easiness += 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)
if easiness < 1.3:
easiness = 1.3

if quality < 3:
self.interval = 1
self.repetitions = 0
else:
if self.repetitions == 0:
self.interval = 1
elif self.repetitions == 1:
self.interval = 6
else:
self.interval = ceil(self.interval * self.easiness)
review_datetime += timedelta(days=interval)

self.repetitions = self.repetitions + 1
return {
"easiness": easiness,
"interval": interval,
"repetitions": repetitions,
"review_date": str(review_datetime),
}

self.easiness += 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)
if self.easiness < 1.3:
self.easiness = 1.3

review_date += timedelta(days=self.interval)
self.review_date = review_date
def first_review(
quality: int,
review_datetime: Optional[Union[datetime, str]] = None,
) -> Dict:
if not review_datetime:
review_datetime = datetime.utcnow()

return self
return review(quality, 2.5, 0, 0, review_datetime)
Loading

0 comments on commit 6de5b77

Please sign in to comment.