klausur-service (11 files): - cv_gutter_repair, ocr_pipeline_regression, upload_api - ocr_pipeline_sessions, smart_spell, nru_worksheet_generator - ocr_pipeline_overlays, mail/aggregator, zeugnis_api - cv_syllable_detect, self_rag backend-lehrer (17 files): - classroom_engine/suggestions, generators/quiz_generator - worksheets_api, llm_gateway/comparison, state_engine_api - classroom/models (→ 4 submodules), services/file_processor - alerts_agent/api/wizard+digests+routes, content_generators/pdf - classroom/routes/sessions, llm_gateway/inference - classroom_engine/analytics, auth/keycloak_auth - alerts_agent/processing/rule_engine, ai_processor/print_versions agent-core (5 files): - brain/memory_store, brain/knowledge_graph, brain/context_manager - orchestrator/supervisor, sessions/session_manager admin-lehrer (5 components): - GridOverlay, StepGridReview, DevOpsPipelineSidebar - DataFlowDiagram, sbom/wizard/page website (2 files): - DependencyMap, lehrer/abitur-archiv Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
144 lines
4.3 KiB
Python
144 lines
4.3 KiB
Python
"""
|
|
State Engine API - Pydantic Models und Helper Functions.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, Any, List, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from state_engine import (
|
|
SchoolYearPhase,
|
|
ClassSummary,
|
|
Event,
|
|
TeacherContext,
|
|
TeacherStats,
|
|
get_phase_info,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# In-Memory Storage (später durch DB ersetzen)
|
|
# ============================================================================
|
|
|
|
_teacher_contexts: Dict[str, TeacherContext] = {}
|
|
_milestones: Dict[str, List[str]] = {} # teacher_id -> milestones
|
|
|
|
|
|
# ============================================================================
|
|
# Pydantic Models
|
|
# ============================================================================
|
|
|
|
class MilestoneRequest(BaseModel):
|
|
"""Request zum Abschließen eines Meilensteins."""
|
|
milestone: str = Field(..., description="Name des Meilensteins")
|
|
|
|
|
|
class TransitionRequest(BaseModel):
|
|
"""Request für Phasen-Übergang."""
|
|
target_phase: str = Field(..., description="Zielphase")
|
|
|
|
|
|
class ContextResponse(BaseModel):
|
|
"""Response mit TeacherContext."""
|
|
context: Dict[str, Any]
|
|
phase_info: Dict[str, Any]
|
|
|
|
|
|
class SuggestionsResponse(BaseModel):
|
|
"""Response mit Vorschlägen."""
|
|
suggestions: List[Dict[str, Any]]
|
|
current_phase: str
|
|
phase_display_name: str
|
|
priority_counts: Dict[str, int]
|
|
|
|
|
|
class DashboardResponse(BaseModel):
|
|
"""Response mit Dashboard-Daten."""
|
|
context: Dict[str, Any]
|
|
suggestions: List[Dict[str, Any]]
|
|
stats: Dict[str, Any]
|
|
upcoming_events: List[Dict[str, Any]]
|
|
progress: Dict[str, Any]
|
|
phases: List[Dict[str, Any]]
|
|
|
|
|
|
# ============================================================================
|
|
# Helper Functions
|
|
# ============================================================================
|
|
|
|
def get_or_create_context(teacher_id: str) -> TeacherContext:
|
|
"""
|
|
Holt oder erstellt TeacherContext.
|
|
|
|
In Produktion würde dies aus der Datenbank geladen.
|
|
"""
|
|
if teacher_id not in _teacher_contexts:
|
|
now = datetime.now()
|
|
school_year_start = datetime(now.year if now.month >= 8 else now.year - 1, 8, 1)
|
|
weeks_since_start = (now - school_year_start).days // 7
|
|
|
|
month = now.month
|
|
if month in [8, 9]:
|
|
phase = SchoolYearPhase.SCHOOL_YEAR_START
|
|
elif month in [10, 11]:
|
|
phase = SchoolYearPhase.TEACHING_SETUP
|
|
elif month == 12:
|
|
phase = SchoolYearPhase.PERFORMANCE_1
|
|
elif month in [1, 2]:
|
|
phase = SchoolYearPhase.SEMESTER_END
|
|
elif month in [3, 4]:
|
|
phase = SchoolYearPhase.TEACHING_2
|
|
elif month in [5, 6]:
|
|
phase = SchoolYearPhase.PERFORMANCE_2
|
|
else:
|
|
phase = SchoolYearPhase.YEAR_END
|
|
|
|
_teacher_contexts[teacher_id] = TeacherContext(
|
|
teacher_id=teacher_id,
|
|
school_id=str(uuid.uuid4()),
|
|
school_year_id=str(uuid.uuid4()),
|
|
federal_state="niedersachsen",
|
|
school_type="gymnasium",
|
|
school_year_start=school_year_start,
|
|
current_phase=phase,
|
|
phase_entered_at=now - timedelta(days=7),
|
|
weeks_since_start=weeks_since_start,
|
|
days_in_phase=7,
|
|
classes=[],
|
|
total_students=0,
|
|
upcoming_events=[],
|
|
completed_milestones=_milestones.get(teacher_id, []),
|
|
pending_milestones=[],
|
|
stats=TeacherStats(),
|
|
)
|
|
|
|
return _teacher_contexts[teacher_id]
|
|
|
|
|
|
def update_context_from_services(ctx: TeacherContext) -> TeacherContext:
|
|
"""
|
|
Aktualisiert Kontext mit Daten aus anderen Services.
|
|
|
|
In Produktion würde dies von school-service, gradebook etc. laden.
|
|
"""
|
|
ctx.days_in_phase = (datetime.now() - ctx.phase_entered_at).days
|
|
ctx.completed_milestones = _milestones.get(ctx.teacher_id, [])
|
|
|
|
phase_info = get_phase_info(ctx.current_phase)
|
|
ctx.pending_milestones = [
|
|
m for m in phase_info.required_actions
|
|
if m not in ctx.completed_milestones
|
|
]
|
|
|
|
return ctx
|
|
|
|
|
|
def get_phase_display_name(phase: str) -> str:
|
|
"""Gibt Display-Name für Phase zurück."""
|
|
try:
|
|
return get_phase_info(SchoolYearPhase(phase)).display_name
|
|
except (ValueError, KeyError):
|
|
return phase
|