Files
breakpilot-lehrer/backend-lehrer/learning_units.py
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

179 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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