fix(cefr): resolve authentic context sentence per term and repair legacy SRS cards

- Add resolve_context_sentence_for_term to replace placeholder blanks and lookup authentic sentences from cefr_questions or books
- Fix dual mistake ingestion in process_cefr_answer to assign distinct context to target and chosen distractor
- Provide authentic context_sentence in options_breakdown and pass it via data-context in cefr.js
- Add repair_mismatched_cefr_contexts in database.py to automatically repair cards with placeholder blanks
- Bump Service Worker cache to notes-v5.9
- Add test_cefr_context_resolution_and_repair covering resolution, dual ingestion, manual add, and DB repair
This commit is contained in:
2026-10-06 22:17:51 +03:00
parent 82b7a654ca
commit 3098d3016d
5 changed files with 230 additions and 10 deletions
+112
View File
@@ -1756,6 +1756,118 @@ Authors' Addresses
assert rfc_books[0]["id"] == book_id
def test_cefr_context_resolution_and_repair():
from app.cefr import resolve_context_sentence_for_term, process_cefr_answer, ingest_cefr_word
from app.database import repair_mismatched_cefr_contexts
with get_db() as conn:
# 1. Test resolve_context_sentence_for_term
# Case A: candidate has the term and placeholder '___'
res_a = resolve_context_sentence_for_term(
conn,
"decision",
"Choosing a career path is a major life ___."
)
assert res_a == "Choosing a career path is a major life decision."
assert "___" not in res_a
# Case B: candidate does NOT match the term, term exists in cefr_questions
res_b = resolve_context_sentence_for_term(
conn,
"opportunity",
"Choosing a career path is a major life ___."
)
assert "opportunity" in res_b.lower()
assert "career path" not in res_b
assert "___" not in res_b
# 2. Test process_cefr_answer mistake ingestion
# Seed test user
user_row = conn.execute("SELECT id FROM users WHERE username = 'testadmin'").fetchone()
if not user_row:
create_user("testadmin", "testpass12345")
user_row = conn.execute("SELECT id FROM users WHERE username = 'testadmin'").fetchone()
assert user_row is not None
user_id = user_row["id"]
# Find or create a question for 'decision'
q = conn.execute(
"SELECT id, level, term, sentence, options_json FROM cefr_questions WHERE term = 'decision' LIMIT 1"
).fetchone()
if not q:
conn.execute(
"""
INSERT INTO cefr_questions (level, term, translation, sentence, options_json, explanation)
VALUES ('B1', 'decision', 'решение', 'Choosing a career path is a major life ___.', '["decision", "opportunity", "behavior", "relationship"]', 'Major decision.')
"""
)
q = conn.execute("SELECT id, level, term, sentence, options_json FROM cefr_questions WHERE term = 'decision' LIMIT 1").fetchone()
result = process_cefr_answer(conn, user_id, q["id"], "opportunity")
assert result["correct"] is False
assert result["correct_option"] == "decision"
assert result["selected_option"] == "opportunity"
# Check options_breakdown has distinct authentic context sentences
breakdown = result["options_breakdown"]
assert len(breakdown) == 4
for item in breakdown:
assert "context_sentence" in item
assert "___" not in item["context_sentence"]
assert item["term"].lower() in item["context_sentence"].lower()
# Check vocabulary_cards in DB:
target_card = conn.execute(
"SELECT * FROM vocabulary_cards WHERE user_id = ? AND lower(term) = 'decision'",
(user_id,)
).fetchone()
assert target_card is not None
assert "decision" in target_card["context_sentence"].lower()
assert "___" not in target_card["context_sentence"]
chosen_card = conn.execute(
"SELECT * FROM vocabulary_cards WHERE user_id = ? AND lower(term) = 'opportunity'",
(user_id,)
).fetchone()
assert chosen_card is not None
assert "opportunity" in chosen_card["context_sentence"].lower()
assert "career path" not in chosen_card["context_sentence"]
assert "___" not in chosen_card["context_sentence"]
# 3. Test ingest_cefr_word
added_word = ingest_cefr_word(
conn,
user_id=user_id,
level="B1",
term="behavior",
context_sentence="Choosing a career path is a major life ___."
)
assert added_word["term"] == "behavior"
assert "behavior" in added_word["context_sentence"].lower()
assert "career path" not in added_word["context_sentence"]
assert "___" not in added_word["context_sentence"]
# 4. Test repair_mismatched_cefr_contexts
# Insert a card with corrupted blank
conn.execute("DELETE FROM vocabulary_cards WHERE user_id = ? AND lower(term) = 'negotiate'", (user_id,))
conn.execute(
"""
INSERT INTO vocabulary_cards (user_id, term, translation, context_sentence, tags)
VALUES (?, 'negotiate', 'вести переговоры', 'With persistent practice, you can ___ fluency in English.', 'cefr-test')
""",
(user_id,)
)
corrupted = conn.execute("SELECT context_sentence FROM vocabulary_cards WHERE user_id = ? AND term = 'negotiate'", (user_id,)).fetchone()
assert "___" in corrupted["context_sentence"]
repair_mismatched_cefr_contexts(conn)
repaired = conn.execute("SELECT context_sentence FROM vocabulary_cards WHERE user_id = ? AND term = 'negotiate'", (user_id,)).fetchone()
assert "___" not in repaired["context_sentence"]
assert "negotiate" in repaired["context_sentence"].lower()