| """ |
| 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 |
|
|
|
|
| |
|
|
| class Card(TypedDict): |
| id: str |
| question: str |
| answer: str |
| topic: str |
| source_chunk: str |
| difficulty: int |
| parent_id: Optional[str] |
|
|
|
|
| class CardState(TypedDict): |
| card_id: str |
| ease: float |
| interval: int |
| reps: int |
| lapses: int |
| last_grade: int |
|
|
|
|
| class GradeResult(TypedDict): |
| score: int |
| correct: bool |
| explanation: str |
| missed_concept: str |
|
|
|
|
| class Session(TypedDict): |
| deck: list[Card] |
| states: dict[str, CardState] |
| queue: list[str] |
| history: list[dict] |
| streak: int |
|
|
|
|
| |
|
|
| 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()) |
| ) |
|
|