Files
breakpilot-lehrer/backend-lehrer/certificates_models.py
Benjamin Admin 451365a312 [split-required] Split remaining 500-680 LOC files (final batch)
website (17 pages + 3 components):
- multiplayer/wizard, middleware/wizard+test-wizard, communication
- builds/wizard, staff-search, voice, sbom/wizard
- foerderantrag, mail/tasks, tools/communication, sbom
- compliance/evidence, uni-crawler, brandbook (already done)
- CollectionsTab, IngestionTab, RiskHeatmap

backend-lehrer (5 files):
- letters_api (641 → 2), certificates_api (636 → 2)
- alerts_agent/db/models (636 → 3)
- llm_gateway/communication_service (614 → 2)
- game/database already done in prior batch

klausur-service (2 files):
- hybrid_vocab_extractor (664 → 2)
- klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2)

voice-service (3 files):
- bqas/rag_judge (618 → 3), runner (529 → 2)
- enhanced_task_orchestrator (519 → 2)

studio-v2 (6 files):
- korrektur/[klausurId] (578 → 4), fairness (569 → 2)
- AlertsWizard (552 → 2), OnboardingWizard (513 → 2)
- korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:56:45 +02:00

185 lines
5.8 KiB
Python

"""
Certificates Models - Pydantic models and enums for Zeugnisverwaltung.
"""
from datetime import datetime
from typing import Optional, List, Dict
from enum import Enum
from pydantic import BaseModel, Field
# =============================================================================
# Enums
# =============================================================================
class CertificateType(str, Enum):
"""Typen von Zeugnissen."""
HALBJAHR = "halbjahr"
JAHRES = "jahres"
ABSCHLUSS = "abschluss"
ABGANG = "abgang"
UEBERGANG = "uebergang"
class CertificateStatus(str, Enum):
"""Status eines Zeugnisses."""
DRAFT = "draft"
REVIEW = "review"
APPROVED = "approved"
ISSUED = "issued"
ARCHIVED = "archived"
class GradeType(str, Enum):
"""Notentyp."""
NUMERIC = "numeric"
POINTS = "points"
TEXT = "text"
class BehaviorGrade(str, Enum):
"""Verhaltens-/Arbeitsnoten."""
A = "A"
B = "B"
C = "C"
D = "D"
# =============================================================================
# Pydantic Models
# =============================================================================
class SchoolInfoModel(BaseModel):
"""Schulinformationen fuer Zeugnis."""
name: str
address: str
phone: str
email: str
website: Optional[str] = None
principal: Optional[str] = None
logo_path: Optional[str] = None
class SubjectGrade(BaseModel):
"""Note fuer ein Fach."""
name: str = Field(..., description="Fachname")
grade: str = Field(..., description="Note (1-6 oder A-D)")
points: Optional[int] = Field(None, description="Punkte (Oberstufe, 0-15)")
note: Optional[str] = Field(None, description="Bemerkung zum Fach")
class AttendanceInfo(BaseModel):
"""Anwesenheitsinformationen."""
days_absent: int = Field(0, description="Fehlende Tage gesamt")
days_excused: int = Field(0, description="Entschuldigte Tage")
days_unexcused: int = Field(0, description="Unentschuldigte Tage")
hours_absent: Optional[int] = Field(None, description="Fehlstunden gesamt")
class CertificateCreateRequest(BaseModel):
"""Request zum Erstellen eines neuen Zeugnisses."""
student_id: str = Field(..., description="ID des Schuelers")
student_name: str = Field(..., description="Name des Schuelers")
student_birthdate: str = Field(..., description="Geburtsdatum")
student_class: str = Field(..., description="Klasse")
school_year: str = Field(..., description="Schuljahr (z.B. '2024/2025')")
certificate_type: CertificateType = Field(..., description="Art des Zeugnisses")
subjects: List[SubjectGrade] = Field(..., description="Fachnoten")
attendance: AttendanceInfo = Field(default_factory=AttendanceInfo)
remarks: Optional[str] = Field(None, description="Bemerkungen")
class_teacher: str = Field(..., description="Klassenlehrer/in")
principal: str = Field(..., description="Schulleiter/in")
school_info: Optional[SchoolInfoModel] = Field(None)
issue_date: Optional[str] = Field(None, description="Ausstellungsdatum")
social_behavior: Optional[BehaviorGrade] = Field(None)
work_behavior: Optional[BehaviorGrade] = Field(None)
class CertificateUpdateRequest(BaseModel):
"""Request zum Aktualisieren eines Zeugnisses."""
subjects: Optional[List[SubjectGrade]] = None
attendance: Optional[AttendanceInfo] = None
remarks: Optional[str] = None
class_teacher: Optional[str] = None
principal: Optional[str] = None
social_behavior: Optional[BehaviorGrade] = None
work_behavior: Optional[BehaviorGrade] = None
status: Optional[CertificateStatus] = None
class CertificateResponse(BaseModel):
"""Response mit Zeugnisdaten."""
id: str
student_id: str
student_name: str
student_birthdate: str
student_class: str
school_year: str
certificate_type: CertificateType
subjects: List[SubjectGrade]
attendance: AttendanceInfo
remarks: Optional[str]
class_teacher: str
principal: str
school_info: Optional[SchoolInfoModel]
issue_date: Optional[str]
social_behavior: Optional[BehaviorGrade]
work_behavior: Optional[BehaviorGrade]
status: CertificateStatus
average_grade: Optional[float]
pdf_path: Optional[str]
dsms_cid: Optional[str]
created_at: datetime
updated_at: datetime
class CertificateListResponse(BaseModel):
"""Response mit Liste von Zeugnissen."""
certificates: List[CertificateResponse]
total: int
page: int
page_size: int
class GradeStatistics(BaseModel):
"""Notenstatistiken fuer eine Klasse."""
class_name: str
school_year: str
certificate_type: CertificateType
student_count: int
average_grade: float
grade_distribution: Dict[str, int]
subject_averages: Dict[str, float]
# =============================================================================
# Helper Functions
# =============================================================================
def get_type_label(cert_type: CertificateType) -> str:
"""Gibt menschenlesbare Labels fuer Zeugnistypen zurueck."""
labels = {
CertificateType.HALBJAHR: "Halbjahreszeugnis",
CertificateType.JAHRES: "Jahreszeugnis",
CertificateType.ABSCHLUSS: "Abschlusszeugnis",
CertificateType.ABGANG: "Abgangszeugnis",
CertificateType.UEBERGANG: "Uebergangszeugnis",
}
return labels.get(cert_type, cert_type.value)
def calculate_average(subjects: List[Dict]) -> Optional[float]:
"""Berechnet Notendurchschnitt."""
numeric_grades = []
for subject in subjects:
grade = subject.get("grade", "")
try:
numeric = float(grade)
if 1 <= numeric <= 6:
numeric_grades.append(numeric)
except (ValueError, TypeError):
pass
if numeric_grades:
return round(sum(numeric_grades) / len(numeric_grades), 2)
return None