# 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)
