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)
|
|