""" Recall — shared data contract (§4 of the main plan). This is the single source of truth all three modules build against. We use plain dicts (Gradio gr.State friendly) + light dataclasses/factories so nobody is blocked on each other. DO NOT change these shapes without a team sync — content_pipeline, learning_engine, and app.py all depend on them. """ from __future__ import annotations import uuid from typing import Optional, TypedDict # ---- Core types ------------------------------------------------------------ class Card(TypedDict): id: str question: str answer: str # reference answer topic: str # short tag, e.g. "Photosynthesis" source_chunk: str # text it came from (grounding / explanations) difficulty: int # 1 (easy) .. 3 (hard) parent_id: Optional[str] # set when this card is a generated follow-up class CardState(TypedDict): card_id: str ease: float # SM-2-style, starts 2.5 interval: int # positions until it reappears in the queue reps: int lapses: int last_grade: int # 0..5 from the grader class GradeResult(TypedDict): score: int # 0..5 correct: bool # score >= 3 explanation: str # shown to the user missed_concept: str # seed for follow-up generation class Session(TypedDict): deck: list[Card] states: dict[str, CardState] # card_id -> CardState queue: list[str] # ordered card_ids to serve history: list[dict] # {card_id, user_answer, grade} streak: int # ---- Factories (use these instead of building dicts by hand) --------------- def new_card( question: str, answer: str, topic: str = "General", source_chunk: str = "", difficulty: int = 1, parent_id: Optional[str] = None, card_id: Optional[str] = None, ) -> Card: return Card( id=card_id or str(uuid.uuid4()), question=question, answer=answer, topic=topic, source_chunk=source_chunk, difficulty=difficulty, parent_id=parent_id, ) def new_card_state(card_id: str) -> CardState: return CardState( card_id=card_id, ease=2.5, interval=1, reps=0, lapses=0, last_grade=0 ) def new_grade(score: int, explanation: str, missed_concept: str = "") -> GradeResult: score = max(0, min(5, int(score))) return GradeResult( score=score, correct=score >= 3, explanation=explanation, missed_concept=missed_concept, ) def validate_card(card: dict) -> bool: """Cheap guard so a malformed model card never crashes the deck.""" required = ("id", "question", "answer", "topic", "source_chunk", "difficulty", "parent_id") return ( isinstance(card, dict) and all(k in card for k in required) and bool(str(card.get("question", "")).strip()) and bool(str(card.get("answer", "")).strip()) )