support csv
Browse files- app.py +85 -32
- markdown.py +3 -3
- utilities.py +6 -4
app.py
CHANGED
|
@@ -1,18 +1,22 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import pytz
|
| 3 |
import os
|
|
|
|
|
|
|
| 4 |
import matplotlib.pyplot as plt
|
| 5 |
from datetime import datetime
|
| 6 |
from markdown import instructions_markdown, faq_markdown
|
| 7 |
from fsrs_optimizer import Optimizer
|
| 8 |
from pathlib import Path
|
| 9 |
from utilities import cleanup
|
| 10 |
-
import re
|
| 11 |
|
| 12 |
with open("./requirements.txt", "r") as f:
|
| 13 |
txt = f.read().strip()
|
| 14 |
version = re.search(r"FSRS-Optimizer==(.*)", txt).group(1)
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
def get_w_markdown(w):
|
| 17 |
return f"""
|
| 18 |
# Updated Parameters
|
|
@@ -26,33 +30,64 @@ def get_w_markdown(w):
|
|
| 26 |
"""
|
| 27 |
|
| 28 |
|
| 29 |
-
def
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
if timezone == "":
|
| 35 |
raise ValueError("Please select a timezone.")
|
| 36 |
now = datetime.now()
|
| 37 |
-
files = [
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
proj_dir.mkdir(parents=True, exist_ok=True)
|
| 43 |
os.chdir(proj_dir)
|
| 44 |
optimizer = Optimizer()
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
optimizer.define_model()
|
| 48 |
optimizer.pretrain(verbose=False)
|
| 49 |
optimizer.train(verbose=False)
|
| 50 |
print(optimizer.w)
|
| 51 |
w_markdown = get_w_markdown(optimizer.w)
|
| 52 |
optimizer.predict_memory_states()
|
| 53 |
-
difficulty_distribution = optimizer.difficulty_distribution.to_string().replace(
|
|
|
|
|
|
|
| 54 |
plot_output = optimizer.find_optimal_retention()[0]
|
| 55 |
-
suggested_retention_markdown =
|
|
|
|
|
|
|
| 56 |
rating_markdown = optimizer.preview(requestRetention).replace("\n", "\n\n")
|
| 57 |
loss_before, loss_after = optimizer.evaluate()
|
| 58 |
loss_markdown = f"""
|
|
@@ -73,15 +108,15 @@ def anki_optimizer(file: gr.File, timezone, next_day_starts_at, revlog_start_dat
|
|
| 73 |
# Ratings
|
| 74 |
{rating_markdown}
|
| 75 |
"""
|
| 76 |
-
os.chdir(
|
| 77 |
files_out = [proj_dir / file for file in files if (proj_dir / file).exists()]
|
| 78 |
cleanup(proj_dir, files)
|
| 79 |
-
plt.close(
|
| 80 |
return w_markdown, markdown_out, plot_output, files_out
|
| 81 |
|
| 82 |
|
| 83 |
description = f"""
|
| 84 |
-
#
|
| 85 |
Based on the [tutorial](https://medium.com/@JarrettYe/how-to-use-the-next-generation-spaced-repetition-algorithm-fsrs-on-anki-5a591ca562e2)
|
| 86 |
of [Jarrett Ye](https://github.com/L-M-Sherlock). This application can give you personalized anki parameters without having to code.
|
| 87 |
|
|
@@ -89,25 +124,34 @@ Read the `Instructions` if its your first time using the app.
|
|
| 89 |
"""
|
| 90 |
|
| 91 |
with gr.Blocks() as demo:
|
| 92 |
-
with gr.Tab("
|
| 93 |
with gr.Box():
|
| 94 |
gr.Markdown(description)
|
| 95 |
with gr.Box():
|
| 96 |
with gr.Row():
|
| 97 |
with gr.Column():
|
| 98 |
-
file = gr.File(label=
|
| 99 |
with gr.Column():
|
| 100 |
-
next_day_starts_at = gr.Number(
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
timezone = gr.Dropdown(
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
with gr.Accordion(label="Advanced Settings (Step 3.2)", open=False):
|
| 106 |
-
requestRetention = gr.Number(
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
with gr.Row():
|
| 110 |
-
btn_plot = gr.Button(
|
| 111 |
with gr.Row():
|
| 112 |
w_output = gr.Markdown()
|
| 113 |
with gr.Tab("Instructions"):
|
|
@@ -122,8 +166,17 @@ with gr.Blocks() as demo:
|
|
| 122 |
with gr.Tab("FAQ"):
|
| 123 |
gr.Markdown(faq_markdown)
|
| 124 |
|
| 125 |
-
btn_plot.click(
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
demo.queue().launch(show_error=True)
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import pytz
|
| 3 |
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import re
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
from datetime import datetime
|
| 8 |
from markdown import instructions_markdown, faq_markdown
|
| 9 |
from fsrs_optimizer import Optimizer
|
| 10 |
from pathlib import Path
|
| 11 |
from utilities import cleanup
|
|
|
|
| 12 |
|
| 13 |
with open("./requirements.txt", "r") as f:
|
| 14 |
txt = f.read().strip()
|
| 15 |
version = re.search(r"FSRS-Optimizer==(.*)", txt).group(1)
|
| 16 |
|
| 17 |
+
home_path = os.getcwd()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
def get_w_markdown(w):
|
| 21 |
return f"""
|
| 22 |
# Updated Parameters
|
|
|
|
| 30 |
"""
|
| 31 |
|
| 32 |
|
| 33 |
+
def optimizer(
|
| 34 |
+
file: gr.File,
|
| 35 |
+
timezone,
|
| 36 |
+
next_day_starts_at,
|
| 37 |
+
revlog_start_date,
|
| 38 |
+
filter_out_suspended_cards,
|
| 39 |
+
requestRetention,
|
| 40 |
+
progress=gr.Progress(track_tqdm=True),
|
| 41 |
+
):
|
| 42 |
+
os.chdir(home_path)
|
| 43 |
+
if file is None:
|
| 44 |
+
raise ValueError("Please upload a deck/collection/csv file.")
|
| 45 |
+
if file.name.endswith(".apkg") or file.name.endswith(".colpkg"):
|
| 46 |
+
mode = "anki"
|
| 47 |
+
elif file.name.endswith(".csv"):
|
| 48 |
+
mode = "csv"
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError(
|
| 51 |
+
"File must be an Anki deck/collection file (.apkg or .colpkg) or a csv file."
|
| 52 |
+
)
|
| 53 |
if timezone == "":
|
| 54 |
raise ValueError("Please select a timezone.")
|
| 55 |
now = datetime.now()
|
| 56 |
+
files = [
|
| 57 |
+
"prediction.tsv",
|
| 58 |
+
"revlog.csv",
|
| 59 |
+
"revlog_history.tsv",
|
| 60 |
+
"stability_for_analysis.tsv",
|
| 61 |
+
"expected_time.csv",
|
| 62 |
+
"evaluation.tsv",
|
| 63 |
+
]
|
| 64 |
+
prefix = now.strftime(f"%Y_%m_%d_%H_%M_%S")
|
| 65 |
+
suffix = file.name.split("/")[-1].replace(".", "_").replace("@", "_")
|
| 66 |
+
proj_dir = Path(f"projects/{prefix}/{suffix}")
|
| 67 |
proj_dir.mkdir(parents=True, exist_ok=True)
|
| 68 |
os.chdir(proj_dir)
|
| 69 |
optimizer = Optimizer()
|
| 70 |
+
if mode == "anki":
|
| 71 |
+
optimizer.anki_extract(file.name, filter_out_suspended_cards)
|
| 72 |
+
else:
|
| 73 |
+
print(file.name)
|
| 74 |
+
shutil.copyfile(file.name, "./revlog.csv")
|
| 75 |
+
analysis_markdown = optimizer.create_time_series(
|
| 76 |
+
timezone, revlog_start_date, next_day_starts_at
|
| 77 |
+
).replace("\n", "\n\n")
|
| 78 |
optimizer.define_model()
|
| 79 |
optimizer.pretrain(verbose=False)
|
| 80 |
optimizer.train(verbose=False)
|
| 81 |
print(optimizer.w)
|
| 82 |
w_markdown = get_w_markdown(optimizer.w)
|
| 83 |
optimizer.predict_memory_states()
|
| 84 |
+
difficulty_distribution = optimizer.difficulty_distribution.to_string().replace(
|
| 85 |
+
"\n", "\n\n"
|
| 86 |
+
)
|
| 87 |
plot_output = optimizer.find_optimal_retention()[0]
|
| 88 |
+
suggested_retention_markdown = (
|
| 89 |
+
f"""# Suggested Retention: `{optimizer.optimal_retention:.2f}`"""
|
| 90 |
+
)
|
| 91 |
rating_markdown = optimizer.preview(requestRetention).replace("\n", "\n\n")
|
| 92 |
loss_before, loss_after = optimizer.evaluate()
|
| 93 |
loss_markdown = f"""
|
|
|
|
| 108 |
# Ratings
|
| 109 |
{rating_markdown}
|
| 110 |
"""
|
| 111 |
+
os.chdir(home_path)
|
| 112 |
files_out = [proj_dir / file for file in files if (proj_dir / file).exists()]
|
| 113 |
cleanup(proj_dir, files)
|
| 114 |
+
plt.close("all")
|
| 115 |
return w_markdown, markdown_out, plot_output, files_out
|
| 116 |
|
| 117 |
|
| 118 |
description = f"""
|
| 119 |
+
# FSRS Optimizer - v{version}
|
| 120 |
Based on the [tutorial](https://medium.com/@JarrettYe/how-to-use-the-next-generation-spaced-repetition-algorithm-fsrs-on-anki-5a591ca562e2)
|
| 121 |
of [Jarrett Ye](https://github.com/L-M-Sherlock). This application can give you personalized anki parameters without having to code.
|
| 122 |
|
|
|
|
| 124 |
"""
|
| 125 |
|
| 126 |
with gr.Blocks() as demo:
|
| 127 |
+
with gr.Tab("FSRS Optimizer"):
|
| 128 |
with gr.Box():
|
| 129 |
gr.Markdown(description)
|
| 130 |
with gr.Box():
|
| 131 |
with gr.Row():
|
| 132 |
with gr.Column():
|
| 133 |
+
file = gr.File(label="Review Logs (Step 1)")
|
| 134 |
with gr.Column():
|
| 135 |
+
next_day_starts_at = gr.Number(
|
| 136 |
+
value=4, label="Next Day Starts at (Step 2)", precision=0
|
| 137 |
+
)
|
| 138 |
+
timezone = gr.Dropdown(
|
| 139 |
+
label="Timezone (Step 3.1)", choices=pytz.all_timezones
|
| 140 |
+
)
|
| 141 |
+
filter_out_suspended_cards = gr.Checkbox(
|
| 142 |
+
value=False, label="Filter out suspended cards"
|
| 143 |
+
)
|
| 144 |
with gr.Accordion(label="Advanced Settings (Step 3.2)", open=False):
|
| 145 |
+
requestRetention = gr.Number(
|
| 146 |
+
value=0.9,
|
| 147 |
+
label="Desired Retention: Recommended to set between 0.8 0.9",
|
| 148 |
+
)
|
| 149 |
+
revlog_start_date = gr.Textbox(
|
| 150 |
+
value="2006-10-05",
|
| 151 |
+
label="Revlog Start Date: Optimize review logs after this date.",
|
| 152 |
+
)
|
| 153 |
with gr.Row():
|
| 154 |
+
btn_plot = gr.Button("Optimize!")
|
| 155 |
with gr.Row():
|
| 156 |
w_output = gr.Markdown()
|
| 157 |
with gr.Tab("Instructions"):
|
|
|
|
| 166 |
with gr.Tab("FAQ"):
|
| 167 |
gr.Markdown(faq_markdown)
|
| 168 |
|
| 169 |
+
btn_plot.click(
|
| 170 |
+
optimizer,
|
| 171 |
+
inputs=[
|
| 172 |
+
file,
|
| 173 |
+
timezone,
|
| 174 |
+
next_day_starts_at,
|
| 175 |
+
revlog_start_date,
|
| 176 |
+
filter_out_suspended_cards,
|
| 177 |
+
requestRetention,
|
| 178 |
+
],
|
| 179 |
+
outputs=[w_output, markdown_output, plot_output, files_output],
|
| 180 |
+
)
|
| 181 |
|
| 182 |
demo.queue().launch(show_error=True)
|
markdown.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
instructions_markdown = """
|
| 2 |
-
# How to get personalized FSRS
|
| 3 |
If you have been using Anki for some time and have accumulated a lot of review logs, you can try this
|
| 4 |
-
|
| 5 |
|
| 6 |
This is based on the amazing work of [Jarrett Ye](https://github.com/L-M-Sherlock). My goal is to further
|
| 7 |
democratize this technology so anyone can use it!
|
|
@@ -41,4 +41,4 @@ You can find it here: [https://www.maimemo.com/paper/](https://www.maimemo.com/p
|
|
| 41 |
What is the original author's research story?
|
| 42 |
|
| 43 |
You can find it here: [https://medium.com/@JarrettYe/how-did-i-publish-a-paper-in-acmkdd-as-an-undergraduate-c0199baddf31](https://medium.com/@JarrettYe/how-did-i-publish-a-paper-in-acmkdd-as-an-undergraduate-c0199baddf31)
|
| 44 |
-
"""
|
|
|
|
| 1 |
instructions_markdown = """
|
| 2 |
+
# How to get personalized FSRS parameters
|
| 3 |
If you have been using Anki for some time and have accumulated a lot of review logs, you can try this
|
| 4 |
+
FSRS optimizer app to generate parameters for you.
|
| 5 |
|
| 6 |
This is based on the amazing work of [Jarrett Ye](https://github.com/L-M-Sherlock). My goal is to further
|
| 7 |
democratize this technology so anyone can use it!
|
|
|
|
| 41 |
What is the original author's research story?
|
| 42 |
|
| 43 |
You can find it here: [https://medium.com/@JarrettYe/how-did-i-publish-a-paper-in-acmkdd-as-an-undergraduate-c0199baddf31](https://medium.com/@JarrettYe/how-did-i-publish-a-paper-in-acmkdd-as-an-undergraduate-c0199baddf31)
|
| 44 |
+
"""
|
utilities.py
CHANGED
|
@@ -5,12 +5,15 @@ from pathlib import Path
|
|
| 5 |
|
| 6 |
# Extract the collection file or deck file to get the .anki21 database.
|
| 7 |
def extract(file, prefix):
|
| 8 |
-
proj_dir = Path(
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
zip_ref.extractall(proj_dir)
|
| 11 |
# print(f"Extracted {file.orig_name} successfully!")
|
| 12 |
return proj_dir
|
| 13 |
|
|
|
|
| 14 |
def cleanup(proj_dir: Path, files):
|
| 15 |
"""
|
| 16 |
Delete all files in prefix that dont have filenames in files
|
|
@@ -18,7 +21,6 @@ def cleanup(proj_dir: Path, files):
|
|
| 18 |
:param files:
|
| 19 |
:return:
|
| 20 |
"""
|
| 21 |
-
for file in proj_dir.glob(
|
| 22 |
if file.name not in files:
|
| 23 |
os.remove(file)
|
| 24 |
-
|
|
|
|
| 5 |
|
| 6 |
# Extract the collection file or deck file to get the .anki21 database.
|
| 7 |
def extract(file, prefix):
|
| 8 |
+
proj_dir = Path(
|
| 9 |
+
f'projects/{prefix}_{file.orig_name.replace(".", "_").replace("@", "_")}'
|
| 10 |
+
)
|
| 11 |
+
with ZipFile(file, "r") as zip_ref:
|
| 12 |
zip_ref.extractall(proj_dir)
|
| 13 |
# print(f"Extracted {file.orig_name} successfully!")
|
| 14 |
return proj_dir
|
| 15 |
|
| 16 |
+
|
| 17 |
def cleanup(proj_dir: Path, files):
|
| 18 |
"""
|
| 19 |
Delete all files in prefix that dont have filenames in files
|
|
|
|
| 21 |
:param files:
|
| 22 |
:return:
|
| 23 |
"""
|
| 24 |
+
for file in proj_dir.glob("*"):
|
| 25 |
if file.name not in files:
|
| 26 |
os.remove(file)
|
|
|