Spaces:
Running on Zero
Running on Zero
| """ | |
| 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()) | |
| ) | |