CoolFace
Apppublic

blizzarman/polyglot-tutor

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
models.py99 linesDownload Raw Back to domain
1"""Core domain entities, persistence- and framework-agnostic.2 3Languages are first-class data (`source_lang` / `target_lang`, ISO 639-1) rather4than constants: English is the launch target, but the model must not need a5migration to add Spanish or German later.6"""7 8from datetime import UTC, datetime9from enum import StrEnum10from typing import Any11from uuid import uuid412 13from pydantic import BaseModel, Field14 15 16def _new_id() -> str:17    return uuid4().hex18 19 20def _now() -> datetime:21    return datetime.now(UTC)22 23 24class CEFRLevel(StrEnum):25    A1 = "A1"26    A2 = "A2"27    B1 = "B1"28    B2 = "B2"29    C1 = "C1"30    C2 = "C2"31 32    @property33    def rank(self) -> int:34        """0-based ordinal position (A1=0 ... C2=5), for ordinal metrics and adjacency."""35        return list(CEFRLevel).index(self)36 37    def distance(self, other: "CEFRLevel") -> int:38        """Absolute level distance; `<= 1` is the classic 'adjacent accuracy' criterion."""39        return abs(self.rank - other.rank)40 41 42class ExerciseType(StrEnum):43    READING_QA = "reading_qa"  # M1 — comprehension questions on a CEFR-graded text44    DICTATION = "dictation"  # M2 — TTS audio, learner types what they hear45    WRITING = "writing"  # M3 — free writing, LLM correction with typed errors46    PRONUNCIATION = "pronunciation"  # M5 — read-aloud with pronunciation scoring47 48 49class Learner(BaseModel):50    id: str = Field(default_factory=_new_id)51    display_name: str52    source_lang: str = "fr"53    target_lang: str = "en"54    created_at: datetime = Field(default_factory=_now)55 56 57class Exercise(BaseModel):58    """A generated, cacheable exercise.59 60    `payload` is type-specific content (text, questions, audio reference...).61    Generated exercises are content-addressed upstream (hash of source text +62    prompt version) so LLM cost stays ~0 and evals are reproducible.63    """64 65    id: str = Field(default_factory=_new_id)66    type: ExerciseType67    target_lang: str68    cefr_level: CEFRLevel69    payload: dict[str, Any] = Field(default_factory=dict)70    created_at: datetime = Field(default_factory=_now)71 72 73class Attempt(BaseModel):74    """One learner answer to one exercise, with its scoring/feedback."""75 76    id: str = Field(default_factory=_new_id)77    learner_id: str78    exercise_id: str79    response: dict[str, Any] = Field(default_factory=dict)80    score: float | None = Field(default=None, ge=0.0, le=1.0)81    feedback: dict[str, Any] = Field(default_factory=dict)82    created_at: datetime = Field(default_factory=_now)83 84 85class ReviewItem(BaseModel):86    """An atomic, schedulable knowledge item (vocab word, recurring error, ...).87 88    `scheduler_state` is an opaque dict owned by the SRS engine (FSRS-ready in89    M4) so the domain model is not coupled to one algorithm's parameters.90    """91 92    id: str = Field(default_factory=_new_id)93    learner_id: str94    kind: str  # "vocab" | "error" | ... (free-form until M4 hardens it)95    content: dict[str, Any] = Field(default_factory=dict)96    due_at: datetime | None = None97    scheduler_state: dict[str, Any] = Field(default_factory=dict)98    created_at: datetime = Field(default_factory=_now)99