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>
This commit is contained in:
197
klausur-service/backend/models/eh.py
Normal file
197
klausur-service/backend/models/eh.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user