Files
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

198 lines
6.2 KiB
Python

"""
Klausur-Service Erwartungshorizont Models
Data classes for BYOEH (Bring-Your-Own-Expectation-Horizon).
"""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Dict, Any
from .enums import EHStatus
@dataclass
class Erwartungshorizont:
"""An encrypted Erwartungshorizont (expectation horizon)."""
id: str
tenant_id: str
teacher_id: str
title: str
subject: str
niveau: str # 'eA' or 'gA'
year: int
aufgaben_nummer: Optional[str]
encryption_key_hash: str
salt: str
encrypted_file_path: str
file_size_bytes: int
original_filename: str
rights_confirmed: bool
rights_confirmed_at: Optional[datetime]
status: EHStatus
chunk_count: int
indexed_at: Optional[datetime]
error_message: Optional[str]
training_allowed: bool # ALWAYS FALSE
created_at: datetime
deleted_at: Optional[datetime]
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
status_value = self.status.value if hasattr(self.status, 'value') else self.status
return {
'id': self.id,
'tenant_id': self.tenant_id,
'teacher_id': self.teacher_id,
'title': self.title,
'subject': self.subject,
'niveau': self.niveau,
'year': self.year,
'aufgaben_nummer': self.aufgaben_nummer,
'status': status_value,
'chunk_count': self.chunk_count,
'rights_confirmed': self.rights_confirmed,
'rights_confirmed_at': self.rights_confirmed_at.isoformat() if self.rights_confirmed_at else None,
'indexed_at': self.indexed_at.isoformat() if self.indexed_at else None,
'file_size_bytes': self.file_size_bytes,
'original_filename': self.original_filename,
'training_allowed': self.training_allowed,
'created_at': self.created_at.isoformat(),
'deleted_at': self.deleted_at.isoformat() if self.deleted_at else None
}
@dataclass
class EHRightsConfirmation:
"""Rights confirmation for an Erwartungshorizont upload."""
id: str
eh_id: str
teacher_id: str
confirmation_type: str # 'upload' | 'annual'
confirmation_text: str
ip_address: Optional[str]
user_agent: Optional[str]
confirmed_at: datetime
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'eh_id': self.eh_id,
'teacher_id': self.teacher_id,
'confirmation_type': self.confirmation_type,
'confirmation_text': self.confirmation_text,
'confirmed_at': self.confirmed_at.isoformat()
}
@dataclass
class EHAuditLogEntry:
"""Audit log entry for EH operations."""
id: str
eh_id: Optional[str]
tenant_id: str
user_id: str
action: str # upload, index, rag_query, download, delete
details: Optional[Dict]
ip_address: Optional[str]
user_agent: Optional[str]
created_at: datetime
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'eh_id': self.eh_id,
'tenant_id': self.tenant_id,
'user_id': self.user_id,
'action': self.action,
'details': self.details,
'created_at': self.created_at.isoformat()
}
@dataclass
class EHKeyShare:
"""Encrypted passphrase share for authorized users."""
id: str
eh_id: str
user_id: str
encrypted_passphrase: str # Passphrase encrypted with recipient's public key
passphrase_hint: str # Optional hint for the passphrase
granted_by: str
granted_at: datetime
role: str # 'second_examiner', 'third_examiner', 'supervisor'
klausur_id: Optional[str] # Link to specific Klausur if applicable
active: bool
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'eh_id': self.eh_id,
'user_id': self.user_id,
'passphrase_hint': self.passphrase_hint,
'granted_by': self.granted_by,
'granted_at': self.granted_at.isoformat(),
'role': self.role,
'klausur_id': self.klausur_id,
'active': self.active
}
@dataclass
class EHKlausurLink:
"""Link between an EH and a Klausur."""
id: str
eh_id: str
klausur_id: str
linked_by: str
linked_at: datetime
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'eh_id': self.eh_id,
'klausur_id': self.klausur_id,
'linked_by': self.linked_by,
'linked_at': self.linked_at.isoformat()
}
@dataclass
class EHShareInvitation:
"""Invitation to share an EH with another user."""
id: str
eh_id: str
inviter_id: str # User who sent the invitation
invitee_id: str # User receiving the invitation
invitee_email: str # Email for notification
role: str # Target role for the invitee
klausur_id: Optional[str] # Optional link to specific Klausur
message: Optional[str] # Optional message from inviter
status: str # 'pending', 'accepted', 'declined', 'expired', 'revoked'
expires_at: datetime # Invitation expiration
created_at: datetime
accepted_at: Optional[datetime]
declined_at: Optional[datetime]
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'eh_id': self.eh_id,
'inviter_id': self.inviter_id,
'invitee_id': self.invitee_id,
'invitee_email': self.invitee_email,
'role': self.role,
'klausur_id': self.klausur_id,
'message': self.message,
'status': self.status,
'expires_at': self.expires_at.isoformat(),
'created_at': self.created_at.isoformat(),
'accepted_at': self.accepted_at.isoformat() if self.accepted_at else None,
'declined_at': self.declined_at.isoformat() if self.declined_at else None
}