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:
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user