Files
breakpilot-lehrer/klausur-service/docs/BYOEH-Architecture.md
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

469 lines
23 KiB
Markdown

# BYOEH (Bring-Your-Own-Expectation-Horizon) - Architecture Documentation
## Overview
The BYOEH module enables teachers to upload their own Erwartungshorizonte (expectation horizons/grading rubrics) and use them for RAG-assisted grading suggestions. Key design principles:
- **Tenant Isolation**: Each teacher/school has an isolated namespace
- **No Training Guarantee**: EH content is only used for RAG, never for model training
- **Operator Blindness**: Client-side encryption ensures Breakpilot cannot view plaintext
- **Rights Confirmation**: Required legal acknowledgment at upload time
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────────────┐
│ klausur-service (Port 8086) │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌────────────────────┐ ┌─────────────────────────────────────────┐ │
│ │ BYOEH REST API │ │ BYOEH Service Layer │ │
│ │ │ │ │ │
│ │ POST /api/v1/eh │───▶│ - Upload Wizard Logic │ │
│ │ GET /api/v1/eh │ │ - Rights Confirmation │ │
│ │ DELETE /api/v1/eh │ │ - Chunking Pipeline │ │
│ │ POST /rag-query │ │ - Encryption Service │ │
│ └────────────────────┘ └────────────────────┬────────────────────┘ │
└─────────────────────────────────────────────────┼────────────────────────┘
┌───────────────────────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────┐
│ PostgreSQL │ │ Qdrant │ │ Encrypted Storage │
│ (Metadata + Audit) │ │ (Vector Search) │ │ /app/eh-uploads/ │
│ │ │ │ │ │
│ In-Memory Storage: │ │ Collection: bp_eh │ │ {tenant}/{eh_id}/ │
│ - erwartungshorizonte│ │ - tenant_id (filter) │ │ encrypted.bin │
│ - eh_chunks │ │ - eh_id │ │ salt.txt │
│ - eh_key_shares │ │ - embedding[1536] │ │ │
│ - eh_klausur_links │ │ - encrypted_content │ └──────────────────────┘
│ - eh_audit_log │ │ │
└──────────────────────┘ └──────────────────────────┘
```
## Data Flow
### 1. Upload Flow
```
Browser Backend Storage
│ │ │
│ 1. User selects PDF │ │
│ 2. User enters passphrase │ │
│ 3. PBKDF2 key derivation │ │
│ 4. AES-256-GCM encryption │ │
│ 5. SHA-256 key hash │ │
│ │ │
│──────────────────────────────▶│ │
│ POST /api/v1/eh/upload │ │
│ (encrypted blob + key_hash) │ │
│ │──────────────────────────────▶│
│ │ Store encrypted.bin + salt │
│ │◀──────────────────────────────│
│ │ │
│ │ Save metadata to DB │
│◀──────────────────────────────│ │
│ Return EH record │ │
```
### 2. Indexing Flow (RAG Preparation)
```
Browser Backend Qdrant
│ │ │
│──────────────────────────────▶│ │
│ POST /api/v1/eh/{id}/index │ │
│ (passphrase for decryption) │ │
│ │ │
│ │ 1. Verify key hash │
│ │ 2. Decrypt content │
│ │ 3. Extract text (PDF) │
│ │ 4. Chunk text │
│ │ 5. Generate embeddings │
│ │ 6. Re-encrypt each chunk │
│ │──────────────────────────────▶│
│ │ Index vectors + encrypted │
│ │ chunks with tenant filter │
│◀──────────────────────────────│ │
│ Return chunk count │ │
```
### 3. RAG Query Flow
```
Browser Backend Qdrant
│ │ │
│──────────────────────────────▶│ │
│ POST /api/v1/eh/rag-query │ │
│ (query + passphrase) │ │
│ │ │
│ │ 1. Generate query embedding │
│ │──────────────────────────────▶│
│ │ 2. Semantic search │
│ │ (tenant-filtered) │
│ │◀──────────────────────────────│
│ │ 3. Decrypt matched chunks │
│◀──────────────────────────────│ │
│ Return decrypted context │ │
```
## Security Architecture
### Client-Side Encryption
```
┌─────────────────────────────────────────────────────────────────┐
│ Browser (Client-Side) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. User enters passphrase (NEVER sent to server) │
│ │ │
│ ▼ │
│ 2. Key Derivation: PBKDF2-SHA256(passphrase, salt, 100k iter) │
│ │ │
│ ▼ │
│ 3. Encryption: AES-256-GCM(key, iv, file_content) │
│ │ │
│ ▼ │
│ 4. Key-Hash: SHA-256(derived_key) → server verification only │
│ │ │
│ ▼ │
│ 5. Upload: encrypted_blob + key_hash + salt (NOT key!) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Security Guarantees
| Guarantee | Implementation |
|-----------|----------------|
| **No Training** | `training_allowed: false` on all Qdrant points |
| **Operator Blindness** | Passphrase never leaves browser; server only sees key hash |
| **Tenant Isolation** | Every query filtered by `tenant_id` |
| **Audit Trail** | All actions logged with timestamps |
## Key Sharing System
The key sharing system enables first examiners to grant access to their EH to second examiners and supervisors.
### Share Flow
```
First Examiner Backend Second Examiner
│ │ │
│ 1. Encrypt passphrase for │ │
│ recipient (client-side) │ │
│ │ │
│─────────────────────────────▶ │
│ POST /eh/{id}/share │ │
│ (encrypted_passphrase, role)│ │
│ │ │
│ │ Store EHKeyShare │
│◀───────────────────────────── │
│ │ │
│ │ │
│ │◀────────────────────────────│
│ │ GET /eh/shared-with-me │
│ │ │
│ │─────────────────────────────▶
│ │ Return shared EH list │
│ │ │
│ │◀────────────────────────────│
│ │ RAG query with decrypted │
│ │ passphrase │
```
### Data Structures
```python
@dataclass
class EHKeyShare:
id: str
eh_id: str
user_id: str # Recipient
encrypted_passphrase: str # Client-encrypted for recipient
passphrase_hint: str # Optional hint
granted_by: str # Grantor user ID
granted_at: datetime
role: str # second_examiner, third_examiner, supervisor
klausur_id: Optional[str] # Link to specific Klausur
active: bool
@dataclass
class EHKlausurLink:
id: str
eh_id: str
klausur_id: str
linked_by: str
linked_at: datetime
```
## API Endpoints
### Core EH Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/eh/upload` | Upload encrypted EH |
| GET | `/api/v1/eh` | List user's EH |
| GET | `/api/v1/eh/{id}` | Get single EH |
| DELETE | `/api/v1/eh/{id}` | Soft delete EH |
| POST | `/api/v1/eh/{id}/index` | Index EH for RAG |
| POST | `/api/v1/eh/rag-query` | Query EH content |
### Key Sharing Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/eh/{id}/share` | Share EH with examiner |
| GET | `/api/v1/eh/{id}/shares` | List shares (owner) |
| DELETE | `/api/v1/eh/{id}/shares/{shareId}` | Revoke share |
| GET | `/api/v1/eh/shared-with-me` | List EH shared with user |
### Klausur Integration Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/eh/{id}/link-klausur` | Link EH to Klausur |
| DELETE | `/api/v1/eh/{id}/link-klausur/{klausurId}` | Unlink EH |
| GET | `/api/v1/klausuren/{id}/linked-eh` | Get linked EH for Klausur |
### Audit & Admin Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/eh/audit-log` | Get audit log |
| GET | `/api/v1/eh/rights-text` | Get rights confirmation text |
| GET | `/api/v1/eh/qdrant-status` | Get Qdrant status (admin) |
## Frontend Components
### EHUploadWizard
5-step wizard for uploading Erwartungshorizonte:
1. **File Selection** - Choose PDF file
2. **Metadata** - Title, Subject, Niveau, Year
3. **Rights Confirmation** - Legal acknowledgment
4. **Encryption** - Set passphrase (2x confirmation)
5. **Summary** - Review and upload
### Integration Points
- **KorrekturPage**: Shows EH prompt after first student upload
- **GutachtenGeneration**: Uses RAG context from linked EH
- **Sidebar Badge**: Shows linked EH count
## File Structure
```
klausur-service/
├── backend/
│ ├── main.py # API endpoints + data structures
│ ├── qdrant_service.py # Vector database operations
│ ├── eh_pipeline.py # Chunking, embedding, encryption
│ └── requirements.txt # Python dependencies
├── frontend/
│ └── src/
│ ├── components/
│ │ └── EHUploadWizard.tsx
│ ├── services/
│ │ ├── api.ts # API client
│ │ └── encryption.ts # Client-side crypto
│ ├── pages/
│ │ └── KorrekturPage.tsx # EH integration
│ └── styles/
│ └── eh-wizard.css
└── docs/
├── BYOEH-Architecture.md
└── BYOEH-Developer-Guide.md
```
## Configuration
### Environment Variables
```env
QDRANT_URL=http://qdrant:6333
OPENAI_API_KEY=sk-... # For embeddings
BYOEH_ENCRYPTION_ENABLED=true
EH_UPLOAD_DIR=/app/eh-uploads
```
### Docker Services
```yaml
# docker-compose.yml
services:
qdrant:
image: qdrant/qdrant:v1.7.4
ports:
- "6333:6333"
volumes:
- qdrant_data:/qdrant/storage
```
## Audit Events
| Action | Description |
|--------|-------------|
| `upload` | EH uploaded |
| `index` | EH indexed for RAG |
| `rag_query` | RAG query executed |
| `delete` | EH soft deleted |
| `share` | EH shared with examiner |
| `revoke_share` | Share revoked |
| `link_klausur` | EH linked to Klausur |
| `unlink_klausur` | EH unlinked from Klausur |
---
## RBAC Extensions for Zeugnis System
The RBAC system has been extended to support the Zeugnis (Certificate) workflow. This enables role-based access control for certificate generation, approval, and management.
### Certificate-Related Roles
```python
class Role(str, Enum):
# Existing exam roles
ERSTPRUEFER = "erstpruefer" # First examiner
ZWEITPRUEFER = "zweitpruefer" # Second examiner
DRITTPRUEFER = "drittpruefer" # Third examiner
FACHVORSITZ = "fachvorsitz" # Subject chair
# Certificate workflow roles
FACHLEHRER = "fachlehrer" # Subject teacher - enters grades
KLASSENLEHRER = "klassenlehrer" # Class teacher - approves grades
ZEUGNISBEAUFTRAGTER = "zeugnisbeauftragter" # Certificate coordinator
SCHULLEITUNG = "schulleitung" # Principal - final sign-off
SEKRETARIAT = "sekretariat" # Secretary - printing
```
### Certificate Resource Types
```python
class ResourceType(str, Enum):
# Existing types
KLAUSUR = "klausur"
ERWARTUNGSHORIZONT = "erwartungshorizont"
# Certificate types
ZEUGNIS = "zeugnis" # Final certificate
ZEUGNIS_VORLAGE = "zeugnis_vorlage" # Certificate template
ZEUGNIS_ENTWURF = "zeugnis_entwurf" # Draft certificate
FACHNOTE = "fachnote" # Subject grade
KOPFNOTE = "kopfnote" # Head grade (Arbeits-/Sozialverhalten)
BEMERKUNG = "bemerkung" # Certificate remarks
STATISTIK = "statistik" # Class/subject statistics
NOTENSPIEGEL = "notenspiegel" # Grade distribution
```
### VerfahrenType Extension
```python
class VerfahrenType(str, Enum):
# Exam types
ABITUR = "abitur"
KLAUSUR = "klausur"
NACHSCHREIBKLAUSUR = "nachschreibklausur"
# Certificate types (NEW)
HALBJAHRESZEUGNIS = "halbjahreszeugnis" # Mid-year certificate
JAHRESZEUGNIS = "jahreszeugnis" # End-of-year certificate
ABSCHLUSSZEUGNIS = "abschlusszeugnis" # Graduation certificate
ABGANGSZEUGNIS = "abgangszeugnis" # Leaving certificate
@classmethod
def is_certificate_type(cls, verfahren: "VerfahrenType") -> bool:
"""Check if this is a certificate type (not an exam)."""
cert_types = {
cls.HALBJAHRESZEUGNIS,
cls.JAHRESZEUGNIS,
cls.ABSCHLUSSZEUGNIS,
cls.ABGANGSZEUGNIS
}
return verfahren in cert_types
```
### Certificate Workflow Permissions
```
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────┐
│ FACHLEHRER │───▶│ KLASSENLEHRER │───▶│ ZEUGNISBEAUFTRAGTER │
│ │ │ │ │ │
│ FACHNOTE: CRUD │ │ ZEUGNIS: CRU │ │ ZEUGNIS: RU │
│ ZEUGNIS_ENTWURF:R │ │ ZEUGNIS_ENTWURF: │ │ ZEUGNIS_VORLAGE: RUU │
│ │ │ CRUD │ │ │
└───────────────────┘ └───────────────────┘ └───────────────────────┘
┌───────────────────┐ ┌───────────────────────┐
│ SEKRETARIAT │◀───│ SCHULLEITUNG │
│ │ │ │
│ ZEUGNIS: RD │ │ ZEUGNIS: R/SIGN/LOCK │
│ (Print & Archive) │ │ (Final Approval) │
└───────────────────┘ └───────────────────────┘
```
### DEFAULT_PERMISSIONS for Certificate Roles
```python
DEFAULT_PERMISSIONS = {
# ... existing roles ...
Role.KLASSENLEHRER: {
ResourceType.KLASSE: {Action.READ, Action.UPDATE},
ResourceType.SCHUELER: {Action.READ, Action.CREATE, Action.UPDATE},
ResourceType.FACHNOTE: {Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE},
ResourceType.ZEUGNIS: {Action.CREATE, Action.READ, Action.UPDATE},
ResourceType.ZEUGNIS_ENTWURF: {Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE},
ResourceType.ZEUGNIS_VORLAGE: {Action.READ},
ResourceType.KOPFNOTE: {Action.CREATE, Action.READ, Action.UPDATE},
ResourceType.BEMERKUNG: {Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE},
ResourceType.STATISTIK: {Action.READ},
ResourceType.NOTENSPIEGEL: {Action.READ},
},
Role.ZEUGNISBEAUFTRAGTER: {
ResourceType.KLASSE: {Action.READ},
ResourceType.ZEUGNIS: {Action.READ, Action.UPDATE},
ResourceType.ZEUGNIS_ENTWURF: {Action.READ, Action.UPDATE},
ResourceType.ZEUGNIS_VORLAGE: {Action.READ, Action.UPDATE, Action.UPLOAD},
ResourceType.STATISTIK: {Action.READ},
ResourceType.NOTENSPIEGEL: {Action.READ},
},
Role.SEKRETARIAT: {
ResourceType.ZEUGNIS: {Action.READ, Action.DOWNLOAD},
ResourceType.ZEUGNIS_VORLAGE: {Action.READ},
},
Role.SCHULLEITUNG: {
ResourceType.ZEUGNIS: {Action.READ, Action.SIGN_OFF, Action.LOCK},
ResourceType.ZEUGNIS_ENTWURF: {Action.READ, Action.UPDATE},
ResourceType.ZEUGNIS_VORLAGE: {Action.READ, Action.UPDATE},
ResourceType.STATISTIK: {Action.READ},
ResourceType.NOTENSPIEGEL: {Action.READ},
},
Role.FACHLEHRER: {
ResourceType.FACHNOTE: {Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE},
ResourceType.ZEUGNIS: {Action.READ, Action.UPDATE},
ResourceType.ZEUGNIS_ENTWURF: {Action.READ, Action.UPDATE},
ResourceType.STATISTIK: {Action.READ},
ResourceType.NOTENSPIEGEL: {Action.READ},
},
}
```
### See Also
For complete Zeugnis system documentation including:
- Full workflow diagrams
- Statistics API endpoints
- Frontend components
- Seed data generator
See: [docs/architecture/zeugnis-system.md](../../docs/architecture/zeugnis-system.md)