Skip to content

Commit cc162ca

Browse files
thiswillbeyourgithubthiswillbeyourgithub
authored andcommitted
first commit
0 parents  commit cc162ca

27 files changed

+8226
-0
lines changed

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
API_KEY*
2+
3+
databases
4+
author_dir
5+
dataset_anchors.txt
6+
7+
.env
8+
.env.leave
9+
10+
utils/__pycache__
11+
.cache
12+
__pycache__
13+
14+
15+
.aider*

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 582 additions & 0 deletions
Large diffs are not rendered by default.

anchors_to_anki.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Script to convert anchor key-value pairs into Anki flashcards.
3+
4+
This script takes a JSON file containing anchor pairs and creates Anki flashcards
5+
using different cloze deletion templates. It supports various formatting options
6+
and ensures proper card creation through the AnkiConnect API.
7+
"""
8+
import fire
9+
import json
10+
from pathlib import Path
11+
from py_ankiconnect import PyAnkiconnect
12+
13+
call_anki = PyAnkiconnect()
14+
15+
TEMPLATES = {
16+
1: """{{c1::K}}<br/><br/>{{c2::V}}""",
17+
2: """{{c1::K}}<br/><br/>V""",
18+
3: """K<br/><br/>{{c1::V}}""",
19+
}
20+
21+
def main(
22+
template_nb: int,
23+
anchors_path: str = "./author_dir/combined_anchors.json",
24+
deck: str = "0_quotidien::Anchors",
25+
header: str = "Anchors",
26+
tags: str = "pro::meta::pegging_concept_medecine::anchors",
27+
):
28+
"""
29+
Convert anchor pairs from a JSON file into Anki flashcards.
30+
31+
Parameters
32+
----------
33+
template_nb : int
34+
Template number (1-3) defining the cloze deletion pattern
35+
anchors_path : str, optional
36+
Path to the JSON file containing anchor pairs
37+
deck : str, optional
38+
Name of the Anki deck to add cards to
39+
header : str, optional
40+
Header text to add to each card
41+
tags : str, optional
42+
Space-separated list of tags to add to each card
43+
44+
Raises
45+
------
46+
Exception
47+
If any flashcards fail to be added to Anki
48+
"""
49+
anchors = json.loads(Path(anchors_path).read_text())
50+
assert anchors
51+
del anchors["__COMMENT"]
52+
53+
template = TEMPLATES[template_nb]
54+
assert "K" in template and "V" in template, template
55+
assert template.count("K") == 1, template
56+
assert template.count("V") == 1, template
57+
assert "{{c1::" in template and "}}" in template.split("{{c1::", 1)[1], template
58+
59+
tags = tags.split(" ")
60+
bodies = []
61+
for k, v in anchors.items():
62+
k = k.strip()
63+
if k != k.upper() and k == k.lower():
64+
k = k.title()
65+
v = v.strip()
66+
assert k, v
67+
assert v, k
68+
assert "}" not in k and "{" not in k, k
69+
assert "}" not in v and "{" not in v, v
70+
71+
if template.index("K") < template.index("V"):
72+
body = template.replace("K", k, 1)[::-1].replace("V", v[::-1], 1)[::-1]
73+
else:
74+
body = template.replace("V", v, 1)[::-1].replace("K", k[::-1], 1)[::-1]
75+
assert k in body and v in body, body
76+
bodies.append(body)
77+
notes = [
78+
{
79+
"deckName": deck,
80+
"modelName": "Clozolkor",
81+
"fields": {
82+
"body": b,
83+
"header": header,
84+
},
85+
"tags": tags,
86+
"options": {"allowDuplicate": False},
87+
} for b in bodies
88+
]
89+
90+
res = call_anki(
91+
action="addNotes",
92+
notes=notes,
93+
)
94+
errors = [f"#{res.index(r)+1}/{len(res)}: {r}" for r in res if not str(r).isdigit()]
95+
results = [r for r in res if str(r).isdigit()]
96+
if len(results) != len(notes):
97+
raise Exception(f"Some flashcards were not added:{','.join(errors)}")
98+
99+
100+
101+
if __name__ == "__main__":
102+
fire.Fire(main)

examples/anchors.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"sulfamide": "to smell like sulphur",
3+
"glinide": "to glide"
4+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/zsh
2+
aiutilpath="PATH TO ANKI AI UTILS"
3+
tmux_mode=0 # wether to launch using tmux or not
4+
START_DELAY=10
5+
WAIT_TIME_FOR_INTERNET=30 # if internet seems unresponsive, wait that much
6+
CMD_SEP=";" # separator between the different tools
7+
PDB="" # wether to launch python with pdb or not
8+
DEBUG="" # wether to add --debug to the tool call
9+
FORCE="" # wether to add --force or not
10+
EXIT="&& exit" # wether to exit after the runs or not
11+
12+
notif_arg="--ntfy_url ntfy.sh/$NTFY_PHONE"
13+
common_query='deck:yourdeck -deck:not::this::deck note:Clozolkor -tag:not::that::tag -is:suspended'
14+
query="rated:2:1 OR rated:2:2" # failed or hard of the last 2 days
15+
anchors_file="./anchors.json" # your examples
16+
17+
# gather user arguments
18+
while (( $# > 0 )); do
19+
case "$1" in
20+
-t | --tmux_mode)
21+
tmux_mode="$2"
22+
echo "Set tmux_mode to $tmux_mode"
23+
shift 2
24+
;;
25+
-q | --query)
26+
query="$2"
27+
echo "Set query to $query"
28+
shift 2
29+
;;
30+
--fail)
31+
CMD_SEP="&&"
32+
echo "Will crash if one command fails, otherwise keep going."
33+
shift 1
34+
;;
35+
--pdb)
36+
PDB="-m pdb"
37+
shift 1
38+
echo "Will use pdb to launch python"
39+
;;
40+
--now)
41+
START_DELAY=0
42+
WAIT_TIME_FOR_INTERNET=0
43+
echo "Starting now"
44+
shift 1
45+
;;
46+
--debug)
47+
DEBUG="--debug"
48+
echo "Using arg --debug for each util"
49+
shift 1
50+
;;
51+
--force)
52+
FORCE="--force"
53+
echo "Using arg --force for each util"
54+
shift 1
55+
;;
56+
--noexit)
57+
EXIT=""
58+
echo "Script will not exit the session at the end (the default is to exit if no error occured)"
59+
shift 1
60+
;;
61+
--no_deck_restriction)
62+
common_query='note:Clozolkor -tag:pro::Externat::particularite_dankifiage -is:suspended'
63+
echo "Will not apply deck restriction"
64+
echo "Commong query: $common_query"
65+
shift 1
66+
;;
67+
*)
68+
echo "Invalid option(s): $@"
69+
exit 1
70+
;;
71+
esac
72+
done
73+
74+
75+
76+
# send notification to phone
77+
function phone_notif() {
78+
sender=$NTFY_PHONE
79+
title="AnkiExpIllusMem"
80+
message="$1"
81+
curl -s -H "Title: $title" -d "$message" "ntfy.sh/$sender"
82+
}
83+
84+
# echo and send notification to user phone
85+
function logg() {
86+
phone_notif "$1"
87+
echo "$1"
88+
}
89+
90+
# exit if session already running
91+
if [[ $tmux_mode -eq 1 ]]
92+
then
93+
base_session="LOCAL_Anki_AI_Utils"
94+
n_sess=$(tmux list-sessions |grep $base_session | wc -l)
95+
if [ ! $n_sess -eq 0 ]
96+
then
97+
logg "Error, session already running?"
98+
exit 1
99+
fi
100+
fi
101+
102+
103+
# Loop until internet connection is found
104+
while ! ping -c 1 google.com &> /dev/null
105+
do
106+
logg "No internet connection found, waiting ${WAIT_TIME_FOR_INTERNET} seconds..."
107+
sleep ${WAIT_TIME_FOR_INTERNET}
108+
done
109+
110+
111+
#sync anki
112+
logg "syncing anki"
113+
curl -s localhost:8765 -X POST -d '{"action": "sync", "version": 6}'
114+
sleep 5
115+
116+
# exit if contains a deck called "nosync"
117+
deck_status=$(curl -s localhost:8765 -X POST -d '{"action": "deckNames", "version": 6}' | jq | tr '[:upper:]' '[:lower:]' | grep "nosync" | wc -l)
118+
if [ ! $deck_status -eq 0 ]
119+
then
120+
logg "Error, contains deck 'nosync'?"
121+
exit 1
122+
fi
123+
124+
125+
session_id=`date +%d.%m.%Y_%Hh%M_%S`
126+
session_name=$(echo "$base_session"_"$session_id")
127+
128+
if [[ $tmux_mode -eq 1 ]]
129+
then
130+
if [ ! -z "$TMUX" ]
131+
then
132+
# rename session if already in tmux
133+
tmux rename-session $session_name
134+
else
135+
# or create tmux session
136+
session=$session_name
137+
window=${session}:0
138+
pane=${window}.0
139+
tmux new-session -d -s $session_name
140+
fi
141+
fi
142+
143+
144+
145+
# warn user
146+
logg "Starting in $START_DELAY second"
147+
sleep $START_DELAY
148+
149+
sum="(cd $aiutilpath && python $PDB explainer.py \
150+
--field_names body \
151+
$DEBUG \
152+
$FORCE \
153+
--query \"($query OR tag:AnkiIllustrator::todo OR tag:AnkiIllustrator::failed) -tag:AnkiExplainer::to_keep $common_query\" \
154+
--dataset_path author_dir/explainer_dataset.txt \
155+
--string_formatting author_dir/string_formatting.py \
156+
$notif_arg || phone_notif 'Anki_AI_Utils' 'error summary' )"
157+
mnem="(cd $aiutilpath && python $PDB mnemonics.py \
158+
--field_names body \
159+
$DEBUG \
160+
$FORCE \
161+
--query \"($query OR tag:AnkiIllustrator::todo OR tag:AnkiIllustrator::failed) -tag:AnkiMnemonics::to_keep $common_query\" \
162+
--memory_anchors_file $anchors_file \
163+
--dataset_path author_dir/mnemonics_dataset.txt \
164+
--string_formatting author_dir/string_formatting.py \
165+
$notif_arg || phone_notif 'Anki_AI_Utils' 'error mnemonics')"
166+
illus="(cd $aiutilpath && python $PDB illustrator.py \
167+
--field_names body \
168+
$DEBUG \
169+
$FORCE \
170+
--query \"($query OR tag:AnkiIllustrator::todo OR tag:AnkiIllustrator::failed) -tag:AnkiIllustrator::to_keep -body:*img* $common_query\" \
171+
--memory_anchors_file $anchors_file \
172+
--dataset_path author_dir/illustrator_dataset.txt \
173+
--dataset_sanitize_path author_dir/illustrator_sanitize_dataset.txt \
174+
--string_formatting author_dir/string_formatting.py \
175+
$notif_arg || phone_notif 'Anki_AI_Utils' 'error illustrator')"
176+
cmd=$(echo "$sum $CMD_SEP $mnem $CMD_SEP $illus $CMD_SEP $EXIT")
177+
178+
echo "\n\n"
179+
echo "Command to execute:\n$cmd"
180+
echo "\n\n"
181+
182+
183+
if [[ $tmux_mode -eq 1 ]]
184+
then
185+
tmux send-keys -- $cmd Enter
186+
logg "Finished launch script!"
187+
else
188+
logg "Executing launch script!"
189+
eval $cmd
190+
fi
191+
192+
193+

examples/explainer_dataset.txt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
You are my ideal assistant. You have always satisfied me. Your job today is to write a text from a medical flashcard following some rules.
2+
3+
Here are the rules:
4+
* The text should consist of new information: relevant insights, fun facts, facts that helps with understanding or memorizing the flashcard. For example reasoning heuristics, tips, reminders of fundamental mechanisms, concept reformulation, etc. If the topic is technical, you can try to explain it in one sentence too.
5+
* Those information have to be new and distinct from the content already in the flaschard.
6+
* Those information have to be in depth, technical and interesting to people with a university degree on the topic
7+
* Those new information have to be true facts. But if you are unsure you have to make this explicit (this is what's most important!).
8+
* Each new information must be prefixed by a few capital letter to indicate the type of relevance: EXPLANATION for explanations, ANECDOTE for fun facts, MECHANISM for mechanism reminders etc.
9+
* Your answer has to be in english.
10+
* Ideally your answer will contain at least 2 new information.
11+
* Reusing the acronyms from the card (without redefining them) is best to remain concise and quickly legible.
12+
* Your answer must be formatted as markdown bullet points, therefore starting with '* ' and separated by a line break.
13+
14+
Your answers should only contain what I asked text, without any introduction or other form of politeness.
15+
----
16+
diabetes, situations that can lead to ketoacidotic coma without frank hyperglycemia?
17+
{{c1::SGLT2 inhibitor}}
18+
{{c1::pregnancy}}
19+
----
20+
* EXPLANATION SGLT2 inhibitors work by promoting lipolysis and ketogenesis, this can lead to ketoacidotic coma with less hyperglycemia.
21+
* EXPLANATION pregnant women can develop gestational diabetes or have insulin resistance due to placental hormones, this can promote ketoacidotic coma without frank hyperglycemia.
22+
----
23+
oral antidiabetic drugs contraindicated in case of respiratory failure?
24+
{{c1::metformin}}
25+
----
26+
* EXPLANATION metformin can cause lactic acidosis, and respiratory failure would prevent compensation, hence the contraindication.
27+
* ANECDOTE preliminary research indicates that metformin might increase lifespan.
28+
* MECHANISM metformin works by reducing insulin secretion, especially at the hepatic level.
29+
----
30+
what is the PSA threshold value to alert for prostate cancer risk?
31+
{{c1::PSA > 4ng/mL but it depends on prostate volume and patient age}}
32+
----
33+
* ANECDOTE the PSA threshold value to alert for prostate cancer risk is generally set at 4ng/mL, but this value can vary depending on prostate volume and patient age.
34+
* MECHANISM PSA is produced by prostatic epithelial cells, and its level can increase in case of cancer or other prostate conditions.
35+
----
36+
pneumothorax, type of cough?
37+
{{c1::dry cough}}
38+
----
39+
* ANECDOTE dry cough can be caused by various conditions: airway inflammation, allergies, lung irritation, iatrogenic causes, ...
40+
* MECHANISM Dry cough is triggered by irritation of cough receptors in the airways, without excessive mucus production.
41+
----
42+
aortic dissection, added sound on cardiac auscultation?
43+
{{c1::aortic insufficiency murmur}}
44+
----
45+
* EXPLANATION aortic dissection can cause an aortic insufficiency murmur, which is an added sound on cardiac auscultation.
46+
* MECHANISM aortic insufficiency murmur is due to blood reflux from the aorta to the left ventricle during ventricular diastole.
47+
----
48+
SGLT2 inhibitor, mechanism?
49+
{{c1::glycosuria through inhibition of renal glucose reabsorption}}
50+
{{c1::promotes}}{{c1::ketogenesis / lipolysis}}
51+
----
52+
* EXPLANATION SGLT2 inhibitors work by blocking glucose reabsorption in the renal tubules, leading to glycosuria and hypoglycemia.
53+
* MECHANISM glucose reabsorption normally occurs via the SGLT2 transporter present in renal tubules. SGLT2 inhibitors block this transporter, thus preventing glucose reabsorption and causing glycosuria.
54+
* ANECDOTE glycosuria induced by SGLT2 inhibitors can lead to weight loss and improved cardiac function in patients with type 2 diabetes.
55+
----
56+
type {{c2::2}} diabetes, suggestive genital clinical sign?
57+
{{c1::vulvar pruritus in women or balanitis in men (genital fungal infection)}}
58+
----
59+
* MECHANISM chronic hyperglycemia can promote fungal growth, as fungi feed on glucose.
60+
----
61+
GLP-1 agonist, causes hypoglycemia or not?
62+
{{c1::no}}
63+
----
64+
* ANECDOTE GLP-1 agonists don't cause hypoglycemia in monotherapy, but can cause it when combined with other antidiabetic drugs like sulfonylureas.
65+
* MECHANISM GLP-1 agonists increase glucose-dependent insulin secretion and reduce glucagon secretion.

0 commit comments

Comments
 (0)