This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/klausur-service/backend/models/eh.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +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
}