Restructure: Move final 16 root files into packages (backend-lehrer)
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 37s
CI / test-go-edu-search (push) Successful in 35s
CI / test-python-klausur (push) Failing after 2m41s
CI / test-python-agent-core (push) Successful in 30s
CI / test-nodejs-website (push) Successful in 38s

classroom/ (+2): state_engine_api, state_engine_models
vocabulary/ (2): api, db
worksheets/ (2): api, models
services/  (+6): audio, email, translation, claude_vision, ai_processor, story_generator
api/        (4): school, klausur_proxy, progress, user_language

Only main.py + config.py remain at root. 16 shims added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 22:50:37 +02:00
parent 6be555fb7c
commit cba877c65a
36 changed files with 3712 additions and 3564 deletions

View File

@@ -1,131 +1,4 @@
"""
Progress API — Tracks student learning progress per unit.
Stores coins, crowns, streak data, and exercise completion stats.
Uses JSON file storage (same pattern as learning_units.py).
"""
import os
import json
import logging
from datetime import datetime, date
from typing import Dict, Any, Optional, List
from pathlib import Path
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/progress",
tags=["progress"],
)
PROGRESS_DIR = os.path.expanduser("~/Arbeitsblaetter/Lerneinheiten/progress")
def _ensure_dir():
os.makedirs(PROGRESS_DIR, exist_ok=True)
def _progress_path(unit_id: str) -> Path:
return Path(PROGRESS_DIR) / f"{unit_id}.json"
def _load_progress(unit_id: str) -> Dict[str, Any]:
path = _progress_path(unit_id)
if path.exists():
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
return {
"unit_id": unit_id,
"coins": 0,
"crowns": 0,
"streak_days": 0,
"last_activity": None,
"exercises": {
"flashcards": {"completed": 0, "correct": 0, "incorrect": 0},
"quiz": {"completed": 0, "correct": 0, "incorrect": 0},
"type": {"completed": 0, "correct": 0, "incorrect": 0},
"story": {"generated": 0},
},
"created_at": datetime.now().isoformat(),
}
def _save_progress(unit_id: str, data: Dict[str, Any]):
_ensure_dir()
path = _progress_path(unit_id)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
class RewardPayload(BaseModel):
exercise_type: str # flashcards, quiz, type, story
correct: bool = True
first_try: bool = True
@router.get("/{unit_id}")
def get_progress(unit_id: str):
"""Get learning progress for a unit."""
return _load_progress(unit_id)
@router.post("/{unit_id}/reward")
def add_reward(unit_id: str, payload: RewardPayload):
"""Record an exercise result and award coins."""
progress = _load_progress(unit_id)
# Update exercise stats
ex = progress["exercises"].get(payload.exercise_type, {"completed": 0, "correct": 0, "incorrect": 0})
ex["completed"] = ex.get("completed", 0) + 1
if payload.correct:
ex["correct"] = ex.get("correct", 0) + 1
else:
ex["incorrect"] = ex.get("incorrect", 0) + 1
progress["exercises"][payload.exercise_type] = ex
# Award coins
if payload.correct:
coins = 3 if payload.first_try else 1
else:
coins = 0
progress["coins"] = progress.get("coins", 0) + coins
# Update streak
today = date.today().isoformat()
last = progress.get("last_activity")
if last != today:
if last == (date.today().replace(day=date.today().day - 1)).isoformat() if date.today().day > 1 else None:
progress["streak_days"] = progress.get("streak_days", 0) + 1
elif last != today:
progress["streak_days"] = 1
progress["last_activity"] = today
# Award crowns for milestones
total_correct = sum(
e.get("correct", 0) for e in progress["exercises"].values() if isinstance(e, dict)
)
progress["crowns"] = total_correct // 20 # 1 crown per 20 correct answers
_save_progress(unit_id, progress)
return {
"coins_awarded": coins,
"total_coins": progress["coins"],
"crowns": progress["crowns"],
"streak_days": progress["streak_days"],
}
@router.get("/")
def list_all_progress():
"""List progress for all units."""
_ensure_dir()
results = []
for f in Path(PROGRESS_DIR).glob("*.json"):
with open(f, "r", encoding="utf-8") as fh:
results.append(json.load(fh))
return results
# Backward-compat shim -- module moved to api/progress.py
import importlib as _importlib
import sys as _sys
_sys.modules[__name__] = _importlib.import_module("api.progress")