feat(cefr): dual mistake ingestion, 1-tap options breakdown, and 260 curated questions

This commit is contained in:
2026-10-06 15:57:07 +03:00
parent a00b330f74
commit a2eb26ced5
7 changed files with 1277 additions and 95 deletions
+36 -8
View File
@@ -1376,10 +1376,10 @@ def test_cefr_testing_and_gap_ingestion():
assert len(levels) == 4
level_map = {l["level"]: l for l in levels}
assert "A1" in level_map and "A2" in level_map and "B1" in level_map and "B2" in level_map
assert level_map["A1"]["total_questions"] == 40
assert level_map["A2"]["total_questions"] == 40
assert level_map["B1"]["total_questions"] == 40
assert level_map["B2"]["total_questions"] == 40
assert level_map["A1"]["total_questions"] >= 65
assert level_map["A2"]["total_questions"] >= 65
assert level_map["B1"]["total_questions"] >= 65
assert level_map["B2"]["total_questions"] >= 65
# 4. Fetch quiz batch
quiz_res = client.get("/api/cefr/quiz?level=A2&count=5")
@@ -1393,8 +1393,12 @@ def test_cefr_testing_and_gap_ingestion():
assert "clue" in q1
assert len(q1["options"]) == 4
# 5. Answer INCORRECTLY -> Automated Gap Ingestion into SRS!
wrong_opt = "__completely_wrong_option__"
# 5. Answer INCORRECTLY -> Automated Dual Gap Ingestion (target + selected option)
# Pick a distractor from the question's actual options
# Find option that is not correct (we test with the first distractor)
# Let's inspect options and find one that is wrong
wrong_opt = [opt for opt in q1["options"] if opt.lower() != q1.get("term", "").lower()][0]
ans_wrong = client.post("/api/cefr/answer", json={
"question_id": q1["id"],
"selected_option": wrong_opt
@@ -1402,12 +1406,15 @@ def test_cefr_testing_and_gap_ingestion():
assert ans_wrong.status_code == 200
w_data = ans_wrong.json()
assert w_data["correct"] is False
assert w_data["card_created"] is True
assert w_data["card_created"] is True or w_data["card_updated"] is True
assert w_data["card_id"] is not None
assert w_data["correct_option"] != ""
assert w_data["explanation"] != ""
assert w_data["selected_card_id"] is not None
assert w_data["selected_card_term"] == wrong_opt
assert len(w_data["options_breakdown"]) == 4
# Verify the card was actually created in vocabulary_cards with state 'learning' and level tag
# Verify target word card in SRS
card_res = client.get(f"/api/cards/{w_data['card_id']}")
assert card_res.status_code == 200
ingested_card = card_res.json()
@@ -1415,6 +1422,27 @@ def test_cefr_testing_and_gap_ingestion():
assert "level-a2" in ingested_card["tags"]
assert "cefr-test" in ingested_card["tags"]
# Verify selected distractor card in SRS
sel_card_res = client.get(f"/api/cards/{w_data['selected_card_id']}")
assert sel_card_res.status_code == 200
sel_card = sel_card_res.json()
assert sel_card["srs_state"] == "learning"
assert "level-a2" in sel_card["tags"]
assert "cefr-test" in sel_card["tags"]
# Test manual ingestion of remaining option via /api/cefr/add-word
other_opt = [opt for opt in q1["options"] if opt != w_data["correct_option"] and opt != wrong_opt][0]
add_word_res = client.post("/api/cefr/add-word", json={
"level": "A2",
"term": other_opt,
"context_sentence": q1["sentence"]
})
assert add_word_res.status_code == 200
add_word_data = add_word_res.json()
assert add_word_data["status"] == "ok"
assert add_word_data["card_id"] is not None
assert add_word_data["term"] == other_opt
# 6. Check that mode='mistakes' returns this question
mistakes_res = client.get("/api/cefr/quiz?level=A2&mode=mistakes")
assert mistakes_res.status_code == 200