from __future__ import annotations from pydantic import BaseModel, Field from typing import List, Dict, Optional from pathlib import Path from datetime import datetime import uuid import json import threading # Basisverzeichnis für Arbeitsblätter & Lerneinheiten BASE_DIR = Path.home() / "Arbeitsblaetter" LEARNING_UNITS_DIR = BASE_DIR / "Lerneinheiten" LEARNING_UNITS_FILE = LEARNING_UNITS_DIR / "learning_units.json" # Thread-Lock, damit Dateizugriffe sicher bleiben _lock = threading.Lock() class LearningUnitBase(BaseModel): title: str = Field(..., description="Titel der Lerneinheit, z.B. 'Das Auge – Klasse 7'") description: Optional[str] = Field(None, description="Freitext-Beschreibung") topic: Optional[str] = Field(None, description="Kurz-Thema, z.B. 'Auge'") grade_level: Optional[str] = Field(None, description="Klassenstufe, z.B. '7'") language: Optional[str] = Field("de", description="Hauptsprache der Lerneinheit (z.B. 'de', 'tr')") worksheet_files: List[str] = Field( default_factory=list, description="Liste der zugeordneten Arbeitsblatt-Dateien (Basenames oder Pfade)" ) status: str = Field( "raw", description="Pipeline-Status: raw, cleaned, qa_generated, mc_generated, cloze_generated" ) class LearningUnitCreate(LearningUnitBase): """Payload zum Erstellen einer neuen Lerneinheit.""" pass class LearningUnitUpdate(BaseModel): """Teil-Update für eine Lerneinheit.""" title: Optional[str] = None description: Optional[str] = None topic: Optional[str] = None grade_level: Optional[str] = None language: Optional[str] = None worksheet_files: Optional[List[str]] = None status: Optional[str] = None class LearningUnit(LearningUnitBase): id: str created_at: datetime updated_at: datetime @classmethod def from_dict(cls, data: Dict) -> "LearningUnit": data = data.copy() if isinstance(data.get("created_at"), str): data["created_at"] = datetime.fromisoformat(data["created_at"]) if isinstance(data.get("updated_at"), str): data["updated_at"] = datetime.fromisoformat(data["updated_at"]) return cls(**data) def to_dict(self) -> Dict: d = self.dict() d["created_at"] = self.created_at.isoformat() d["updated_at"] = self.updated_at.isoformat() return d def _ensure_storage(): """Sorgt dafür, dass der Ordner und die JSON-Datei existieren.""" LEARNING_UNITS_DIR.mkdir(parents=True, exist_ok=True) if not LEARNING_UNITS_FILE.exists(): with LEARNING_UNITS_FILE.open("w", encoding="utf-8") as f: json.dump({}, f) def _load_all_units() -> Dict[str, Dict]: _ensure_storage() with LEARNING_UNITS_FILE.open("r", encoding="utf-8") as f: try: data = json.load(f) if not isinstance(data, dict): return {} return data except json.JSONDecodeError: return {} def _save_all_units(raw: Dict[str, Dict]) -> None: _ensure_storage() with LEARNING_UNITS_FILE.open("w", encoding="utf-8") as f: json.dump(raw, f, ensure_ascii=False, indent=2) def list_learning_units() -> List[LearningUnit]: with _lock: raw = _load_all_units() return [LearningUnit.from_dict(v) for v in raw.values()] def get_learning_unit(unit_id: str) -> Optional[LearningUnit]: with _lock: raw = _load_all_units() data = raw.get(unit_id) if not data: return None return LearningUnit.from_dict(data) def create_learning_unit(payload: LearningUnitCreate) -> LearningUnit: now = datetime.utcnow() lu = LearningUnit( id=str(uuid.uuid4()), created_at=now, updated_at=now, **payload.dict() ) with _lock: raw = _load_all_units() raw[lu.id] = lu.to_dict() _save_all_units(raw) return lu def update_learning_unit(unit_id: str, payload: LearningUnitUpdate) -> Optional[LearningUnit]: with _lock: raw = _load_all_units() existing = raw.get(unit_id) if not existing: return None lu = LearningUnit.from_dict(existing) update_data = payload.dict(exclude_unset=True) for field, value in update_data.items(): setattr(lu, field, value) lu.updated_at = datetime.utcnow() raw[lu.id] = lu.to_dict() _save_all_units(raw) return lu def delete_learning_unit(unit_id: str) -> bool: with _lock: raw = _load_all_units() if unit_id not in raw: return False del raw[unit_id] _save_all_units(raw) return True def attach_worksheets(unit_id: str, worksheet_files: List[str]) -> Optional[LearningUnit]: """ Hängt eine Liste von Arbeitsblatt-Dateien an eine bestehende Lerneinheit an. Doppelte Einträge werden vermieden. """ with _lock: raw = _load_all_units() existing = raw.get(unit_id) if not existing: return None lu = LearningUnit.from_dict(existing) current_set = set(lu.worksheet_files) for f in worksheet_files: current_set.add(f) lu.worksheet_files = sorted(current_set) lu.updated_at = datetime.utcnow() raw[lu.id] = lu.to_dict() _save_all_units(raw) return lu