Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔖 v3.0.0 #16

Merged
merged 11 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading