| """ |
| NAH-13 — weak-topic targeting: next_card biases toward low-average-grade topics. |
| |
| No model needed (pure scheduling logic). Run: python3 test_weak_topic.py |
| """ |
| import learning_engine as le |
| from schema import new_card |
|
|
|
|
| def _deck(): |
| return [ |
| new_card("strong q1", "a", topic="Algebra", card_id="alg1"), |
| new_card("strong q2", "a", topic="Algebra", card_id="alg2"), |
| new_card("weak q1", "a", topic="Geometry", card_id="geo1"), |
| new_card("weak q2", "a", topic="Geometry", card_id="geo2"), |
| ] |
|
|
|
|
| def test_no_history_keeps_order(): |
| s = le.init_session(_deck()) |
| |
| assert le.next_card(s)["id"] == "alg1" |
| assert s["queue"][0] == "alg1" |
| print("ok no history -> normal queue order") |
|
|
|
|
| def test_weak_topic_surfaces_first(): |
| s = le.init_session(_deck()) |
| |
| s["history"] = [ |
| {"card_id": "alg1", "topic": "Algebra", "grade": 5, "user_answer": ""}, |
| {"card_id": "geo1", "topic": "Geometry", "grade": 1, "user_answer": ""}, |
| ] |
| |
| s["queue"] = ["alg2", "geo2", "alg1"] |
| card = le.next_card(s) |
| assert card["topic"] == "Geometry", f"expected weak topic first, got {card}" |
| assert s["queue"][0] == card["id"], "chosen card must be rotated to the front" |
| print("ok weak topic (Geometry) surfaced ahead of strong topic") |
|
|
|
|
| def test_lookahead_window_respected(): |
| s = le.init_session(_deck()) |
| s["history"] = [{"card_id": "geo1", "topic": "Geometry", "grade": 0, "user_answer": ""}] |
| |
| s["queue"] = ["alg1", "alg2", "alg2", "alg1", "geo2"] |
| le.WEAK_LOOKAHEAD |
| card = le.next_card(s) |
| assert card["topic"] == "Algebra", "weak card outside window should not jump" |
| print("ok weak card beyond lookahead window left in place") |
|
|
|
|
| def test_apply_result_contract_holds(): |
| |
| s = le.init_session(_deck()) |
| s["history"] = [{"card_id": "geo1", "topic": "Geometry", "grade": 1, "user_answer": ""}] |
| s["queue"] = ["alg1", "geo2"] |
| card = le.next_card(s) |
| assert card["id"] == "geo2" |
| before = len(s["queue"]) |
| le.apply_result(s, card, {"score": 4, "correct": True, |
| "explanation": "", "missed_concept": ""}) |
| assert "geo2" not in s["queue"][:1], "served card should be popped from front" |
| assert len(s["queue"]) == before, "popped one, re-inserted one" |
| print("ok apply_result still pops the served card from the front") |
|
|
|
|
| if __name__ == "__main__": |
| test_no_history_keeps_order() |
| test_weak_topic_surfaces_first() |
| test_lookahead_window_respected() |
| test_apply_result_contract_holds() |
| print("\nAll NAH-13 weak-topic tests passed.") |
|
|