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>
185 lines
5.8 KiB
Python
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
|