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>
198 lines
6.2 KiB
Python
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
|
|
}
|