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:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit 21a844cb8a
1986 changed files with 744143 additions and 1731 deletions

View File

@@ -0,0 +1,468 @@
# 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)

View File

@@ -0,0 +1,481 @@
# BYOEH Developer Guide
## Quick Start
### Prerequisites
- Python 3.10+
- Node.js 18+
- Docker & Docker Compose
- OpenAI API Key (for embeddings)
### Setup
1. **Start services:**
```bash
docker-compose up -d qdrant
```
2. **Configure environment:**
```env
QDRANT_URL=http://localhost:6333
OPENAI_API_KEY=sk-your-key
BYOEH_ENCRYPTION_ENABLED=true
```
3. **Run klausur-service:**
```bash
cd klausur-service/backend
pip install -r requirements.txt
uvicorn main:app --reload --port 8086
```
4. **Run frontend:**
```bash
cd klausur-service/frontend
npm install
npm run dev
```
## Client-Side Encryption
The encryption service (`encryption.ts`) handles all cryptographic operations in the browser:
### Encrypting a File
```typescript
import { encryptFile, generateSalt } from '../services/encryption'
const file = document.getElementById('fileInput').files[0]
const passphrase = 'user-secret-password'
const encrypted = await encryptFile(file, passphrase)
// Result:
// {
// encryptedData: ArrayBuffer,
// keyHash: string, // SHA-256 hash for verification
// salt: string, // Hex-encoded salt
// iv: string // Hex-encoded initialization vector
// }
```
### Decrypting Content
```typescript
import { decryptText, verifyPassphrase } from '../services/encryption'
// First verify the passphrase
const isValid = await verifyPassphrase(passphrase, salt, expectedKeyHash)
if (isValid) {
const decrypted = await decryptText(encryptedBase64, passphrase, salt)
}
```
## Backend API Usage
### Upload an Erwartungshorizont
```python
# The upload endpoint accepts FormData with:
# - file: encrypted binary blob
# - metadata_json: JSON string with metadata
POST /api/v1/eh/upload
Content-Type: multipart/form-data
{
"file": <encrypted_blob>,
"metadata_json": {
"metadata": {
"title": "Deutsch LK 2025",
"subject": "deutsch",
"niveau": "eA",
"year": 2025,
"aufgaben_nummer": "Aufgabe 1"
},
"encryption_key_hash": "abc123...",
"salt": "def456...",
"rights_confirmed": true,
"original_filename": "erwartungshorizont.pdf"
}
}
```
### Index for RAG
```python
POST /api/v1/eh/{eh_id}/index
Content-Type: application/json
{
"passphrase": "user-secret-password"
}
```
The backend will:
1. Verify the passphrase against stored key hash
2. Decrypt the file
3. Extract text from PDF
4. Chunk the text (1000 chars, 200 overlap)
5. Generate OpenAI embeddings
6. Re-encrypt each chunk
7. Index in Qdrant with tenant filter
### RAG Query
```python
POST /api/v1/eh/rag-query
Content-Type: application/json
{
"query_text": "Wie sollte die Einleitung strukturiert sein?",
"passphrase": "user-secret-password",
"subject": "deutsch", # Optional filter
"limit": 5 # Max results
}
```
Response:
```json
{
"context": "Die Einleitung sollte...",
"sources": [
{
"text": "Die Einleitung sollte...",
"eh_id": "uuid",
"eh_title": "Deutsch LK 2025",
"chunk_index": 2,
"score": 0.89
}
],
"query": "Wie sollte die Einleitung strukturiert sein?"
}
```
## Key Sharing Implementation
### Invitation Flow (Recommended)
The invitation flow provides a two-phase sharing process: Invite -> Accept
```typescript
import { ehApi } from '../services/api'
// 1. First examiner sends invitation to second examiner
const invitation = await ehApi.inviteToEH(ehId, {
invitee_email: 'zweitkorrektor@school.de',
role: 'second_examiner',
klausur_id: 'klausur-uuid', // Optional: link to specific Klausur
message: 'Bitte fuer Zweitkorrektur nutzen',
expires_in_days: 14 // Default: 14 days
})
// Returns: { invitation_id, eh_id, invitee_email, role, expires_at, eh_title }
// 2. Second examiner sees pending invitation
const pending = await ehApi.getPendingInvitations()
// [{ invitation: {...}, eh: { id, title, subject, niveau, year } }]
// 3. Second examiner accepts invitation
const accepted = await ehApi.acceptInvitation(
invitationId,
encryptedPassphrase // Passphrase encrypted for recipient
)
// Returns: { status: 'accepted', share_id, eh_id, role, klausur_id }
```
### Invitation Management
```typescript
// Get invitations sent by current user
const sent = await ehApi.getSentInvitations()
// Decline an invitation (as invitee)
await ehApi.declineInvitation(invitationId)
// Revoke a pending invitation (as inviter)
await ehApi.revokeInvitation(invitationId)
// Get complete access chain for an EH
const chain = await ehApi.getAccessChain(ehId)
// Returns: { eh_id, eh_title, owner, active_shares, pending_invitations, revoked_shares }
```
### Direct Sharing (Legacy)
For immediate sharing without invitation:
```typescript
// First examiner shares directly with second examiner
await ehApi.shareEH(ehId, {
user_id: 'second-examiner-uuid',
role: 'second_examiner',
encrypted_passphrase: encryptedPassphrase, // Encrypted for recipient
passphrase_hint: 'Das uebliche Passwort',
klausur_id: 'klausur-uuid' // Optional
})
```
### Accessing Shared EH
```typescript
// Second examiner gets shared EH
const shared = await ehApi.getSharedWithMe()
// [{ eh: {...}, share: {...} }]
// Query using provided passphrase
const result = await ehApi.ragQuery({
query_text: 'search query',
passphrase: decryptedPassphrase,
subject: 'deutsch'
})
```
### Revoking Access
```typescript
// List all shares for an EH
const shares = await ehApi.listShares(ehId)
// Revoke a share
await ehApi.revokeShare(ehId, shareId)
```
## Klausur Integration
### Automatic EH Prompt
The `KorrekturPage` shows an EH upload prompt after the first student work is uploaded:
```typescript
// In KorrekturPage.tsx
useEffect(() => {
if (
currentKlausur?.students.length === 1 &&
linkedEHs.length === 0 &&
!ehPromptDismissed
) {
setShowEHPrompt(true)
}
}, [currentKlausur?.students.length])
```
### Linking EH to Klausur
```typescript
// After EH upload, auto-link to Klausur
await ehApi.linkToKlausur(ehId, klausurId)
// Get linked EH for a Klausur
const linked = await klausurEHApi.getLinkedEH(klausurId)
```
## Frontend Components
### EHUploadWizard Props
```typescript
interface EHUploadWizardProps {
onClose: () => void
onComplete?: (ehId: string) => void
defaultSubject?: string // Pre-fill subject
defaultYear?: number // Pre-fill year
klausurId?: string // Auto-link after upload
}
// Usage
<EHUploadWizard
onClose={() => setShowWizard(false)}
onComplete={(ehId) => console.log('Uploaded:', ehId)}
defaultSubject={klausur.subject}
defaultYear={klausur.year}
klausurId={klausur.id}
/>
```
### Wizard Steps
1. **file** - PDF file selection with drag & drop
2. **metadata** - Form for title, subject, niveau, year
3. **rights** - Rights confirmation checkbox
4. **encryption** - Passphrase input with strength meter
5. **summary** - Review and confirm upload
## Qdrant Operations
### Collection Schema
```python
# Collection: bp_eh
{
"vectors": {
"size": 1536, # OpenAI text-embedding-3-small
"distance": "Cosine"
}
}
# Point payload
{
"tenant_id": "school-uuid",
"eh_id": "eh-uuid",
"chunk_index": 0,
"encrypted_content": "base64...",
"training_allowed": false # ALWAYS false
}
```
### Tenant-Isolated Search
```python
from qdrant_service import search_eh
results = await search_eh(
query_embedding=embedding,
tenant_id="school-uuid",
subject="deutsch",
limit=5
)
```
## Testing
### Unit Tests
```bash
cd klausur-service/backend
pytest tests/test_byoeh.py -v
```
### Test Structure
```python
# tests/test_byoeh.py
class TestBYOEH:
def test_upload_eh(self, client, auth_headers):
"""Test EH upload with encryption"""
pass
def test_index_eh(self, client, auth_headers, uploaded_eh):
"""Test EH indexing for RAG"""
pass
def test_rag_query(self, client, auth_headers, indexed_eh):
"""Test RAG query returns relevant chunks"""
pass
def test_share_eh(self, client, auth_headers, uploaded_eh):
"""Test sharing EH with another user"""
pass
```
### Frontend Tests
```typescript
// EHUploadWizard.test.tsx
describe('EHUploadWizard', () => {
it('completes all steps successfully', async () => {
// ...
})
it('validates passphrase strength', async () => {
// ...
})
it('auto-links to klausur when klausurId provided', async () => {
// ...
})
})
```
## Error Handling
### Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| `Passphrase verification failed` | Wrong passphrase | Ask user to re-enter |
| `EH not found` | Invalid ID or deleted | Check ID, reload list |
| `Access denied` | User not owner/shared | Check permissions |
| `Qdrant connection failed` | Service unavailable | Check Qdrant container |
### Error Response Format
```json
{
"detail": "Passphrase verification failed"
}
```
## Security Considerations
### Do's
- Store key hash, never the key itself
- Always filter by tenant_id
- Log all access in audit trail
- Use HTTPS in production
### Don'ts
- Never log passphrase or decrypted content
- Never store passphrase in localStorage
- Never send passphrase as URL parameter
- Never return decrypted content without auth
## Performance Tips
### Chunking Configuration
```python
CHUNK_SIZE = 1000 # Characters per chunk
CHUNK_OVERLAP = 200 # Overlap for context continuity
```
### Embedding Batching
```python
# Generate embeddings in batches of 20
EMBEDDING_BATCH_SIZE = 20
```
### Qdrant Optimization
```python
# Use HNSW index for fast approximate search
# Collection is automatically optimized on creation
```
## Debugging
### Enable Debug Logging
```python
import logging
logging.getLogger('byoeh').setLevel(logging.DEBUG)
```
### Check Qdrant Status
```bash
curl http://localhost:6333/collections/bp_eh
```
### Verify Encryption
```typescript
import { isEncryptionSupported } from '../services/encryption'
if (!isEncryptionSupported()) {
console.error('Web Crypto API not available')
}
```
## Migration Notes
### From v1.0 to v1.1
1. Added key sharing system
2. Added Klausur linking
3. EH prompt after student upload
No database migrations required - all data structures are additive.

View File

@@ -0,0 +1,788 @@
# DSGVO-Audit-Dokumentation: OCR-Labeling-System für Handschrifterkennung
**Dokumentversion:** 1.0.0
**Datum:** 21. Januar 2026
**Klassifizierung:** Vertraulich - Nur für internen Gebrauch und Auditoren
**Nächste Überprüfung:** 21. Januar 2027
---
## 1. Management Summary
### 1.1 Systemübersicht
Das OCR-Labeling-System ist eine **vollständig lokal betriebene** Lösung zur Digitalisierung und Auswertung handschriftlicher Schülerarbeiten (Klausuren, Aufsätze). Das System nutzt:
- **llama3.2-vision:11b** - Open-Source Vision-Language-Modell für OCR (lokal via Ollama)
- **TrOCR** - Microsoft Transformer OCR für Handschrifterkennung (lokal)
- **qwen2.5:14b** - Open-Source LLM für Korrekturassistenz (lokal via Ollama)
### 1.2 Datenschutz-Garantien
| Merkmal | Umsetzung |
|---------|-----------|
| **Verarbeitungsort** | 100% lokal auf schuleigenem Mac Mini |
| **Cloud-Dienste** | Keine - vollständig offline-fähig |
| **Datenübertragung** | Keine Übertragung an externe Server |
| **KI-Modelle** | Open-Source, lokal ausgeführt, kein Telemetrie |
| **Speicherung** | Lokale PostgreSQL-Datenbank, MinIO Object Storage |
### 1.3 Compliance-Status
Das System erfüllt die Anforderungen der:
- DSGVO (Verordnung (EU) 2016/679)
- BDSG (Bundesdatenschutzgesetz)
- Niedersächsisches Schulgesetz (NSchG) §31
- EU AI Act (Verordnung (EU) 2024/1689)
---
## 2. Verzeichnis der Verarbeitungstätigkeiten (Art. 30 DSGVO)
### 2.1 Verantwortlicher
| Feld | Inhalt |
|------|--------|
| **Verantwortlicher** | [Schulname], [Schuladresse] |
| **Vertreter** | Schulleitung: [Name] |
| **Kontakt** | [E-Mail], [Telefon] |
### 2.2 Datenschutzbeauftragter
| Feld | Inhalt |
|------|--------|
| **Name** | [Name DSB] |
| **Organisation** | [Behördlicher/Externer DSB] |
| **Kontakt** | [E-Mail], [Telefon] |
### 2.3 Verarbeitungstätigkeiten
#### 2.3.1 OCR-Verarbeitung von Klausuren
| Attribut | Beschreibung |
|----------|--------------|
| **Zweck** | Digitalisierung handschriftlicher Prüfungsantworten mittels KI-gestützter Texterkennung zur Unterstützung der Lehrkräfte bei der Korrektur |
| **Rechtsgrundlage** | Art. 6 Abs. 1 lit. e DSGVO i.V.m. §31 NSchG (öffentliche Aufgabe der Leistungsbewertung) |
| **Betroffene Personen** | Schülerinnen und Schüler (Prüfungsarbeiten) |
| **Datenkategorien** | Handschriftproben, Prüfungsantworten, Schülerkennung (optional) |
| **Empfänger** | Ausschließlich berechtigte Lehrkräfte der Schule |
| **Drittlandübermittlung** | Keine |
| **Löschfrist** | Gem. Aufbewahrungspflichten für Prüfungsunterlagen (i.d.R. 2-10 Jahre je nach Bundesland) |
#### 2.3.2 Labeling für Modell-Training
| Attribut | Beschreibung |
|----------|--------------|
| **Zweck** | Erstellung von Trainingsdaten für lokales Fine-Tuning der OCR-Modelle zur Verbesserung der Handschrifterkennung |
| **Rechtsgrundlage** | Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) oder Art. 6 Abs. 1 lit. a DSGVO (Einwilligung) |
| **Betroffene Personen** | Schülerinnen und Schüler (anonymisierte Handschriftproben) |
| **Datenkategorien** | Anonymisierte/pseudonymisierte Handschriftbilder, korrigierter Text |
| **Empfänger** | Lokales ML-System, keine externen Empfänger |
| **Drittlandübermittlung** | Keine |
| **Löschfrist** | Trainingsdaten: Nach Abschluss des Trainings oder auf Widerruf |
### 2.4 Verweis auf TOM
Siehe Abschnitt 8: Technisch-Organisatorische Maßnahmen
---
## 3. Rechtsgrundlagen (Art. 6 DSGVO)
### 3.1 Primäre Rechtsgrundlagen
| Verarbeitungsschritt | Rechtsgrundlage | Begründung |
|---------------------|-----------------|------------|
| Scan von Klausuren | Art. 6 Abs. 1 lit. e DSGVO | Öffentliche Aufgabe der schulischen Leistungsbewertung |
| OCR-Verarbeitung | Art. 6 Abs. 1 lit. e DSGVO | Teil der Bewertungsaufgabe, Effizienzsteigerung |
| Lehrerkorrektur | Art. 6 Abs. 1 lit. e DSGVO | Kernaufgabe der Leistungsbewertung |
| Export für Training | Art. 6 Abs. 1 lit. f DSGVO | Berechtigtes Interesse an Modellverbesserung |
### 3.2 Landesrechtliche Grundlagen
**Niedersachsen:**
- §31 NSchG: Erhebung, Verarbeitung und Nutzung personenbezogener Daten
- Ergänzende Bestimmungen zur VO-DV I
**Interesse-Abwägung für Training (Art. 6 Abs. 1 lit. f):**
| Aspekt | Bewertung |
|--------|-----------|
| **Interesse des Verantwortlichen** | Verbesserung der OCR-Qualität für effizientere Klausurkorrektur |
| **Erwartung der Betroffenen** | Schüler erwarten, dass Prüfungsarbeiten für schulische Zwecke verarbeitet werden |
| **Auswirkung auf Betroffene** | Minimal - Daten werden pseudonymisiert, rein lokale Verarbeitung |
| **Schutzmaßnahmen** | Pseudonymisierung, keine Weitergabe, lokale Verarbeitung |
| **Ergebnis** | Berechtigtes Interesse überwiegt |
### 3.3 Besondere Kategorien (Art. 9 DSGVO)
**Prüfung auf besondere Kategorien:**
Handschriftproben könnten theoretisch Rückschlüsse auf Gesundheitszustände ermöglichen (z.B. Tremor). Dies wird wie folgt adressiert:
- OCR-Modelle analysieren ausschließlich Textinhalt, nicht Handschriftcharakteristiken
- Keine Speicherung von Handschriftanalysen
- Bei Training werden nur Textinhalte verwendet, keine biometrischen Merkmale
**Ergebnis:** Art. 9 ist nicht anwendbar, da keine Verarbeitung besonderer Kategorien erfolgt.
---
## 4. Datenschutz-Folgenabschätzung (Art. 35 DSGVO)
### 4.1 Schwellwertanalyse - Erforderlichkeit der DSFA
| Kriterium | Erfüllt | Begründung |
|-----------|---------|------------|
| Neue Technologien (KI/ML) | ✓ | Vision-LLM für OCR |
| Umfangreiche Verarbeitung | ✗ | Begrenzt auf einzelne Schule |
| Daten von Minderjährigen | ✓ | Schülerarbeiten |
| Systematische Überwachung | ✗ | Keine Überwachung |
| Scoring/Profiling | ✗ | Keine automatische Bewertung |
**Ergebnis:** DSFA erforderlich aufgrund KI-Einsatz und Verarbeitung von Daten Minderjähriger.
### 4.2 Systematische Beschreibung der Verarbeitung
#### Datenfluss-Diagramm
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ OCR-LABELING DATENFLUSS │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 1. SCAN │───►│ 2. UPLOAD │───►│ 3. OCR │───►│ 4. LABELING │ │
│ │ (Lehrkraft) │ │ (MinIO) │ │ (Ollama) │ │ (Lehrkraft) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Papierdokument Verschlüsselte Lokale LLM- Bestätigung/ │
│ → digitaler Scan Bildspeicherung Verarbeitung Korrektur │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ SPEICHERUNG (PostgreSQL) │ │
│ │ • Session-ID (UUID) • Status (pending/confirmed/corrected) │ │
│ │ • Bild-Hash (SHA256) • Ground Truth (korrigierter Text) │ │
│ │ • OCR-Text • Zeitstempel │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 5. EXPORT │ Pseudonymisierte Trainingsdaten (JSONL) │
│ │ (Optional) │ → Lokal gespeichert für Fine-Tuning │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
#### Verarbeitungsschritte im Detail
| Schritt | Beschreibung | Datenschutzmaßnahme |
|---------|--------------|---------------------|
| 1. Scan | Lehrkraft scannt Papierklausur | Physischer Zugang nur für Lehrkräfte |
| 2. Upload | Bild wird in lokales MinIO hochgeladen | SHA256-Deduplizierung, verschlüsselte Speicherung |
| 3. OCR | llama3.2-vision erkennt Text | 100% lokal, kein Internet |
| 4. Labeling | Lehrkraft prüft/korrigiert OCR-Ergebnis | Protokollierung aller Aktionen |
| 5. Export | Optional: Pseudonymisierte Trainingsdaten | Entfernung direkter Identifikatoren |
### 4.3 Notwendigkeit und Verhältnismäßigkeit
#### Prüfung der Erforderlichkeit
| Prinzip | Umsetzung |
|---------|-----------|
| **Zweckbindung** | Ausschließlich für schulische Leistungsbewertung und Modelltraining |
| **Datenminimierung** | Nur Bildausschnitte mit Text, keine vollständigen Klausuren nötig |
| **Speicherbegrenzung** | Automatische Löschung nach definierter Aufbewahrungsfrist |
#### Alternativenprüfung
| Alternative | Bewertung |
|-------------|-----------|
| Manuelle Transkription | Zeitaufwändig, fehleranfällig, nicht praktikabel |
| Cloud-OCR (Google, Azure) | Datenschutzrisiken durch Drittlandübermittlung |
| Kommerzielles lokales OCR | Hohe Kosten, Lizenzabhängigkeit |
| **Gewählte Lösung** | Open-Source lokal - optimale Balance |
### 4.4 Risikobewertung
#### Identifizierte Risiken
| Risiko | Eintrittswahrscheinlichkeit | Schwere | Risikostufe | Mitigationsmaßnahme |
|--------|---------------------------|---------|-------------|---------------------|
| R1: Unbefugter Zugriff auf Schülerdaten | Gering | Hoch | Mittel | Rollenbasierte Zugriffskontrolle, MFA |
| R2: Datenleck durch Systemkompromittierung | Gering | Hoch | Mittel | Verschlüsselung, Netzwerkisolation |
| R3: Fehlerhaftes OCR beeinflusst Bewertung | Mittel | Mittel | Mittel | Pflicht-Review durch Lehrkraft |
| R4: Re-Identifizierung aus Handschrift | Gering | Mittel | Gering | Pseudonymisierung, keine Handschriftanalyse |
| R5: Bias im OCR-Modell | Mittel | Mittel | Mittel | Regelmäßige Qualitätsprüfung |
#### Risikomatrix
```
SCHWERE
Gering Mittel Hoch
┌───────┬───────┬───────┐
Hoch │ │ │ │
├───────┼───────┼───────┤
Mittel │ │ R3,R5 │ │ WAHRSCHEINLICHKEIT
├───────┼───────┼───────┤
Gering │ │ R4 │ R1,R2 │
└───────┴───────┴───────┘
```
### 4.5 Maßnahmen zur Risikominderung
| Risiko | Maßnahme | Umsetzungsstatus |
|--------|----------|------------------|
| R1 | RBAC, MFA, Audit-Logging | ✓ Implementiert |
| R2 | FileVault-Verschlüsselung, lokales Netz | ✓ Implementiert |
| R3 | Pflicht-Bestätigung durch Lehrkraft | ✓ Implementiert |
| R4 | Pseudonymisierung bei Export | ✓ Implementiert |
| R5 | Diverse Trainingssamples, manuelle Reviews | ○ In Entwicklung |
---
## 5. Informationspflichten (Art. 13/14 DSGVO)
### 5.1 Informationen für Betroffene
Folgende Informationen werden Schülern und Erziehungsberechtigten bereitgestellt:
#### 5.1.1 Pflichtangaben nach Art. 13 DSGVO
| Information | Bereitstellung |
|-------------|----------------|
| Identität des Verantwortlichen | Schulwebsite, Datenschutzerklärung |
| Kontakt DSB | Schulwebsite, Aushang |
| Verarbeitungszwecke | Datenschutzinformation bei Einschulung |
| Rechtsgrundlage | Datenschutzinformation |
| Empfänger/Kategorien | Datenschutzinformation |
| Speicherdauer | Datenschutzinformation |
| Betroffenenrechte | Datenschutzinformation, auf Anfrage |
| Beschwerderecht | Datenschutzinformation |
#### 5.1.2 KI-spezifische Transparenz
Zusätzlich zu den Standard-Informationspflichten:
| Information | Inhalt |
|-------------|--------|
| Art der KI | Vision-LLM für Texterkennung, kein automatisches Bewerten |
| Menschliche Aufsicht | Jedes OCR-Ergebnis wird von Lehrkraft geprüft |
| Keine automatische Entscheidung | System macht Vorschläge, Lehrkraft entscheidet |
| Widerspruchsrecht | Opt-out von Training-Verwendung möglich |
### 5.2 Informationsbereitstellung
| Kanal | Zeitpunkt | Zielgruppe |
|-------|-----------|------------|
| Einschulungsunterlagen | Bei Schulanmeldung | Erziehungsberechtigte |
| Datenschutzerklärung Website | Dauerhaft | Alle |
| Klausur-Deckblatt (optional) | Bei Prüfung | Schüler |
| Elternabend | Jährlich | Erziehungsberechtigte |
---
## 6. Automatisierte Entscheidungsfindung (Art. 22 DSGVO)
### 6.1 Anwendbarkeitsprüfung
**Prüfung der Tatbestandsmerkmale:**
| Merkmal | Erfüllt | Begründung |
|---------|---------|------------|
| Automatisierte Verarbeitung | Ja | KI-gestützte Texterkennung |
| Entscheidung | Nein | OCR liefert nur Vorschlag |
| Rechtliche Wirkung/erhebliche Beeinträchtigung | Nein | Lehrkraft trifft finale Bewertungsentscheidung |
**Ergebnis:** Art. 22 DSGVO ist **nicht anwendbar**, da keine automatisierte Entscheidung mit rechtlicher Wirkung erfolgt.
### 6.2 Teacher-in-the-Loop Garantie
Das System implementiert obligatorische menschliche Aufsicht:
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ OCR-System │────►│ Lehrkraft │────►│ Bewertung │
│ (Vorschlag) │ │ (Prüfung) │ │ (Final) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ ▼ │
│ ┌──────────────┐ │
└───────────►│ Korrektur │◄───────────┘
│ (Optional) │
└──────────────┘
```
**Workflow-Garantien:**
1. Kein OCR-Ergebnis wird automatisch als korrekt übernommen
2. Lehrkraft muss explizit bestätigen ODER korrigieren
3. Bewertungsentscheidung liegt ausschließlich bei der Lehrkraft
4. System gibt keine Notenvorschläge
### 6.3 Dokumentation der menschlichen Aufsicht
| Metrik | Erhebung |
|--------|----------|
| Bestätigungsrate | % der OCR-Ergebnisse als korrekt bestätigt |
| Korrekturrate | % der OCR-Ergebnisse mit Korrekturen |
| Durchschnittliche Prüfzeit | Zeit pro Item in Sekunden |
| Lehrkraft-ID | Pseudonymisiert für Audit-Trail |
---
## 7. Privacy by Design und Default (Art. 25 DSGVO)
### 7.1 Design-Prinzipien
| Prinzip | Implementierung |
|---------|-----------------|
| **Proaktive Maßnahmen** | Datenschutz von Anfang an im System-Design berücksichtigt |
| **Standard-Datenschutz** | Minimale Datenerhebung als Default |
| **Eingebetteter Datenschutz** | Technische Maßnahmen nicht umgehbar |
| **Volle Funktionalität** | Kein Trade-off Datenschutz vs. Funktionalität |
| **End-to-End Sicherheit** | Verschlüsselung vom Upload bis zur Löschung |
| **Sichtbarkeit/Transparenz** | Alle Verarbeitungen protokolliert und nachvollziehbar |
| **Nutzerzentrierung** | Betroffenenrechte einfach ausübbar |
### 7.2 Umsetzung Datenminimierung
| Maßnahme | Beschreibung |
|----------|--------------|
| Bildausschnitte | Nur relevante Textbereiche, nicht vollständige Seiten |
| Metadaten-Beschränkung | Keine Speicherung von Geräteinformationen des Scanners |
| Pseudonymisierung | Schüler-IDs durch UUIDs ersetzt bei Export |
| Automatische Löschung | Konfigurierbare Aufbewahrungsfristen |
### 7.3 Default-Einstellungen
| Einstellung | Default | Begründung |
|-------------|---------|------------|
| OCR-Ergebnis automatisch übernehmen | Nein | Menschliche Prüfung erforderlich |
| Training-Export aktiviert | Nein | Opt-in erforderlich |
| Metadaten-Speicherung | Minimal | Nur notwendige Daten |
| Zugriffsprotokollierung | Ja | Transparenz und Nachvollziehbarkeit |
### 7.4 Vendor-Auswahl
Die verwendeten KI-Modelle wurden nach Datenschutzkriterien ausgewählt:
| Modell | Anbieter | Lizenz | Lokale Ausführung | Telemetrie |
|--------|----------|--------|-------------------|------------|
| llama3.2-vision:11b | Meta | Llama 3.2 Community | ✓ | Keine |
| qwen2.5:14b | Alibaba | Apache 2.0 | ✓ | Keine |
| TrOCR | Microsoft | MIT | ✓ | Keine |
---
## 8. Technisch-Organisatorische Maßnahmen (Art. 32 DSGVO)
### 8.1 Vertraulichkeit
#### 8.1.1 Zutrittskontrolle
| Maßnahme | Umsetzung |
|----------|-----------|
| Physische Sicherung | Server in abgeschlossenem Raum |
| Zugangsprotokoll | Elektronisches Schloss mit Protokollierung |
| Berechtigte Personen | IT-Administrator, Schulleitung |
#### 8.1.2 Zugangskontrolle
| Maßnahme | Umsetzung |
|----------|-----------|
| Authentifizierung | Benutzername + Passwort |
| Passwort-Policy | Min. 12 Zeichen, Komplexitätsanforderungen |
| Session-Timeout | 30 Minuten Inaktivität |
| Fehlversuche | Account-Sperrung nach 5 Fehlversuchen |
#### 8.1.3 Zugriffskontrolle (RBAC)
| Rolle | Berechtigungen |
|-------|----------------|
| **Admin** | Vollzugriff, Benutzerverwaltung |
| **Lehrkraft** | Eigene Sessions, Labeling, Export |
| **Viewer** | Nur Lesezugriff auf Statistiken |
#### 8.1.4 Pseudonymisierung
| Datenfeld | Maßnahme |
|-----------|----------|
| Schüler-ID | UUID statt Klarname bei Export |
| Lehrkraft-ID | Pseudonymisiert in Logs |
| Session-Name | Keine Schülernamen erlaubt |
#### 8.1.5 Verschlüsselung
| Bereich | Maßnahme |
|---------|----------|
| Festplatte | FileVault 2 (AES-256) |
| Datenbank | Transparent Data Encryption |
| MinIO Storage | Server-Side Encryption (SSE) |
| Netzwerk | TLS 1.3 für lokale Verbindungen |
### 8.2 Integrität
#### 8.2.1 Weitergabekontrolle
| Maßnahme | Umsetzung |
|----------|-----------|
| Netzwerkisolation | Lokales Netz, keine Internet-Verbindung erforderlich |
| USB-Ports | Administrativ deaktiviert |
| Firewall | Eingehende Verbindungen blockiert |
#### 8.2.2 Eingabekontrolle
| Maßnahme | Umsetzung |
|----------|-----------|
| Audit-Log | Alle Aktionen mit Timestamp und User-ID |
| Unveränderlichkeit | Append-only Logging |
| Log-Retention | 1 Jahr |
**Protokollierte Aktionen:**
- Session erstellen/löschen
- Bild hochladen
- OCR ausführen
- Label bestätigen/korrigieren/überspringen
- Export durchführen
- Login/Logout
### 8.3 Verfügbarkeit
| Maßnahme | Umsetzung |
|----------|-----------|
| Backup | Tägliches inkrementelles Backup |
| USV | Unterbrechungsfreie Stromversorgung |
| RAID | RAID 1 Spiegelung für Datenträger |
| Recovery-Test | Halbjährlich |
### 8.4 Belastbarkeit
| Maßnahme | Umsetzung |
|----------|-----------|
| Ressourcen-Monitoring | Prometheus + Grafana |
| Alerts | E-Mail bei kritischen Schwellwerten |
| Kapazitätsplanung | Jährliche Review |
---
## 9. BSI-Anforderungen und Sicherheitsrichtlinien
### 9.1 Angewandte BSI-Publikationen
| Publikation | Relevanz | Umsetzung |
|-------------|----------|-----------|
| IT-Grundschutz-Kompendium | Basis-Absicherung | TOM nach Abschnitt 8 |
| BSI TR-03116-4 | Kryptographische Verfahren | AES-256, TLS 1.3 |
| Kriterienkatalog KI (Juni 2025) | KI-Sicherheit | Siehe 9.2 |
| QUAIDAL (Juli 2025) | Trainingsdaten-Qualität | Siehe 9.3 |
### 9.2 KI-Sicherheitsanforderungen (BSI Kriterienkatalog)
| Kriterium | Anforderung | Umsetzung |
|-----------|-------------|-----------|
| Modellintegrität | Schutz vor Manipulation | Lokale Modelle, keine Updates ohne Review |
| Eingabevalidierung | Schutz vor Adversarial Attacks | Bildformat-Prüfung, Größenlimits |
| Ausgabevalidierung | Plausibilitätsprüfung | Konfidenz-Schwellwerte |
| Protokollierung | Nachvollziehbarkeit | Vollständiges Audit-Log |
| Incident Response | Reaktion auf Fehlfunktionen | Eskalationsprozess definiert |
### 9.3 Trainingsdaten-Qualität (QUAIDAL)
| Qualitätskriterium | Umsetzung |
|--------------------|-----------|
| **Herkunftsdokumentation** | Alle Trainingsdaten aus eigenem Labeling-Prozess |
| **Repräsentativität** | Diverse Handschriften aus verschiedenen Klassenstufen |
| **Qualitätskontrolle** | Lehrkraft-Verifikation jedes Samples |
| **Bias-Prüfung** | Regelmäßige Stichproben-Analyse |
| **Versionierung** | Git-basierte Versionskontrolle für Datasets |
---
## 10. EU AI Act Compliance (KI-Verordnung)
### 10.1 Risikoklassifizierung
**Prüfung nach Anhang III der KI-Verordnung:**
| Hochrisiko-Kategorie | Anwendbar | Begründung |
|---------------------|-----------|------------|
| 3(a) Biometrische Identifizierung | Nein | Keine biometrische Verarbeitung |
| 3(b) Kritische Infrastruktur | Nein | Keine kritische Infrastruktur |
| 3(c) Allgemeine/berufliche Bildung | **Prüfen** | Bildungsbereich |
| 3(d) Beschäftigung | Nein | Nicht anwendbar |
**Detailprüfung Bildung (Anhang III, Nr. 3c):**
Das System wird **nicht** für folgende Hochrisiko-Anwendungen genutzt:
- ✗ Entscheidung über Zugang zu Bildungseinrichtungen
- ✗ Zuweisung zu Bildungseinrichtungen oder -programmen
- ✗ Bewertung von Lernergebnissen (nur Unterstützung, keine automatische Bewertung)
- ✗ Überwachung von Prüfungen
**Ergebnis:** Kein Hochrisiko-KI-System nach aktuellem Stand.
### 10.2 Allgemeine Anforderungen
Auch ohne Hochrisiko-Klassifizierung werden folgende Transparenzanforderungen erfüllt:
| Anforderung | Umsetzung |
|-------------|-----------|
| KI-Literacy (Art. 4) | Schulung der Lehrkräfte |
| Transparenz gegenüber Nutzern | Information über KI-Einsatz |
| Menschliche Aufsicht | Teacher-in-the-Loop |
### 10.3 Verbotsprüfung (Art. 5)
| Verbotene Praxis | Geprüft | Ergebnis |
|------------------|---------|----------|
| Unterschwellige Manipulation | ✓ | Nicht vorhanden |
| Ausnutzung von Schwächen | ✓ | Nicht vorhanden |
| Social Scoring | ✓ | Nicht vorhanden |
| Echtzeit-Biometrie | ✓ | Nicht vorhanden |
| Emotionserkennung in Bildung | ✓ | **Nicht vorhanden** |
---
## 11. ML/AI Training Dokumentation
### 11.1 Trainingsdaten-Quellen
| Datensatz | Quelle | Rechtsgrundlage | Volumen |
|-----------|--------|-----------------|---------|
| Klausur-Scans | Schulinterne Prüfungen | Art. 6(1)(e) + Einwilligung | Variabel |
| Lehrer-Korrekturen | Labeling-System | Art. 6(1)(e) | Variabel |
### 11.2 Datenqualitätsmaßnahmen
| Maßnahme | Beschreibung |
|----------|--------------|
| Deduplizierung | SHA256-Hash zur Vermeidung von Duplikaten |
| Qualitätskontrolle | Jedes Sample von Lehrkraft geprüft |
| Repräsentativität | Samples aus verschiedenen Fächern/Klassenstufen |
| Dokumentation | Metadaten zu jedem Sample |
### 11.3 Labeling-Prozess
```
┌─────────────────────────────────────────────────────────────────────┐
│ LABELING WORKFLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Bild-Upload 2. OCR-Vorschlag 3. Review │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Scan │─────────►│ LLM-OCR │─────────►│ Lehrkraft │ │
│ │ Upload │ │ (lokal) │ │ prüft │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌──────────────────────┴─────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────┐ │
│ │ Bestätigt │ │Korrigiert│ │
│ │ (korrekt) │ │(manuell) │ │
│ └─────────────┘ └─────────┘ │
│ │ │ │
│ └──────────┬─────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ground Truth │ │
│ │ (verifiziert) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### 11.4 Export-Prozeduren
| Schritt | Beschreibung | Datenschutzmaßnahme |
|---------|--------------|---------------------|
| 1. Auswahl | Sessions/Items für Export wählen | Nur bestätigte/korrigierte Items |
| 2. Pseudonymisierung | Entfernung direkter Identifikatoren | UUID statt Schüler-ID |
| 3. Format-Konvertierung | TrOCR/Llama/Generic Format | Nur notwendige Felder |
| 4. Speicherung | Lokal in /app/ocr-exports/ | Verschlüsselt, zugriffsbeschränkt |
### 11.5 Modell-Provenienz
| Modell | Basis | Fine-Tuning Daten | Training-Parameter |
|--------|-------|-------------------|-------------------|
| llama3.2-vision:11b | Meta Llama 3.2 | Lokale gelabelte Daten | Dokumentiert pro Training |
| TrOCR | Microsoft | Lokale gelabelte Daten | Dokumentiert pro Training |
---
## 12. Betroffenenrechte
### 12.1 Implementierte Rechte
| Recht | Art. DSGVO | Umsetzung |
|-------|-----------|-----------|
| **Auskunft** | 15 | Schriftliche Anfrage an DSB |
| **Berichtigung** | 16 | Korrektur falscher OCR-Ergebnisse |
| **Löschung** | 17 | Nach Aufbewahrungsfrist oder auf Antrag |
| **Einschränkung** | 18 | Sperrung der Verarbeitung auf Antrag |
| **Datenportabilität** | 20 | Export eigener Daten in JSON |
| **Widerspruch** | 21 | Opt-out von Training-Verwendung |
### 12.2 Sonderrechte bei KI-Training
| Recht | Umsetzung |
|-------|-----------|
| Widerspruch gegen Training | Daten werden nicht für Fine-Tuning verwendet |
| Löschung aus Trainingsset | "Machine Unlearning" durch Re-Training ohne betroffene Daten |
### 12.3 Anfrage-Prozess
| Schritt | Frist | Verantwortlich |
|---------|-------|----------------|
| Eingang der Anfrage | - | Sekretariat |
| Identitätsprüfung | 3 Werktage | DSB |
| Bearbeitung | 1 Monat | IT + DSB |
| Antwort | 1 Monat | DSB |
---
## 13. Schulung und Awareness
### 13.1 Schulungskonzept
| Schulung | Zielgruppe | Frequenz | Dokumentation |
|----------|------------|----------|---------------|
| DSGVO-Grundlagen | Alle Lehrkräfte | Jährlich | Teilnehmerliste |
| OCR-System-Nutzung | Nutzende Lehrkräfte | Bei Einführung | Zertifikat |
| KI-Kompetenz (AI Act Art. 4) | Alle Nutzenden | Jährlich | Nachweis |
### 13.2 Schulungsinhalte
**DSGVO-Grundlagen:**
- Prinzipien der Datenverarbeitung
- Betroffenenrechte
- Meldepflichten bei Datenpannen
**OCR-System-Nutzung:**
- Systemfunktionen und Bedienung
- Datenschutzrelevante Einstellungen
- Dos and Don'ts
**KI-Kompetenz:**
- Funktionsweise von KI-Systemen
- Grenzen und Risiken
- Verantwortungsvoller Umgang
---
## 14. Review und Audit
### 14.1 Regelmäßige Überprüfungen
| Prüfung | Frequenz | Verantwortlich |
|---------|----------|----------------|
| DSFA-Review | Jährlich | DSB |
| TOM-Wirksamkeit | Jährlich | IT-Administrator |
| Zugriffsrechte | Halbjährlich | IT-Administrator |
| Backup-Test | Halbjährlich | IT-Administrator |
| Modell-Bias-Prüfung | Jährlich | IT + Lehrkräfte |
### 14.2 Audit-Trail
| Protokollierte Daten | Aufbewahrung | Format |
|---------------------|--------------|--------|
| Benutzeraktionen | 1 Jahr | PostgreSQL |
| Systemereignisse | 1 Jahr | Syslog |
| Sicherheitsvorfälle | 3 Jahre | Incident-Dokumentation |
---
## 15. Vorfallmanagement
### 15.1 Datenpannen-Prozess
```
┌─────────────────────────────────────────────────────────────────────┐
│ INCIDENT RESPONSE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Erkennung ──► Bewertung ──► Meldung ──► Eindämmung ──► Behebung │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Monitoring Risiko- 72h an LfD Isolation Ursachen- │
│ Audit-Log einschätzung (Art.33) Forensik analyse │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### 15.2 Meldepflichten
| Ereignis | Frist | Empfänger |
|----------|-------|-----------|
| Datenpanne mit Risiko | 72 Stunden | Landesbeauftragte/r für Datenschutz |
| Hohes Risiko für Betroffene | Unverzüglich | Betroffene Personen |
### 15.3 KI-spezifische Vorfälle
| Vorfall | Reaktion |
|---------|----------|
| Systematisch falsche OCR-Ergebnisse | Modell-Rollback, Analyse |
| Bias-Erkennung | Untersuchung, ggf. Re-Training |
| Adversarial Attack | System-Isolierung, Forensik |
---
## 16. Kontakte
### 16.1 Interne Kontakte
| Rolle | Name | Kontakt |
|-------|------|---------|
| Schulleitung | [Name] | [E-Mail] |
| IT-Administrator | [Name] | [E-Mail] |
| Datenschutzbeauftragter | [Name] | [E-Mail] |
### 16.2 Externe Kontakte
| Institution | Kontakt |
|-------------|---------|
| LfD Niedersachsen | poststelle@lfd.niedersachsen.de |
| BSI | bsi@bsi.bund.de |
---
## Anhänge
### Anhang A: Systemarchitektur-Diagramm
Siehe Abschnitt 4.2
### Anhang B: TOM-Checkliste
| Kategorie | Maßnahme | Status |
|-----------|----------|--------|
| Zutrittskontrolle | Serverraum verschlossen | ✓ |
| Zugangskontrolle | Passwort-Policy | ✓ |
| Zugriffskontrolle | RBAC implementiert | ✓ |
| Weitergabekontrolle | Netzwerkisolation | ✓ |
| Eingabekontrolle | Audit-Logging | ✓ |
| Verfügbarkeit | Backup + USV | ✓ |
| Trennungskontrolle | Mandantentrennung | ✓ |
| Verschlüsselung | FileVault + TLS | ✓ |
### Anhang C: Muster-Informationsschreiben
[Zu erstellen für spezifische Schule]
### Anhang D: Einwilligungserklärung Training
[Zu erstellen für spezifische Schule]
### Anhang E: Vendor-Dokumentation
- llama3.2-vision: https://llama.meta.com/
- TrOCR: https://github.com/microsoft/unilm/tree/master/trocr
- Ollama: https://ollama.ai/
---
**Dokumentende**
*Diese Dokumentation wird jährlich oder bei wesentlichen Änderungen aktualisiert.*
*Letzte Aktualisierung: 21. Januar 2026*

View File

@@ -0,0 +1,227 @@
# NiBiS Ingestion Pipeline
## Overview
Die NiBiS Ingestion Pipeline verarbeitet Abitur-Erwartungshorizonte aus Niedersachsen und indexiert sie in Qdrant für RAG-basierte Klausurkorrektur.
## Unterstützte Daten
### Verzeichnisse
| Verzeichnis | Jahre | Namenskonvention |
|-------------|-------|------------------|
| `docs/za-download` | 2024, 2025 | `{Jahr}_{Fach}_{niveau}_{Nr}_EWH.pdf` |
| `docs/za-download-2` | 2016 | `{Jahr}{Fach}{Niveau}Lehrer/{Jahr}{Fach}{Niveau}A{Nr}L.pdf` |
| `docs/za-download-3` | 2017 | `{Jahr}{Fach}{Niveau}Lehrer/{Jahr}{Fach}{Niveau}A{Nr}L.pdf` |
### Dokumenttypen
- **EWH** - Erwartungshorizont (Hauptziel)
- **Aufgabe** - Prüfungsaufgaben
- **Material** - Zusatzmaterialien
- **GBU** - Gefährdungsbeurteilung (Chemie/Biologie)
- **Bewertungsbogen** - Standardisierte Bewertungsbögen
### Fächer
Deutsch, Englisch, Mathematik, Informatik, Biologie, Chemie, Physik, Geschichte, Erdkunde, Kunst, Musik, Sport, Latein, Griechisch, Französisch, Spanisch, Katholische Religion, Evangelische Religion, Werte und Normen, BRC, BVW, Gesundheit-Pflege
## Architektur
```
┌─────────────────────────────────────────────────────────────────┐
│ NiBiS Ingestion Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. ZIP Extraction │
│ └── Entpackt 2024.zip, 2025.zip, etc. │
│ │
│ 2. Document Discovery │
│ ├── Parst alte Namenskonvention (2016/2017) │
│ └── Parst neue Namenskonvention (2024/2025) │
│ │
│ 3. PDF Processing │
│ ├── Text-Extraktion (PyPDF2) │
│ └── Chunking (1000 chars, 200 overlap) │
│ │
│ 4. Embedding Generation │
│ └── OpenAI text-embedding-3-small (1536 dim) │
│ │
│ 5. Qdrant Indexing │
│ └── Collection: bp_nibis_eh │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Verwendung
### Via API (empfohlen)
```bash
# 1. Vorschau der verfügbaren Dokumente
curl http://localhost:8086/api/v1/admin/nibis/discover
# 2. ZIP-Dateien entpacken
curl -X POST http://localhost:8086/api/v1/admin/nibis/extract-zips
# 3. Ingestion starten
curl -X POST http://localhost:8086/api/v1/admin/nibis/ingest \
-H "Content-Type: application/json" \
-d '{"ewh_only": true}'
# 4. Status prüfen
curl http://localhost:8086/api/v1/admin/nibis/status
# 5. Semantische Suche testen
curl -X POST http://localhost:8086/api/v1/admin/nibis/search \
-H "Content-Type: application/json" \
-d '{"query": "Analyse literarischer Texte", "subject": "Deutsch", "limit": 5}'
```
### Via CLI
```bash
# Dry-Run (nur analysieren)
cd klausur-service/backend
python nibis_ingestion.py --dry-run
# Vollständige Ingestion
python nibis_ingestion.py
# Nur bestimmtes Jahr
python nibis_ingestion.py --year 2024
# Nur bestimmtes Fach
python nibis_ingestion.py --subject Deutsch
# Manifest erstellen
python nibis_ingestion.py --manifest /tmp/nibis_manifest.json
```
### Via Shell Script
```bash
./klausur-service/scripts/run_nibis_ingestion.sh --dry-run
./klausur-service/scripts/run_nibis_ingestion.sh --year 2024 --subject Deutsch
```
## Qdrant Schema
### Collection: `bp_nibis_eh`
```json
{
"id": "nibis_2024_deutsch_ea_1_abc123_chunk_0",
"vector": [1536 dimensions],
"payload": {
"doc_id": "nibis_2024_deutsch_ea_1_abc123",
"chunk_index": 0,
"text": "Der Erwartungshorizont...",
"year": 2024,
"subject": "Deutsch",
"niveau": "eA",
"task_number": 1,
"doc_type": "EWH",
"bundesland": "NI",
"variant": null,
"source": "nibis",
"training_allowed": true
}
}
```
## API Endpoints
| Methode | Endpoint | Beschreibung |
|---------|----------|--------------|
| GET | `/api/v1/admin/nibis/status` | Ingestion-Status |
| POST | `/api/v1/admin/nibis/extract-zips` | ZIP-Dateien entpacken |
| GET | `/api/v1/admin/nibis/discover` | Dokumente finden |
| POST | `/api/v1/admin/nibis/ingest` | Ingestion starten |
| POST | `/api/v1/admin/nibis/search` | Semantische Suche |
| GET | `/api/v1/admin/nibis/stats` | Statistiken |
| GET | `/api/v1/admin/nibis/collections` | Qdrant Collections |
| DELETE | `/api/v1/admin/nibis/collection` | Collection löschen |
## Erweiterung für andere Bundesländer
Die Pipeline ist so designed, dass sie leicht erweitert werden kann:
### 1. Neues Bundesland hinzufügen
```python
# In nibis_ingestion.py
# Bundesland-Code (ISO 3166-2:DE)
BUNDESLAND_CODES = {
"NI": "Niedersachsen",
"BE": "Berlin",
"BY": "Bayern",
# ...
}
# Parsing-Funktion für neues Format
def parse_filename_berlin(filename: str, file_path: Path) -> Optional[Dict]:
# Berlin-spezifische Namenskonvention
pass
```
### 2. Neues Verzeichnis registrieren
```python
# docs/za-download-berlin/ hinzufügen
ZA_DOWNLOAD_DIRS = [
"za-download",
"za-download-2",
"za-download-3",
"za-download-berlin", # NEU
]
```
### 3. Dokumenttyp-Erweiterung
Für Zeugnisgeneration oder andere Dokumenttypen:
```python
DOC_TYPES = {
"EWH": "Erwartungshorizont",
"ZEUGNIS_VORLAGE": "Zeugnisvorlage",
"NOTENSPIEGEL": "Notenspiegel",
"BEMERKUNG": "Bemerkungstexte",
}
```
## Rechtliche Hinweise
- NiBiS-Daten sind unter den [NiBiS-Nutzungsbedingungen](https://nibis.de) frei nutzbar
- `training_allowed: true` - Strukturelles Wissen darf für KI-Training genutzt werden
- Für Lehrer-eigene Erwartungshorizonte (BYOEH) gilt: `training_allowed: false`
## Troubleshooting
### Qdrant nicht erreichbar
```bash
# Prüfen ob Qdrant läuft
curl http://localhost:6333/health
# Docker starten
docker-compose up -d qdrant
```
### OpenAI API Fehler
```bash
# API Key setzen
export OPENAI_API_KEY=sk-...
```
### PDF-Extraktion fehlgeschlagen
Einige PDFs können problematisch sein (gescannte Dokumente ohne OCR). Diese werden übersprungen und im Error-Log protokolliert.
## Performance
- ~500-1000 Chunks pro Minute (abhängig von OpenAI API)
- ~2-3 GB Qdrant Storage für alle NiBiS-Daten (2016-2025)
- Embeddings werden nur einmal generiert (idempotent via Hash)

View File

@@ -0,0 +1,446 @@
# OCR-Labeling System Spezifikation
**Version:** 1.1.0
**Datum:** 2026-01-23
**Status:** In Produktion (Mac Mini)
## Übersicht
Das OCR-Labeling System ermöglicht das Erstellen von Trainingsdaten für Handschrift-OCR-Modelle aus eingescannten Klausuren. Es unterstützt folgende OCR-Modelle:
| Modell | Beschreibung | Geschwindigkeit | Empfohlen für |
|--------|--------------|-----------------|---------------|
| **llama3.2-vision:11b** | Vision-LLM (Standard) | Langsam | Handschrift, beste Qualität |
| **TrOCR** | Microsoft Transformer | Schnell | Gedruckter Text |
| **PaddleOCR + LLM** | Hybrid-Ansatz (NEU) | Sehr schnell (4x) | Gemischte Dokumente |
| **Donut** | Document Understanding (NEU) | Mittel | Tabellen, Formulare |
| **qwen2.5:14b** | Korrektur-LLM | - | Klausurbewertung |
### Neue OCR-Optionen (v1.1.0)
#### PaddleOCR + LLM (Empfohlen für Geschwindigkeit)
PaddleOCR ist ein zweistufiger Ansatz:
1. **PaddleOCR** - Schnelle, präzise Texterkennung mit Bounding-Boxes
2. **qwen2.5:14b** - Semantische Strukturierung des erkannten Texts
**Vorteile:**
- 4x schneller als Vision-LLM (~7-15 Sek vs 30-60 Sek pro Seite)
- Höhere Genauigkeit bei gedrucktem Text (95-99%)
- Weniger Halluzinationen (LLM korrigiert nur, erfindet nicht)
- Position-basierte Spaltenerkennung möglich
**Dateien:**
- `/klausur-service/backend/hybrid_vocab_extractor.py` - PaddleOCR Integration
#### Donut (Document Understanding Transformer)
Donut ist speziell für strukturierte Dokumente optimiert:
- Tabellen und Formulare
- Rechnungen und Quittungen
- Multi-Spalten-Layouts
**Dateien:**
- `/klausur-service/backend/services/donut_ocr_service.py` - Donut Service
## Architektur
```
┌──────────────────────────────────────────────────────────────────────────┐
│ OCR-Labeling System │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────┐ ┌────────────────────────┐ │
│ │ Frontend │◄──►│ Klausur-Service │◄──►│ PostgreSQL │ │
│ │ (Next.js) │ │ (FastAPI) │ │ - ocr_labeling_sessions│ │
│ │ Port 3000 │ │ Port 8086 │ │ - ocr_labeling_items │ │
│ └─────────────┘ └────────┬─────────┘ │ - ocr_training_samples │ │
│ │ └────────────────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌─────────┐ ┌───────────────┐ │
│ │ MinIO │ │ Ollama │ │ Export Service │ │
│ │ (Images) │ │ (OCR) │ │ (Training) │ │
│ │ Port 9000 │ │ :11434 │ │ │ │
│ └───────────┘ └─────────┘ └───────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
## Datenmodell
### PostgreSQL Tabellen
```sql
-- Labeling Sessions (gruppiert zusammengehörige Bilder)
CREATE TABLE ocr_labeling_sessions (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
source_type VARCHAR(50) NOT NULL, -- 'klausur', 'handwriting_sample', 'scan'
description TEXT,
ocr_model VARCHAR(100), -- z.B. 'llama3.2-vision:11b'
total_items INTEGER DEFAULT 0,
labeled_items INTEGER DEFAULT 0,
confirmed_items INTEGER DEFAULT 0,
corrected_items INTEGER DEFAULT 0,
skipped_items INTEGER DEFAULT 0,
teacher_id VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
-- Einzelne Labeling Items (Bild + OCR + Ground Truth)
CREATE TABLE ocr_labeling_items (
id VARCHAR(36) PRIMARY KEY,
session_id VARCHAR(36) REFERENCES ocr_labeling_sessions(id),
image_path TEXT NOT NULL, -- MinIO Pfad oder lokaler Pfad
image_hash VARCHAR(64), -- SHA256 für Deduplizierung
ocr_text TEXT, -- Von LLM erkannter Text
ocr_confidence FLOAT, -- Konfidenz (0-1)
ocr_model VARCHAR(100),
ground_truth TEXT, -- Korrigierter/bestätigter Text
status VARCHAR(20) DEFAULT 'pending', -- pending/confirmed/corrected/skipped
labeled_by VARCHAR(100),
labeled_at TIMESTAMP,
label_time_seconds INTEGER,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Exportierte Training Samples
CREATE TABLE ocr_training_samples (
id VARCHAR(36) PRIMARY KEY,
item_id VARCHAR(36) REFERENCES ocr_labeling_items(id),
image_path TEXT NOT NULL,
ground_truth TEXT NOT NULL,
export_format VARCHAR(50) NOT NULL, -- 'generic', 'trocr', 'llama_vision'
exported_at TIMESTAMP DEFAULT NOW(),
training_batch VARCHAR(100),
used_in_training BOOLEAN DEFAULT FALSE
);
```
## API Referenz
Base URL: `http://macmini:8086/api/v1/ocr-label`
### Sessions
#### POST /sessions
Neue Labeling-Session erstellen.
**Request:**
```json
{
"name": "Klausur Deutsch 12a Q1",
"source_type": "klausur",
"description": "Gedichtanalyse Expressionismus",
"ocr_model": "llama3.2-vision:11b"
}
```
**Response:**
```json
{
"id": "abc-123-def",
"name": "Klausur Deutsch 12a Q1",
"source_type": "klausur",
"total_items": 0,
"labeled_items": 0,
"created_at": "2026-01-21T10:30:00Z"
}
```
#### GET /sessions
Sessions auflisten.
**Query Parameter:**
- `limit` (int, default: 50) - Maximale Anzahl
#### GET /sessions/{session_id}
Einzelne Session abrufen.
### Upload
#### POST /sessions/{session_id}/upload
Bilder zu einer Session hochladen.
**Request:** Multipart Form Data
- `files` (File[]) - PNG/JPG/PDF Dateien
- `run_ocr` (bool, default: true) - OCR direkt ausführen
- `metadata` (JSON string) - Optional: Metadaten
**Response:**
```json
{
"session_id": "abc-123-def",
"uploaded_count": 5,
"items": [
{
"id": "item-1",
"filename": "scan_001.png",
"image_path": "ocr-labeling/abc-123/item-1.png",
"ocr_text": "Die Lösung der Aufgabe...",
"ocr_confidence": 0.87,
"status": "pending"
}
]
}
```
### Labeling Queue
#### GET /queue
Nächste zu labelnde Items abrufen.
**Query Parameter:**
- `session_id` (str, optional) - Nach Session filtern
- `status` (str, default: "pending") - Status-Filter
- `limit` (int, default: 10) - Maximale Anzahl
**Response:**
```json
[
{
"id": "item-456",
"session_id": "abc-123",
"session_name": "Klausur Deutsch",
"image_path": "/app/ocr-labeling/abc-123/item-456.png",
"image_url": "/api/v1/ocr-label/images/abc-123/item-456.png",
"ocr_text": "Erkannter Text...",
"ocr_confidence": 0.87,
"ground_truth": null,
"status": "pending",
"metadata": {"page": 1}
}
]
```
### Labeling Actions
#### POST /confirm
OCR-Text als korrekt bestätigen.
**Request:**
```json
{
"item_id": "item-456",
"label_time_seconds": 5
}
```
**Effect:** `ground_truth = ocr_text`, `status = 'confirmed'`
#### POST /correct
Ground Truth korrigieren.
**Request:**
```json
{
"item_id": "item-456",
"ground_truth": "Korrigierter Text hier",
"label_time_seconds": 15
}
```
**Effect:** `ground_truth = <input>`, `status = 'corrected'`
#### POST /skip
Item überspringen (unbrauchbar).
**Request:**
```json
{
"item_id": "item-456"
}
```
**Effect:** `status = 'skipped'` (wird nicht exportiert)
### Statistiken
#### GET /stats
Labeling-Statistiken abrufen.
**Query Parameter:**
- `session_id` (str, optional) - Für Session-spezifische Stats
**Response:**
```json
{
"total_items": 100,
"labeled_items": 75,
"confirmed_items": 60,
"corrected_items": 15,
"pending_items": 25,
"accuracy_rate": 0.80,
"avg_label_time_seconds": 8.5
}
```
### Training Export
#### POST /export
Trainingsdaten exportieren.
**Request:**
```json
{
"export_format": "trocr",
"session_id": "abc-123",
"batch_id": "batch_20260121"
}
```
**Export Formate:**
| Format | Beschreibung | Output |
|--------|--------------|--------|
| `generic` | Allgemeines JSONL | `{"id", "image_path", "ground_truth", ...}` |
| `trocr` | Microsoft TrOCR | `{"file_name", "text", "id"}` |
| `llama_vision` | Llama 3.2 Vision | OpenAI-style Messages mit image_url |
**Response:**
```json
{
"export_format": "trocr",
"batch_id": "batch_20260121",
"exported_count": 75,
"export_path": "/app/ocr-exports/trocr/batch_20260121",
"manifest_path": "/app/ocr-exports/trocr/batch_20260121/manifest.json",
"samples": [...]
}
```
#### GET /exports
Verfügbare Exports auflisten.
**Query Parameter:**
- `export_format` (str, optional) - Nach Format filtern
## Export Formate im Detail
### TrOCR Format
```
batch_20260121/
├── manifest.json
├── train.jsonl
└── images/
├── item-1.png
└── item-2.png
```
**train.jsonl:**
```jsonl
{"file_name": "images/item-1.png", "text": "Ground truth text", "id": "item-1"}
{"file_name": "images/item-2.png", "text": "Another text", "id": "item-2"}
```
### Llama Vision Format
```jsonl
{
"id": "item-1",
"messages": [
{"role": "system", "content": "Du bist ein OCR-Experte für deutsche Handschrift..."},
{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": "images/item-1.png"}},
{"type": "text", "text": "Lies den handgeschriebenen Text in diesem Bild."}
]},
{"role": "assistant", "content": "Ground truth text"}
]
}
```
### Generic Format
```jsonl
{
"id": "item-1",
"image_path": "images/item-1.png",
"ground_truth": "Ground truth text",
"ocr_text": "OCR recognized text",
"ocr_confidence": 0.87,
"metadata": {"page": 1, "session": "Deutsch 12a"}
}
```
## Frontend Integration
Die OCR-Labeling UI ist unter `/admin/ocr-labeling` verfügbar.
### Keyboard Shortcuts
| Taste | Aktion |
|-------|--------|
| `Enter` | Bestätigen (OCR korrekt) |
| `Tab` | Ins Korrekturfeld springen |
| `Escape` | Überspringen |
| `←` / `→` | Navigation (Prev/Next) |
### Workflow
1. **Session erstellen** - Name, Typ, OCR-Modell wählen
2. **Bilder hochladen** - Drag & Drop oder File-Browser
3. **Labeling durchführen** - Bild + OCR-Text vergleichen
- Korrekt → Bestätigen (Enter)
- Falsch → Korrigieren + Speichern
- Unbrauchbar → Überspringen
4. **Export** - Format wählen (TrOCR, Llama Vision, Generic)
5. **Training starten** - Export-Ordner für Fine-Tuning nutzen
## Umgebungsvariablen
```bash
# PostgreSQL
DATABASE_URL=postgres://user:pass@postgres:5432/breakpilot_db
# MinIO (S3-kompatibel)
MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=breakpilot
MINIO_SECRET_KEY=breakpilot123
MINIO_BUCKET=breakpilot-rag
MINIO_SECURE=false
# Ollama (Vision-LLM)
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_VISION_MODEL=llama3.2-vision:11b
OLLAMA_CORRECTION_MODEL=qwen2.5:14b
# Export
OCR_EXPORT_PATH=/app/ocr-exports
OCR_STORAGE_PATH=/app/ocr-labeling
```
## Sicherheit & Datenschutz
- **100% Lokale Verarbeitung** - Alle Daten bleiben auf dem Mac Mini
- **Keine Cloud-Uploads** - Ollama läuft vollständig offline
- **DSGVO-konform** - Keine Schülerdaten verlassen das Schulnetzwerk
- **Deduplizierung** - SHA256-Hash verhindert doppelte Bilder
## Dateien
| Datei | Beschreibung |
|-------|--------------|
| `klausur-service/backend/ocr_labeling_api.py` | FastAPI Router mit OCR Model Dispatcher |
| `klausur-service/backend/training_export_service.py` | Export-Service für TrOCR/Llama |
| `klausur-service/backend/metrics_db.py` | PostgreSQL CRUD Funktionen |
| `klausur-service/backend/minio_storage.py` | MinIO OCR-Image Storage |
| `klausur-service/backend/hybrid_vocab_extractor.py` | PaddleOCR Integration |
| `klausur-service/backend/services/donut_ocr_service.py` | Donut OCR Service (NEU) |
| `klausur-service/backend/services/trocr_service.py` | TrOCR Service (NEU) |
| `website/app/admin/ocr-labeling/page.tsx` | Frontend UI mit Model-Auswahl |
| `website/app/admin/ocr-labeling/types.ts` | TypeScript Interfaces inkl. OCRModel Type |
## Tests
```bash
# Backend-Tests ausführen
cd klausur-service/backend
pytest tests/test_ocr_labeling.py -v
# Mit Coverage
pytest tests/test_ocr_labeling.py --cov=. --cov-report=html
```

View File

@@ -0,0 +1,472 @@
# RAG & Daten-Management Spezifikation
## Übersicht
Admin-Frontend für die Verwaltung von Trainingsdaten und RAG-Systemen in BreakPilot.
**Location**: `/admin/docs` → Tab "Daten & RAG"
**Backend**: `klausur-service` (Port 8086)
**Storage**: MinIO (persistentes Docker Volume `minio_data`)
**Vector DB**: Qdrant (Port 6333)
## Datenmodell
### Zwei Datentypen mit unterschiedlichen Regeln
| Typ | Quelle | Training erlaubt | Isolation | Collection |
|-----|--------|------------------|-----------|------------|
| **Landes-Daten** | NiBiS, andere Bundesländer | ✅ Ja | Pro Bundesland | `bp_{bundesland}_{usecase}` |
| **Lehrer-Daten** | Lehrer-Upload (BYOEH) | ❌ Nein | Pro Tenant (Schule/Lehrer) | `bp_eh` (verschlüsselt) |
### Bundesland-Codes (ISO 3166-2:DE)
```
NI = Niedersachsen BY = Bayern BW = Baden-Württemberg
NW = Nordrhein-Westf. HE = Hessen SN = Sachsen
BE = Berlin HH = Hamburg SH = Schleswig-Holstein
BB = Brandenburg MV = Meckl.-Vorp. ST = Sachsen-Anhalt
TH = Thüringen RP = Rheinland-Pfalz SL = Saarland
HB = Bremen
```
### Use Cases (RAG-Sammlungen)
| Use Case | Collection Pattern | Beschreibung |
|----------|-------------------|--------------|
| Klausurkorrektur | `bp_{bl}_klausur` | Erwartungshorizonte für Abitur |
| Zeugnisgenerator | `bp_{bl}_zeugnis` | Textbausteine für Zeugnisse |
| Lehrplan | `bp_{bl}_lehrplan` | Kerncurricula, Rahmenrichtlinien |
Beispiel: `bp_ni_klausur` = Niedersachsen Klausurkorrektur
## MinIO Bucket-Struktur
```
breakpilot-rag/
├── landes-daten/
│ ├── ni/ # Niedersachsen
│ │ ├── klausur/
│ │ │ ├── 2016/
│ │ │ │ ├── manifest.json
│ │ │ │ └── *.pdf
│ │ │ ├── 2017/
│ │ │ ├── ...
│ │ │ └── 2025/
│ │ └── zeugnis/
│ ├── by/ # Bayern
│ └── .../
└── lehrer-daten/ # BYOEH - verschlüsselt
└── {tenant_id}/
└── {lehrer_id}/
└── *.pdf.enc
```
## Qdrant Schema
### Landes-Daten Collection (z.B. `bp_ni_klausur`)
```json
{
"id": "uuid-v5-from-string",
"vector": [384 dimensions],
"payload": {
"original_id": "nibis_2024_deutsch_ea_1_abc123_chunk_0",
"doc_id": "nibis_2024_deutsch_ea_1_abc123",
"chunk_index": 0,
"text": "Der Erwartungshorizont...",
"year": 2024,
"subject": "Deutsch",
"niveau": "eA",
"task_number": 1,
"doc_type": "EWH",
"bundesland": "NI",
"source": "nibis",
"training_allowed": true,
"minio_path": "landes-daten/ni/klausur/2024/2024_Deutsch_eA_I_EWH.pdf"
}
}
```
### Lehrer-Daten Collection (`bp_eh`)
```json
{
"id": "uuid",
"vector": [384 dimensions],
"payload": {
"tenant_id": "schule_123",
"eh_id": "eh_abc",
"chunk_index": 0,
"subject": "deutsch",
"encrypted_content": "base64...",
"training_allowed": false
}
}
```
## Frontend-Komponenten
### 1. Sammlungen-Übersicht (`/admin/rag/collections`)
```
┌─────────────────────────────────────────────────────────────────┐
│ Daten & RAG │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Sammlungen [+ Neu] │
│ ───────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📚 Niedersachsen - Klausurkorrektur │ │
│ │ bp_ni_klausur | 630 Docs | 4.521 Chunks | 2016-2025 │ │
│ │ [Suchen] [Indexieren] [Details] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📚 Niedersachsen - Zeugnisgenerator │ │
│ │ bp_ni_zeugnis | 0 Docs | Leer │ │
│ │ [Suchen] [Indexieren] [Details] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 2. Upload-Bereich (`/admin/rag/upload`)
```
┌─────────────────────────────────────────────────────────────────┐
│ Dokumente hochladen │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Ziel-Sammlung: [Niedersachsen - Klausurkorrektur ▼] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 📁 ZIP-Datei oder Ordner hierher ziehen │ │
│ │ │ │
│ │ oder [Dateien auswählen] │ │
│ │ │ │
│ │ Unterstützt: .zip, .pdf, Ordner │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Upload-Queue: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ✅ 2018.zip - 45 PDFs erkannt │ │
│ │ ⏳ 2019.zip - Wird analysiert... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [Hochladen & Indexieren] │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 3. Ingestion-Status (`/admin/rag/ingestion`)
```
┌─────────────────────────────────────────────────────────────────┐
│ Ingestion Status │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Aktueller Job: Niedersachsen Klausur 2024 │
│ ████████████████████░░░░░░░░░░ 65% (412/630 Docs) │
│ Chunks: 2.891 | Fehler: 3 | ETA: 4:32 │
│ [Pausieren] [Abbrechen] │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ Letzte Jobs: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ✅ 09.01.2025 15:30 - NI Klausur 2024 - 128 Chunks │ │
│ │ ✅ 09.01.2025 14:00 - NI Klausur 2017 - 890 Chunks │ │
│ │ ❌ 08.01.2025 10:15 - BY Klausur - Fehler: Timeout │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 4. Suche & Qualitätstest (`/admin/rag/search`)
```
┌─────────────────────────────────────────────────────────────────┐
│ RAG Suche & Qualitätstest │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Sammlung: [Niedersachsen - Klausurkorrektur ▼] │
│ │
│ Query: [Analyse eines Gedichts von Rilke ] │
│ │
│ Filter: │
│ Jahr: [Alle ▼] Fach: [Deutsch ▼] Niveau: [eA ▼] │
│ │
│ [🔍 Suchen] │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ Ergebnisse (3): Latenz: 45ms │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ #1 | Score: 0.847 | 2024 Deutsch eA Aufgabe 2 │ │
│ │ │ │
│ │ "...Die Analyse des Rilke-Gedichts soll folgende │ │
│ │ Aspekte berücksichtigen: Aufbau, Bildsprache..." │ │
│ │ │ │
│ │ Relevanz: [⭐⭐⭐⭐⭐] [⭐⭐⭐⭐] [⭐⭐⭐] [⭐⭐] [⭐] │ │
│ │ Notizen: [Optional: Warum relevant/nicht relevant? ] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 5. Metriken-Dashboard (`/admin/rag/metrics`)
```
┌─────────────────────────────────────────────────────────────────┐
│ RAG Qualitätsmetriken │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Zeitraum: [Letzte 7 Tage ▼] Sammlung: [Alle ▼] │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Precision@5 │ │ Recall@10 │ │ MRR │ │
│ │ 0.78 │ │ 0.85 │ │ 0.72 │ │
│ │ ↑ +5% │ │ ↑ +3% │ │ ↓ -2% │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Avg Latency │ │ Bewertungen │ │ Fehlerrate │ │
│ │ 52ms │ │ 127 │ │ 0.3% │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ Score-Verteilung: │
│ 0.9+ ████████████████ 23% │
│ 0.7+ ████████████████████████████ 41% │
│ 0.5+ ████████████████████ 28% │
│ <0.5 ██████ 8% │
│ │
│ [Export CSV] [Detailbericht] │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## API Endpoints
### Collections API
```
GET /api/v1/admin/rag/collections
POST /api/v1/admin/rag/collections
GET /api/v1/admin/rag/collections/{id}
DELETE /api/v1/admin/rag/collections/{id}
GET /api/v1/admin/rag/collections/{id}/stats
```
### Upload API
```
POST /api/v1/admin/rag/upload
Content-Type: multipart/form-data
- file: ZIP oder PDF
- collection_id: string
- metadata: JSON (optional)
POST /api/v1/admin/rag/upload/folder
- Für Ordner-Upload (WebKitDirectory)
```
### Ingestion API
```
POST /api/v1/admin/rag/ingest
- collection_id: string
- filters: {year?, subject?, doc_type?}
GET /api/v1/admin/rag/ingest/status
GET /api/v1/admin/rag/ingest/history
POST /api/v1/admin/rag/ingest/cancel
```
### Search API
```
POST /api/v1/admin/rag/search
- query: string
- collection_id: string
- filters: {year?, subject?, niveau?}
- limit: int
POST /api/v1/admin/rag/search/feedback
- result_id: string
- rating: 1-5
- notes: string (optional)
```
### Metrics API
```
GET /api/v1/admin/rag/metrics
- collection_id?: string
- from_date?: date
- to_date?: date
GET /api/v1/admin/rag/metrics/export
- format: csv|json
```
## Embedding-Konfiguration
```python
# Default: Lokale Embeddings (kein API-Key nötig)
EMBEDDING_BACKEND = "local"
LOCAL_EMBEDDING_MODEL = "all-MiniLM-L6-v2"
VECTOR_DIMENSIONS = 384
# Optional: OpenAI (für Produktion)
EMBEDDING_BACKEND = "openai"
EMBEDDING_MODEL = "text-embedding-3-small"
VECTOR_DIMENSIONS = 1536
```
## Datenpersistenz
### Docker Volumes (WICHTIG - nicht löschen!)
```yaml
volumes:
minio_data: # Alle hochgeladenen Dokumente
qdrant_data: # Alle Vektoren und Embeddings
postgres_data: # Metadaten, Bewertungen, History
```
### Backup-Strategie
```bash
# MinIO Backup
docker exec breakpilot-pwa-minio mc mirror /data /backup
# Qdrant Backup
curl -X POST http://localhost:6333/collections/bp_ni_klausur/snapshots
# Postgres Backup (bereits implementiert)
# Läuft automatisch täglich um 2 Uhr
```
## Implementierungsreihenfolge
1. ✅ Backend: Basis-Ingestion (nibis_ingestion.py)
2. ✅ Backend: Lokale Embeddings (sentence-transformers)
3. ✅ Backend: MinIO-Integration (minio_storage.py)
4. ✅ Backend: Collections API (admin_api.py)
5. ✅ Backend: Upload API mit ZIP-Support
6. ✅ Backend: Metrics API mit PostgreSQL (metrics_db.py)
7. ✅ Frontend: Sammlungen-Übersicht
8. ✅ Frontend: Upload-Bereich (Drag & Drop)
9. ✅ Frontend: Ingestion-Status
10. ✅ Frontend: Suche & Qualitätstest (mit Stern-Bewertungen)
11. ✅ Frontend: Metriken-Dashboard
## Technologie-Stack
- **Frontend**: Next.js 15 (`/website/app/admin/rag/page.tsx`)
- **Backend**: FastAPI (`klausur-service/backend/`)
- **Vector DB**: Qdrant v1.7.4 (384-dim Vektoren)
- **Object Storage**: MinIO (S3-kompatibel)
- **Embeddings**: sentence-transformers `all-MiniLM-L6-v2`
- **Metrics DB**: PostgreSQL 16
## Entwickler-Dokumentation
### Projektstruktur
```
klausur-service/
├── backend/
│ ├── main.py # FastAPI App + BYOEH Endpoints
│ ├── admin_api.py # RAG Admin API (Upload, Search, Metrics)
│ ├── nibis_ingestion.py # NiBiS Dokument-Ingestion Pipeline
│ ├── eh_pipeline.py # Chunking, Embeddings, Encryption
│ ├── qdrant_service.py # Qdrant Client + Search
│ ├── minio_storage.py # MinIO S3 Storage
│ ├── metrics_db.py # PostgreSQL Metrics
│ ├── requirements.txt # Python Dependencies
│ └── tests/
│ └── test_rag_admin.py
└── docs/
└── RAG-Admin-Spec.md # Diese Datei
```
### Schnellstart für Entwickler
```bash
# 1. Services starten
cd /path/to/breakpilot-pwa
docker-compose up -d qdrant minio postgres
# 2. Dependencies installieren
cd klausur-service/backend
pip install -r requirements.txt
# 3. Service starten
python -m uvicorn main:app --port 8086 --reload
# 4. RAG-Services initialisieren (erstellt Bucket + Tabellen)
curl -X POST http://localhost:8086/api/v1/admin/rag/init
```
### API-Referenz (Implementiert)
#### NiBiS Ingestion
```
GET /api/v1/admin/nibis/discover # Dokumente finden
POST /api/v1/admin/nibis/ingest # Indexierung starten
GET /api/v1/admin/nibis/status # Status abfragen
GET /api/v1/admin/nibis/stats # Statistiken
POST /api/v1/admin/nibis/search # Semantische Suche
GET /api/v1/admin/nibis/collections # Qdrant Collections
```
#### RAG Upload & Storage
```
POST /api/v1/admin/rag/upload # ZIP/PDF hochladen
GET /api/v1/admin/rag/upload/history # Upload-Verlauf
GET /api/v1/admin/rag/storage/stats # MinIO Statistiken
```
#### Metrics & Feedback
```
GET /api/v1/admin/rag/metrics # Qualitätsmetriken
POST /api/v1/admin/rag/search/feedback # Bewertung abgeben
POST /api/v1/admin/rag/init # Services initialisieren
```
### Umgebungsvariablen
```bash
# Qdrant
QDRANT_URL=http://localhost:6333
# MinIO
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=breakpilot
MINIO_SECRET_KEY=breakpilot123
MINIO_BUCKET=breakpilot-rag
# PostgreSQL
DATABASE_URL=postgres://breakpilot:breakpilot123@localhost:5432/breakpilot_db
# Embeddings
EMBEDDING_BACKEND=local
LOCAL_EMBEDDING_MODEL=all-MiniLM-L6-v2
```
### Aktuelle Indexierungs-Statistik
- **Dokumente**: 579 Erwartungshorizonte (NiBiS)
- **Chunks**: 7.352
- **Jahre**: 2016, 2017, 2024, 2025
- **Fächer**: Deutsch, Englisch, Mathematik, Physik, Chemie, Biologie, Geschichte, Politik-Wirtschaft, Erdkunde, Sport, Kunst, Musik, Latein, Informatik, Ev. Religion, Kath. Religion, Werte und Normen, etc.
- **Collection**: `bp_nibis_eh`
- **Vektor-Dimensionen**: 384

View File

@@ -0,0 +1,293 @@
# Vokabel-Arbeitsblatt Generator - Architektur
**Version:** 1.0.0
**Datum:** 2026-01-23
**Status:** Produktiv
---
## 1. Uebersicht
Der Vokabel-Arbeitsblatt Generator ist ein DSGVO-konformes Tool fuer Lehrer, das Vokabeln aus Schulbuchseiten extrahiert und druckfertige Arbeitsblaetter generiert.
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Studio v2 (Next.js) │
│ Port 3001 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ /vocab-worksheet │ │
│ │ - Session-Management (erstellen, fortsetzen, loeschen) │ │
│ │ - PDF-Upload mit Seitenauswahl │ │
│ │ - Vokabel-Bearbeitung (Grid-Editor) │ │
│ │ - Arbeitsblatt-Konfiguration │ │
│ │ - PDF-Export │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
▼ HTTP/REST
┌─────────────────────────────────────────────────────────────────────────┐
│ Klausur-Service (FastAPI) │
│ Port 8086 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ /api/v1/vocab/* │ │
│ │ - Session CRUD │ │
│ │ - PDF-Verarbeitung (PyMuPDF) │ │
│ │ - Vokabel-Extraktion (Vision LLM / Hybrid OCR) │ │
│ │ - Arbeitsblatt-Generierung (WeasyPrint) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐
▼ ▼
┌───────────────────────────────┐ ┌───────────────────────────────────┐
│ Ollama Vision LLM │ │ LLM Gateway │
│ Port 11434 │ │ Port 8002 │
│ ┌─────────────────────────┐ │ │ ┌─────────────────────────────┐ │
│ │ qwen2.5vl:32b │ │ │ │ qwen2.5:14b │ │
│ │ (Bild → Vokabeln) │ │ │ │ (OCR-Text → strukturiert) │ │
│ └─────────────────────────┘ │ │ └─────────────────────────────┘ │
└───────────────────────────────┘ └───────────────────────────────────┘
```
---
## 2. Komponenten
### 2.1 Frontend (studio-v2)
**Datei:** `/studio-v2/app/vocab-worksheet/page.tsx`
| Aspekt | Details |
|--------|---------|
| Framework | Next.js 16.1.4 mit React 19.0.0 |
| Styling | Tailwind CSS 3.4.17 |
| Sprache | TypeScript 5.7.0 |
| State | React Hooks (useState, useRef, useEffect) |
**Tab-basierter Workflow:**
1. **Upload** - Session benennen, Datei auswaehlen (Bild/PDF)
2. **Pages** - Bei PDFs: Seiten mit Thumbnails auswaehlen
3. **Vocabulary** - Extrahierte Vokabeln pruefen/bearbeiten
4. **Worksheet** - Arbeitsblatt-Typ und Format waehlen
5. **Export** - PDF herunterladen
**Datenstrukturen:**
```typescript
interface VocabularyEntry {
id: string
english: string
german: string
example_sentence?: string
word_type?: string
source_page?: number
}
interface Session {
id: string
name: string
status: 'pending' | 'processing' | 'extracted' | 'completed'
vocabulary_count: number
}
type WorksheetType = 'en_to_de' | 'de_to_en' | 'copy' | 'gap_fill'
```
### 2.2 Backend API
**Datei:** `/klausur-service/backend/vocab_worksheet_api.py`
| Aspekt | Details |
|--------|---------|
| Framework | FastAPI (async) |
| Router-Prefix | `/api/v1/vocab` |
| Storage | In-Memory (Dict) + Filesystem |
**Endpoints:**
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| POST | `/sessions` | Session erstellen |
| GET | `/sessions` | Sessions auflisten |
| GET | `/sessions/{id}` | Session-Details |
| DELETE | `/sessions/{id}` | Session loeschen |
| POST | `/sessions/{id}/upload` | Bild/PDF hochladen |
| POST | `/sessions/{id}/upload-pdf-info` | PDF-Info abrufen |
| GET | `/sessions/{id}/pdf-thumbnail/{page}` | Seiten-Thumbnail |
| POST | `/sessions/{id}/process-single-page/{page}` | Einzelne Seite verarbeiten |
| GET | `/sessions/{id}/vocabulary` | Vokabeln abrufen |
| PUT | `/sessions/{id}/vocabulary` | Vokabeln aktualisieren |
| POST | `/sessions/{id}/generate` | Arbeitsblatt generieren |
| GET | `/worksheets/{id}/pdf` | Arbeitsblatt-PDF |
| GET | `/worksheets/{id}/solution` | Loesungs-PDF |
### 2.3 Vokabel-Extraktion
**Zwei Modi verfuegbar:**
#### A. Vision LLM (Standard)
```python
OLLAMA_URL = "http://host.docker.internal:11434"
VISION_MODEL = "qwen2.5vl:32b"
```
- Bild wird Base64-kodiert an Ollama gesendet
- Prompt in Deutsch fuer bessere Erkennung
- Timeout: 5 Minuten pro Seite
- Confidence: ~85%
#### B. Hybrid OCR + LLM (Optional)
**Datei:** `/klausur-service/backend/hybrid_vocab_extractor.py`
```
Bild → PaddleOCR → Text-Regionen → LLM Gateway → Strukturiertes JSON
```
- PaddleOCR 3.x fuer Text-Erkennung
- Automatische Spalten-Erkennung (2 oder 3 Spalten)
- qwen2.5:14b fuer Strukturierung
- ~4x schneller als Vision LLM
### 2.4 PDF-Verarbeitung
| Aufgabe | Bibliothek |
|---------|------------|
| PDF → PNG | PyMuPDF (fitz) |
| Thumbnails | PyMuPDF mit Zoom 0.5 |
| OCR-Bilder | PyMuPDF mit Zoom 2.0 |
| PDF-Generierung | WeasyPrint |
---
## 3. Datenfluss
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Upload │───►│ OCR/ │───►│ Edit │───►│ Export │
│ PDF │ │ Extract │ │ Vocab │ │ PDF │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
/upload /process- /vocabulary /generate
single-page
```
**Session-Status-Workflow:**
```
PENDING → PROCESSING → EXTRACTED → COMPLETED
│ │ │ │
Upload Extraktion Bereit zum Worksheet
erfolgt laeuft Bearbeiten generiert
```
---
## 4. Arbeitsblatt-Typen
| Typ | Beschreibung |
|-----|--------------|
| `en_to_de` | Englisch → Deutsch uebersetzen |
| `de_to_en` | Deutsch → Englisch uebersetzen |
| `copy` | Woerter mehrfach abschreiben |
| `gap_fill` | Lueckentext mit Beispielsaetzen |
**Optionen:**
- Zeilenhoehe: normal / large / extra-large
- Loesungen: ja / nein
- Wiederholungen (bei Copy): 1-5
---
## 5. Datenschutz (DSGVO)
| Aspekt | Umsetzung |
|--------|-----------|
| Verarbeitung | 100% lokal (Mac Mini) |
| Externe APIs | Keine |
| LLM | Ollama (lokal) |
| Speicherung | Lokales Filesystem |
| Datentransfer | Nur innerhalb LAN |
**Keine Daten werden an externe Server gesendet.**
---
## 6. Konfiguration
**Umgebungsvariablen:**
```bash
# Ollama Vision LLM
OLLAMA_URL=http://host.docker.internal:11434
OLLAMA_VISION_MODEL=qwen2.5vl:32b
# LLM Gateway (Hybrid Mode)
LLM_GATEWAY_URL=http://host.docker.internal:8002
LLM_MODEL=qwen2.5:14b
# Storage
VOCAB_STORAGE_PATH=/app/vocab-worksheets
```
---
## 7. Abhaengigkeiten
### Backend (Python)
| Paket | Version | Zweck |
|-------|---------|-------|
| FastAPI | 0.123.9 | Web Framework |
| PyMuPDF | 1.25.4 | PDF-Verarbeitung |
| WeasyPrint | 66.0 | PDF-Generierung |
| Pillow | 11.3.0 | Bildverarbeitung |
| httpx | 0.28.1 | Async HTTP Client |
| PaddleOCR | 3.x | OCR (optional) |
### Frontend (Node.js)
| Paket | Version | Zweck |
|-------|---------|-------|
| Next.js | 16.1.4 | Framework |
| React | 19.0.0 | UI Library |
| Tailwind CSS | 3.4.17 | Styling |
| TypeScript | 5.7.0 | Type Safety |
---
## 8. Deployment
**Docker-Container:**
- `klausur-service` (Port 8086) - Backend API
- `studio-v2` (Port 3001) - Frontend
**URLs:**
- Frontend: `http://macmini:3001/vocab-worksheet`
- API: `http://macmini:8086/api/v1/vocab/`
---
## 9. Erweiterungsmoeglichkeiten
| Feature | Status |
|---------|--------|
| Weitere Sprachen (FR, ES) | Geplant |
| Datenbank-Persistenz | Geplant |
| Batch-Verarbeitung | Geplant |
| Woerterbuch-Integration | Idee |
| Audio-Ausspracheuebungen | Idee |
---
## 10. Verwandte Dokumentation
- [BYOEH-Architecture.md](./BYOEH-Architecture.md)
- [OCR-Labeling-Spec.md](./OCR-Labeling-Spec.md)
- [DSGVO-Audit-OCR-Labeling.md](./DSGVO-Audit-OCR-Labeling.md)

View File

@@ -0,0 +1,425 @@
# Vokabel-Arbeitsblatt Generator - Entwicklerhandbuch
**Version:** 1.0.0
**Datum:** 2026-01-23
---
## 1. Schnellstart
### 1.1 Lokale Entwicklung
```bash
# Backend starten (klausur-service)
cd /Users/benjaminadmin/Projekte/breakpilot-pwa/klausur-service/backend
source venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8086 --reload
# Frontend starten (studio-v2)
cd /Users/benjaminadmin/Projekte/breakpilot-pwa/studio-v2
npm run dev
```
### 1.2 URLs
| Umgebung | Frontend | Backend API |
|----------|----------|-------------|
| Lokal | http://localhost:3001/vocab-worksheet | http://localhost:8086/api/v1/vocab/ |
| Mac Mini | http://macmini:3001/vocab-worksheet | http://macmini:8086/api/v1/vocab/ |
---
## 2. Projektstruktur
```
breakpilot-pwa/
├── klausur-service/
│ ├── backend/
│ │ ├── main.py # FastAPI App (inkl. Vocab-Router)
│ │ ├── vocab_worksheet_api.py # Vocab-Worksheet Endpoints
│ │ ├── hybrid_vocab_extractor.py # PaddleOCR + LLM Pipeline
│ │ └── tests/
│ │ └── test_vocab_worksheet.py # Unit Tests
│ └── docs/
│ ├── Vocab-Worksheet-Architecture.md
│ └── Vocab-Worksheet-Developer-Guide.md
└── studio-v2/
└── app/
└── vocab-worksheet/
└── page.tsx # Frontend (React/Next.js)
```
---
## 3. Backend API
### 3.1 Endpoints-Uebersicht
```
POST /api/v1/vocab/sessions # Session erstellen
GET /api/v1/vocab/sessions # Sessions auflisten
GET /api/v1/vocab/sessions/{id} # Session abrufen
DELETE /api/v1/vocab/sessions/{id} # Session loeschen
POST /api/v1/vocab/sessions/{id}/upload # Bild/PDF hochladen
POST /api/v1/vocab/sessions/{id}/upload-pdf-info # PDF-Info abrufen
GET /api/v1/vocab/sessions/{id}/pdf-thumbnail/{p} # Seiten-Thumbnail
POST /api/v1/vocab/sessions/{id}/process-single-page/{p} # Seite verarbeiten
GET /api/v1/vocab/sessions/{id}/vocabulary # Vokabeln abrufen
PUT /api/v1/vocab/sessions/{id}/vocabulary # Vokabeln aktualisieren
POST /api/v1/vocab/sessions/{id}/generate # Arbeitsblatt generieren
GET /api/v1/vocab/worksheets/{id}/pdf # PDF herunterladen
GET /api/v1/vocab/worksheets/{id}/solution # Loesungs-PDF
```
### 3.2 Session erstellen
```bash
curl -X POST http://localhost:8086/api/v1/vocab/sessions \
-H "Content-Type: application/json" \
-d '{
"name": "Englisch Klasse 7 - Unit 3",
"description": "Vokabeln aus Green Line",
"source_language": "en",
"target_language": "de"
}'
```
**Response:**
```json
{
"id": "15dce1f4-f587-4b80-8c3d-62b20e7b845c",
"name": "Englisch Klasse 7 - Unit 3",
"status": "pending",
"vocabulary_count": 0,
"created_at": "2026-01-23T10:00:00Z"
}
```
### 3.3 Bild hochladen
```bash
curl -X POST http://localhost:8086/api/v1/vocab/sessions/{session_id}/upload \
-F "file=@vokabeln.png"
```
### 3.4 PDF verarbeiten
```bash
# 1. PDF hochladen und Info abrufen
curl -X POST http://localhost:8086/api/v1/vocab/sessions/{id}/upload-pdf-info \
-F "file=@schulbuch.pdf"
# Response: {"session_id": "...", "page_count": 5}
# 2. Einzelne Seiten verarbeiten (empfohlen)
curl -X POST http://localhost:8086/api/v1/vocab/sessions/{id}/process-single-page/0
curl -X POST http://localhost:8086/api/v1/vocab/sessions/{id}/process-single-page/1
```
### 3.5 Vokabeln aktualisieren
```bash
curl -X PUT http://localhost:8086/api/v1/vocab/sessions/{id}/vocabulary \
-H "Content-Type: application/json" \
-d '{
"vocabulary": [
{
"id": "uuid-1",
"english": "achieve",
"german": "erreichen",
"example_sentence": "She achieved her goals."
}
]
}'
```
### 3.6 Arbeitsblatt generieren
```bash
curl -X POST http://localhost:8086/api/v1/vocab/sessions/{id}/generate \
-H "Content-Type: application/json" \
-d '{
"worksheet_types": ["en_to_de", "de_to_en"],
"include_solutions": true,
"line_height": "large"
}'
```
---
## 4. Frontend-Entwicklung
### 4.1 Komponenten-Struktur
Die gesamte UI ist in einer Datei (`page.tsx`) organisiert:
```typescript
// Hauptkomponente
export default function VocabWorksheetPage() {
const [activeTab, setActiveTab] = useState<TabType>('upload')
const [sessions, setSessions] = useState<Session[]>([])
const [currentSession, setCurrentSession] = useState<Session | null>(null)
const [vocabulary, setVocabulary] = useState<VocabularyEntry[]>([])
// ...
}
// Tabs
type TabType = 'upload' | 'pages' | 'vocabulary' | 'worksheet' | 'export'
```
### 4.2 API-Aufrufe
```typescript
// API Base URL automatisch ermitteln
const getApiBase = () => {
if (typeof window === 'undefined') return 'http://localhost:8086'
const host = window.location.hostname
return `http://${host}:8086`
}
// Session erstellen
const createSession = async (name: string) => {
const response = await fetch(`${getApiBase()}/api/v1/vocab/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
})
return response.json()
}
```
### 4.3 Styling
Tailwind CSS mit Dark/Light Theme:
```typescript
// Theme-aware Klassen
className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
// Gradient-Buttons
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
```
---
## 5. Vokabel-Extraktion
### 5.1 Vision LLM Modus (Standard)
```python
# vocab_worksheet_api.py
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://host.docker.internal:11434")
VISION_MODEL = os.getenv("OLLAMA_VISION_MODEL", "qwen2.5vl:32b")
async def extract_vocabulary_from_image(image_data: bytes, filename: str):
# Base64-kodiertes Bild an Ollama senden
image_base64 = base64.b64encode(image_data).decode("utf-8")
payload = {
"model": VISION_MODEL,
"messages": [{
"role": "user",
"content": VOCAB_EXTRACTION_PROMPT,
"images": [image_base64]
}],
"stream": False
}
response = await client.post(f"{OLLAMA_URL}/api/chat", json=payload)
# Parse JSON response...
```
### 5.2 Hybrid OCR + LLM Modus (Optional)
```python
# hybrid_vocab_extractor.py
async def extract_vocabulary_hybrid(image_bytes: bytes, page_number: int):
# 1. PaddleOCR fuer Text-Erkennung
regions, raw_text = run_paddle_ocr(image_bytes)
# 2. Text fuer LLM formatieren
formatted_text = format_ocr_for_llm(regions)
# 3. LLM strukturiert die Daten
vocabulary = await structure_vocabulary_with_llm(formatted_text)
return vocabulary, confidence, error
```
### 5.3 Prompt Engineering
Der Extraktions-Prompt ist auf Deutsch fuer bessere Ergebnisse:
```python
VOCAB_EXTRACTION_PROMPT = """Analysiere dieses Bild einer Vokabelliste aus einem Schulbuch.
AUFGABE: Extrahiere alle Vokabeleintraege in folgendem JSON-Format:
{
"vocabulary": [
{
"english": "to improve",
"german": "verbessern",
"example": "I want to improve my English."
}
]
}
REGELN:
1. Erkenne das typische 3-Spalten-Layout: Englisch | Deutsch | Beispielsatz
2. Behalte die exakte Schreibweise bei
3. Bei fehlenden Beispielsaetzen: "example": null
4. Ignoriere Seitenzahlen, Ueberschriften
5. Gib NUR valides JSON zurueck
"""
```
---
## 6. Tests
### 6.1 Tests ausfuehren
```bash
cd /Users/benjaminadmin/Projekte/breakpilot-pwa/klausur-service/backend
source venv/bin/activate
# Alle Tests
pytest tests/test_vocab_worksheet.py -v
# Mit Coverage
pytest tests/test_vocab_worksheet.py --cov=vocab_worksheet_api --cov-report=html
# Einzelne Testklasse
pytest tests/test_vocab_worksheet.py::TestSessionCRUD -v
```
### 6.2 Test-Kategorien
| Klasse | Beschreibung |
|--------|--------------|
| `TestSessionCRUD` | Session erstellen, lesen, loeschen |
| `TestVocabulary` | Vokabeln abrufen, aktualisieren |
| `TestWorksheetGeneration` | Arbeitsblatt-Generierung |
| `TestJSONParsing` | LLM-Response parsing |
| `TestFileUpload` | Bild/PDF-Upload |
| `TestSessionStatus` | Status-Workflow |
| `TestEdgeCases` | Randfaelle |
---
## 7. Deployment
### 7.1 Docker Build
```bash
# Klausur-Service neu bauen
docker compose build klausur-service
# Studio-v2 neu bauen
docker compose build studio-v2
```
### 7.2 Sync zum Mac Mini
```bash
# Source-Files synchronisieren
rsync -avz --exclude 'node_modules' --exclude '.next' --exclude '__pycache__' \
/Users/benjaminadmin/Projekte/breakpilot-pwa/ \
macmini:/Users/benjaminadmin/Projekte/breakpilot-pwa/
# Container neu starten
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-pwa && docker compose up -d"
```
---
## 8. Troubleshooting
### 8.1 Haeufige Probleme
| Problem | Loesung |
|---------|---------|
| Ollama nicht erreichbar | `docker exec -it ollama ollama list` pruefen |
| PDF-Konvertierung schlaegt fehl | PyMuPDF installiert? `pip install PyMuPDF` |
| Vision LLM Timeout | Timeout auf 300s erhoehen |
| Leere Vokabel-Liste | Bild-Qualitaet pruefen, anderen LLM-Modus testen |
### 8.2 Logs pruefen
```bash
# Backend-Logs
docker logs klausur-service -f --tail 100
# Ollama-Logs
docker logs ollama -f --tail 100
```
### 8.3 Debug-Modus
```python
# In vocab_worksheet_api.py
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# Zeigt detaillierte OCR/LLM-Ausgaben
```
---
## 9. Erweiterung
### 9.1 Neue Sprache hinzufuegen
1. `source_language` und `target_language` in Session-Model erweitern
2. Prompt anpassen fuer neue Sprachkombination
3. Frontend-Dropdown erweitern
### 9.2 Neuer Arbeitsblatt-Typ
1. `WorksheetType` Enum erweitern:
```python
class WorksheetType(str, Enum):
# ...
CROSSWORD = "crossword" # Neu
```
2. `generate_worksheet_html()` erweitern
3. Frontend-Checkbox hinzufuegen
### 9.3 Datenbank-Persistenz
Aktuell: In-Memory (`_sessions` Dict)
Fuer Produktion PostgreSQL hinzufuegen:
```python
# models.py
class VocabSession(Base):
__tablename__ = "vocab_sessions"
id = Column(UUID, primary_key=True)
name = Column(String)
status = Column(String)
vocabulary = Column(JSON)
# ...
```
---
## 10. API-Referenz
Vollstaendige OpenAPI-Dokumentation verfuegbar unter:
- **Swagger UI:** http://macmini:8086/docs
- **ReDoc:** http://macmini:8086/redoc
Filter nach Tag `Vocabulary Worksheets` fuer alle Vocab-Endpoints.

View File

@@ -0,0 +1,410 @@
# Visual Worksheet Editor - Architecture Documentation
**Version:** 1.0
**Datum:** 2026-01-23
**Status:** Implementiert
## 1. Übersicht
Der Visual Worksheet Editor ist ein Canvas-basierter Editor für die Erstellung und Bearbeitung von Arbeitsblättern. Er ermöglicht Lehrern, eingescannte Arbeitsblätter originalgetreu zu rekonstruieren oder neue Arbeitsblätter visuell zu gestalten.
### 1.1 Hauptfunktionen
- **Canvas-basiertes Editieren** mit Fabric.js
- **Freie Positionierung** von Text, Bildern und Formen
- **Typografie-Steuerung** (Schriftarten, Größen, Stile)
- **Bilder & Grafiken** hochladen und einfügen
- **KI-generierte Bilder** via Ollama/Stable Diffusion
- **PDF/Bild-Export** für Druck und digitale Nutzung
- **Mehrseitige Dokumente** mit Seitennavigation
### 1.2 Technologie-Stack
| Komponente | Technologie | Lizenz |
|------------|-------------|--------|
| Canvas-Bibliothek | Fabric.js 6.x | MIT |
| PDF-Export | pdf-lib 1.17.x | MIT |
| Frontend | Next.js / React | MIT |
| Backend API | FastAPI | MIT |
| KI-Bilder | Ollama + Stable Diffusion | Apache 2.0 / MIT |
## 2. Architektur
```
┌──────────────────────────────────────────────────────────────────────┐
│ Frontend (studio-v2 / Next.js) │
│ /studio-v2/app/worksheet-editor/page.tsx │
│ │
│ ┌─────────────┐ ┌────────────────────────────┐ ┌────────────────┐ │
│ │ Toolbar │ │ Fabric.js Canvas │ │ Properties │ │
│ │ (Links) │ │ (Mitte - 60%) │ │ Panel │ │
│ │ │ │ │ │ (Rechts) │ │
│ │ - Select │ │ ┌──────────────────────┐ │ │ │ │
│ │ - Text │ │ │ │ │ │ - Schriftart │ │
│ │ - Formen │ │ │ A4 Arbeitsfläche │ │ │ - Größe │ │
│ │ - Bilder │ │ │ mit Grid │ │ │ - Farbe │ │
│ │ - KI-Bild │ │ │ │ │ │ - Position │ │
│ │ - Tabelle │ │ └──────────────────────┘ │ │ - Ebene │ │
│ └─────────────┘ └────────────────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Seiten-Navigation | Zoom | Grid | Export PDF │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│ klausur-service (FastAPI - Port 8086) │
│ POST /api/v1/worksheet/ai-image → Bild via Ollama generieren │
│ POST /api/v1/worksheet/save → Worksheet speichern │
│ GET /api/v1/worksheet/{id} → Worksheet laden │
│ POST /api/v1/worksheet/export-pdf → PDF generieren │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│ Ollama (Port 11434) │
│ Model: stable-diffusion oder kompatibles Text-to-Image Modell │
│ Text-to-Image für KI-generierte Grafiken │
└──────────────────────────────────────────────────────────────────────┘
```
## 3. Dateistruktur
### 3.1 Frontend (studio-v2)
```
/studio-v2/
├── app/
│ └── worksheet-editor/
│ ├── page.tsx # Haupt-Editor-Seite
│ └── types.ts # TypeScript Interfaces
├── components/
│ └── worksheet-editor/
│ ├── index.ts # Exports
│ ├── FabricCanvas.tsx # Fabric.js Canvas Wrapper
│ ├── EditorToolbar.tsx # Werkzeugleiste (links)
│ ├── PropertiesPanel.tsx # Eigenschaften-Panel (rechts)
│ ├── AIImageGenerator.tsx # KI-Bild Generator Modal
│ ├── CanvasControls.tsx # Zoom, Grid, Seiten
│ ├── ExportPanel.tsx # PDF/Bild Export
│ └── PageNavigator.tsx # Mehrseitige Dokumente
├── lib/
│ └── worksheet-editor/
│ ├── index.ts # Exports
│ └── WorksheetContext.tsx # State Management
```
### 3.2 Backend (klausur-service)
```
/klausur-service/backend/
├── worksheet_editor_api.py # API Endpoints
└── main.py # Router-Registrierung
```
## 4. API Endpoints
### 4.1 KI-Bild generieren
```http
POST /api/v1/worksheet/ai-image
Content-Type: application/json
{
"prompt": "Ein freundlicher Cartoon-Hund der ein Buch liest",
"style": "cartoon",
"width": 512,
"height": 512
}
```
**Response:**
```json
{
"image_base64": "data:image/png;base64,...",
"prompt_used": "...",
"error": null
}
```
**Styles:**
- `realistic` - Fotorealistisch
- `cartoon` - Cartoon/Comic
- `sketch` - Handgezeichnete Skizze
- `clipart` - Einfache Clipart-Grafiken
- `educational` - Bildungs-Illustrationen
### 4.2 Worksheet speichern
```http
POST /api/v1/worksheet/save
Content-Type: application/json
{
"id": "optional-existing-id",
"title": "Englisch Vokabeln Unit 3",
"pages": [
{ "id": "page_1", "index": 0, "canvasJSON": "{...}" }
],
"pageFormat": {
"width": 210,
"height": 297,
"orientation": "portrait"
}
}
```
### 4.3 Worksheet laden
```http
GET /api/v1/worksheet/{id}
```
### 4.4 PDF exportieren
```http
POST /api/v1/worksheet/{id}/export-pdf
```
**Response:** PDF-Datei als Download
### 4.5 Worksheets auflisten
```http
GET /api/v1/worksheet/list/all
```
## 5. Komponenten
### 5.1 FabricCanvas
Die Kernkomponente für den Canvas-Bereich:
- **A4-Format**: 794 x 1123 Pixel (96 DPI)
- **Grid-Overlay**: Optionales Raster mit Snap-Funktion
- **Zoom/Pan**: Mausrad und Controls
- **Selection**: Einzel- und Mehrfachauswahl
- **Keyboard Shortcuts**: Del, Ctrl+C/V/Z/D
### 5.2 EditorToolbar
Werkzeuge für die Bearbeitung:
| Icon | Tool | Beschreibung |
|------|------|--------------|
| 🖱️ | Select | Elemente auswählen/verschieben |
| T | Text | Text hinzufügen (IText) |
| ▭ | Rechteck | Rechteck zeichnen |
| ○ | Kreis | Kreis/Ellipse zeichnen |
| ― | Linie | Linie zeichnen |
| → | Pfeil | Pfeil zeichnen |
| 🖼️ | Bild | Bild hochladen |
| ✨ | KI-Bild | Bild mit KI generieren |
| ⊞ | Tabelle | Tabelle einfügen |
### 5.3 PropertiesPanel
Eigenschaften-Editor für ausgewählte Objekte:
**Text-Eigenschaften:**
- Schriftart (Arial, Times, Georgia, OpenDyslexic, Schulschrift)
- Schriftgröße (8-120pt)
- Schriftstil (Normal, Fett, Kursiv)
- Zeilenhöhe, Zeichenabstand
- Textausrichtung
- Textfarbe
**Form-Eigenschaften:**
- Füllfarbe
- Rahmenfarbe und -stärke
- Eckenradius
**Allgemein:**
- Deckkraft
- Löschen-Button
### 5.4 WorksheetContext
React Context für globalen State:
```typescript
interface WorksheetContextType {
canvas: Canvas | null
document: WorksheetDocument | null
activeTool: EditorTool
selectedObjects: FabricObject[]
zoom: number
showGrid: boolean
snapToGrid: boolean
currentPageIndex: number
canUndo: boolean
canRedo: boolean
isDirty: boolean
// ... Methoden
}
```
## 6. Datenmodelle
### 6.1 WorksheetDocument
```typescript
interface WorksheetDocument {
id: string
title: string
description?: string
pages: WorksheetPage[]
pageFormat: PageFormat
createdAt: string
updatedAt: string
}
```
### 6.2 WorksheetPage
```typescript
interface WorksheetPage {
id: string
index: number
canvasJSON: string // Serialisierter Fabric.js Canvas
thumbnail?: string
}
```
### 6.3 PageFormat
```typescript
interface PageFormat {
width: number // in mm (Standard: 210)
height: number // in mm (Standard: 297)
orientation: 'portrait' | 'landscape'
margins: { top, right, bottom, left: number }
}
```
## 7. Features
### 7.1 Undo/Redo
- History-Stack mit max. 50 Einträgen
- Automatische Speicherung bei jeder Änderung
- Keyboard: Ctrl+Z (Undo), Ctrl+Y (Redo)
### 7.2 Grid & Snap
- Konfigurierbares Raster (5mm, 10mm, 15mm, 20mm)
- Snap-to-Grid beim Verschieben
- Ein-/Ausblendbar
### 7.3 Export
- **PDF**: Mehrseitig, A4-Format
- **PNG**: Hochauflösend (2x Multiplier)
- **JPG**: Mit Qualitätseinstellung
### 7.4 Speicherung
- **Backend**: REST API mit JSON-Persistierung
- **Fallback**: localStorage bei Offline-Betrieb
## 8. KI-Bildgenerierung
### 8.1 Ollama Integration
Der Editor nutzt Ollama für die KI-Bildgenerierung:
```python
OLLAMA_URL = "http://host.docker.internal:11434"
```
### 8.2 Placeholder-System
Falls Ollama nicht verfügbar ist, wird ein Placeholder-Bild generiert:
- Farbcodiert nach Stil
- Prompt-Text als Beschreibung
- "KI-Bild (Platzhalter)"-Badge
### 8.3 Stil-Prompts
Jeder Stil fügt automatisch Modifikatoren zum Prompt hinzu:
```python
STYLE_PROMPTS = {
"realistic": "photorealistic, high detail",
"cartoon": "cartoon style, colorful, child-friendly",
"sketch": "pencil sketch, hand-drawn",
"clipart": "clipart style, flat design",
"educational": "educational illustration, textbook style"
}
```
## 9. Glassmorphism Design
Der Editor folgt dem Glassmorphism-Design des Studio v2:
```typescript
// Dark Theme
'backdrop-blur-xl bg-white/10 border border-white/20'
// Light Theme
'backdrop-blur-xl bg-white/70 border border-black/10 shadow-xl'
```
## 10. Internationalisierung
Unterstützte Sprachen:
- 🇩🇪 Deutsch
- 🇬🇧 English
- 🇹🇷 Türkçe
- 🇸🇦 العربية (RTL)
- 🇷🇺 Русский
- 🇺🇦 Українська
- 🇵🇱 Polski
Translation Key: `nav_worksheet_editor`
## 11. Sicherheit
### 11.1 Bild-Upload
- Nur Bildformate (image/*)
- Client-seitige Validierung
- Base64-Konvertierung
### 11.2 CORS
Aktiviert für lokale Entwicklung und Docker-Umgebung.
## 12. Deployment
### 12.1 Frontend
```bash
cd studio-v2
npm install
npm run dev # Port 3001
```
### 12.2 Backend
Der klausur-service läuft auf Port 8086:
```bash
cd klausur-service/backend
python main.py
```
### 12.3 Docker
Der Service ist Teil des docker-compose.yml.
## 13. Zukünftige Erweiterungen
- [ ] Tabellen-Tool mit Zellbearbeitung
- [ ] Vorlagen-Bibliothek
- [ ] Kollaboratives Editieren
- [ ] Drag & Drop aus Dokumentenbibliothek
- [ ] Integration mit Vocab-Worksheet

View File

@@ -0,0 +1,480 @@
# Visual Worksheet Editor - Developer Guide
**Version:** 1.0
**Datum:** 2026-01-23
## 1. Schnellstart
### 1.1 Dependencies installieren
```bash
cd studio-v2
npm install fabric@^6.0.0 pdf-lib@^1.17.1
```
### 1.2 Entwicklungsserver starten
```bash
# Frontend (Port 3001)
cd studio-v2
npm run dev
# Backend (Port 8086)
cd klausur-service/backend
source venv/bin/activate
python main.py
```
### 1.3 Editor öffnen
```
http://localhost:3001/worksheet-editor
```
## 2. Komponenten-Entwicklung
### 2.1 Neues Werkzeug hinzufügen
1. **Tool-Typ definieren** in `types.ts`:
```typescript
export type EditorTool =
| 'select'
| 'text'
// ... existierende Tools
| 'neues-tool' // NEU
```
2. **Button hinzufügen** in `EditorToolbar.tsx`:
```tsx
<ToolButton
tool="neues-tool"
isActive={activeTool === 'neues-tool'}
onClick={() => handleToolClick('neues-tool')}
isDark={isDark}
label="Neues Tool"
icon={<svg>...</svg>}
/>
```
3. **Handler implementieren** in `FabricCanvas.tsx`:
```typescript
case 'neues-tool': {
// Canvas-Objekt erstellen
const obj = new fabric.CustomObject({...})
fabricCanvas.add(obj)
fabricCanvas.setActiveObject(obj)
setActiveTool('select')
break
}
```
### 2.2 Eigenschaften-Panel erweitern
In `PropertiesPanel.tsx` neue Eigenschaften für einen Objekttyp hinzufügen:
```tsx
{isMyType && (
<div className="space-y-4">
<div>
<label className={`block text-sm font-medium mb-2 ${labelStyle}`}>
Neue Eigenschaft
</label>
<input
type="text"
value={myProperty}
onChange={(e) => {
setMyProperty(e.target.value)
updateProperty('myProperty', e.target.value)
}}
className={`w-full px-3 py-2 rounded-xl border text-sm ${inputStyle}`}
/>
</div>
</div>
)}
```
## 3. Context API
### 3.1 State abrufen
```tsx
import { useWorksheet } from '@/lib/worksheet-editor/WorksheetContext'
function MyComponent() {
const {
canvas,
activeTool,
setActiveTool,
selectedObjects,
zoom,
setZoom
} = useWorksheet()
// Verwenden...
}
```
### 3.2 Canvas-Operationen
```typescript
// Objekt hinzufügen
canvas.add(newObject)
canvas.setActiveObject(newObject)
canvas.renderAll()
// Objekt entfernen
canvas.remove(selectedObject)
// Alle Objekte abrufen (ohne Grid)
const objects = canvas.getObjects().filter(obj => !obj.isGrid)
// Canvas exportieren
const json = canvas.toJSON()
const dataUrl = canvas.toDataURL({ format: 'png', multiplier: 2 })
// Canvas laden
canvas.loadFromJSON(jsonData, () => {
canvas.renderAll()
})
```
## 4. API-Integration
### 4.1 Worksheet speichern
```typescript
const saveWorksheet = async () => {
const host = window.location.hostname
const apiBase = `http://${host}:8086`
const response = await fetch(`${apiBase}/api/v1/worksheet/save`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: worksheetId,
title: 'Mein Arbeitsblatt',
pages: [{
id: 'page_1',
index: 0,
canvasJSON: JSON.stringify(canvas.toJSON())
}]
})
})
const result = await response.json()
console.log('Gespeichert:', result.id)
}
```
### 4.2 KI-Bild generieren
```typescript
const generateAIImage = async (prompt: string) => {
const host = window.location.hostname
const apiBase = `http://${host}:8086`
const response = await fetch(`${apiBase}/api/v1/worksheet/ai-image`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt,
style: 'educational',
width: 512,
height: 512
})
})
const { image_base64, error } = await response.json()
if (image_base64) {
// Bild zum Canvas hinzufügen
fabric.Image.fromURL(image_base64, (img) => {
canvas.add(img)
canvas.setActiveObject(img)
canvas.renderAll()
})
}
}
```
## 5. Fabric.js Patterns
### 5.1 Text-Objekt erstellen
```typescript
const text = new fabric.IText('Text eingeben', {
left: 100,
top: 100,
fontFamily: 'Arial',
fontSize: 16,
fill: '#000000',
})
canvas.add(text)
text.enterEditing() // Bearbeitungsmodus
```
### 5.2 Form erstellen
```typescript
// Rechteck
const rect = new fabric.Rect({
left: 100,
top: 100,
width: 200,
height: 100,
fill: 'transparent',
stroke: '#000000',
strokeWidth: 2,
rx: 5, // Eckenradius
ry: 5,
})
// Kreis
const circle = new fabric.Circle({
left: 100,
top: 100,
radius: 50,
fill: '#ff6b6b',
stroke: '#000000',
strokeWidth: 2,
})
// Linie
const line = new fabric.Line([50, 50, 200, 50], {
stroke: '#000000',
strokeWidth: 2,
})
```
### 5.3 Bild laden
```typescript
fabric.Image.fromURL(imageUrl, (img) => {
// Skalierung auf max. Größe
const maxWidth = 400
const maxHeight = 300
const scale = Math.min(maxWidth / img.width, maxHeight / img.height, 1)
img.set({
left: 100,
top: 100,
scaleX: scale,
scaleY: scale,
})
canvas.add(img)
}, { crossOrigin: 'anonymous' })
```
### 5.4 Events
```typescript
// Selection Events
canvas.on('selection:created', (e) => {
const selected = canvas.getActiveObjects()
console.log('Ausgewählt:', selected.length)
})
canvas.on('selection:cleared', () => {
console.log('Auswahl aufgehoben')
})
// Object Events
canvas.on('object:modified', (e) => {
console.log('Objekt geändert:', e.target)
saveToHistory('modified')
})
canvas.on('object:added', (e) => {
console.log('Objekt hinzugefügt:', e.target)
})
// Mouse Events
canvas.on('mouse:down', (e) => {
const pointer = canvas.getPointer(e.e)
console.log('Klick bei:', pointer.x, pointer.y)
})
```
## 6. Testing
### 6.1 Unit Tests für Context
```typescript
// __tests__/worksheet-context.test.tsx
import { renderHook, act } from '@testing-library/react'
import { WorksheetProvider, useWorksheet } from '../WorksheetContext'
describe('useWorksheet', () => {
it('should initialize with default values', () => {
const { result } = renderHook(() => useWorksheet(), {
wrapper: WorksheetProvider,
})
expect(result.current.activeTool).toBe('select')
expect(result.current.zoom).toBe(1)
expect(result.current.showGrid).toBe(true)
})
it('should change tool', () => {
const { result } = renderHook(() => useWorksheet(), {
wrapper: WorksheetProvider,
})
act(() => {
result.current.setActiveTool('text')
})
expect(result.current.activeTool).toBe('text')
})
})
```
### 6.2 API Tests
```python
# tests/test_worksheet_editor_api.py
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_save_worksheet():
response = client.post("/api/v1/worksheet/save", json={
"title": "Test Worksheet",
"pages": [{
"id": "page_1",
"index": 0,
"canvasJSON": "{}"
}]
})
assert response.status_code == 200
assert "id" in response.json()
def test_get_worksheet():
# Erst erstellen
create_response = client.post("/api/v1/worksheet/save", json={
"title": "Test",
"pages": [{"id": "p1", "index": 0, "canvasJSON": "{}"}]
})
worksheet_id = create_response.json()["id"]
# Dann laden
response = client.get(f"/api/v1/worksheet/{worksheet_id}")
assert response.status_code == 200
assert response.json()["title"] == "Test"
def test_ai_image_generation():
response = client.post("/api/v1/worksheet/ai-image", json={
"prompt": "A friendly dog",
"style": "cartoon",
"width": 256,
"height": 256
})
# Kann 200 (Bild) oder 503 (Ollama nicht verfügbar) sein
assert response.status_code in [200, 503]
```
## 7. Styling
### 7.1 Glassmorphism Utilities
```typescript
// Für Theme-aware Styling
const glassCard = isDark
? 'backdrop-blur-xl bg-white/10 border border-white/20'
: 'backdrop-blur-xl bg-white/70 border border-black/10 shadow-xl'
const glassInput = isDark
? 'bg-white/10 border-white/20 text-white placeholder-white/40'
: 'bg-white/50 border-black/10 text-slate-900 placeholder-slate-400'
const labelStyle = isDark ? 'text-white/70' : 'text-slate-600'
```
### 7.2 Button States
```typescript
const buttonStyle = (active: boolean) => isDark
? active
? 'bg-purple-500/30 text-purple-300'
: 'text-white/70 hover:bg-white/10 hover:text-white'
: active
? 'bg-purple-100 text-purple-700'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
```
## 8. Best Practices
### 8.1 Performance
- Grid-Objekte mit `isGrid: true` markieren und vom Export ausschließen
- Canvas-JSON nur bei tatsächlichen Änderungen speichern
- History auf 50 Einträge limitieren
- Bilder mit `multiplier: 2` für Retina-Export
### 8.2 Fehlerbehandlung
```typescript
try {
const response = await fetch(apiUrl)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const data = await response.json()
return data
} catch (error) {
console.error('API Error:', error)
// Fallback auf localStorage
return JSON.parse(localStorage.getItem(key) || '{}')
}
```
### 8.3 Hydration Safety
```typescript
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <LoadingSpinner />
}
```
## 9. Debugging
### 9.1 Canvas-State inspizieren
```typescript
// Im Browser DevTools Console
const canvas = document.querySelector('canvas')?.__fabric
console.log('Objects:', canvas?.getObjects())
console.log('Active:', canvas?.getActiveObject())
console.log('JSON:', canvas?.toJSON())
```
### 9.2 API-Calls überwachen
```bash
# Backend-Logs
tail -f /var/log/klausur-service.log
# Oder im Terminal wo main.py läuft
```
## 10. Deployment Checklist
- [ ] Dependencies in package.json
- [ ] Fabric.js und pdf-lib installiert
- [ ] Backend-Router registriert
- [ ] i18n-Übersetzungen vorhanden
- [ ] Sidebar-Navigation aktualisiert
- [ ] CORS für Produktions-Domain konfiguriert
- [ ] Ollama-URL in Umgebungsvariablen

View File

@@ -0,0 +1,604 @@
ePrivacy-Richtlinie (Richtlinie 2002/58/EG)
Datenschutz in der elektronischen Kommunikation
========================================
GRUNDLAGEN
========================================
Was ist die ePrivacy-Richtlinie?
Die ePrivacy-Richtlinie (Richtlinie 2002/58/EG) ist eine EU-Richtlinie, die spezifische Datenschutzregeln fuer den Bereich der elektronischen Kommunikation festlegt. Sie ergaenzt die DSGVO als "lex specialis" fuer diesen Bereich.
Offizieller Titel: "Richtlinie 2002/58/EG des Europaeischen Parlaments und des Rates vom 12. Juli 2002 ueber die Verarbeitung personenbezogener Daten und den Schutz der Privatsphaere in der elektronischen Kommunikation"
Die Richtlinie wurde mehrfach geaendert:
- 2006 durch Richtlinie 2006/24/EG (Vorratsdatenspeicherung, spaeter aufgehoben)
- 2009 durch Richtlinie 2009/136/EG ("Cookie-Richtlinie")
WICHTIG: Die ePrivacy-Verordnung (ePVO) soll die Richtlinie ersetzen, ist aber Stand 2026 noch nicht in Kraft getreten.
Anwendungsbereich der ePrivacy-Richtlinie
Die ePrivacy-Richtlinie gilt fuer:
1. ANBIETER OEFFENTLICHER KOMMUNIKATIONSDIENSTE
- Telekommunikationsunternehmen
- Internet Service Provider
- E-Mail-Dienste
- Messenger-Dienste (umstritten)
2. BETREIBER VON WEBSITES UND APPS
- Cookies und aehnliche Technologien
- Online-Tracking
- Direktwerbung per E-Mail
3. JEDE VERARBEITUNG VON
- Verkehrsdaten
- Standortdaten
- Kommunikationsinhalten
NICHT anwendbar auf:
- Rein unternehmensinterne Kommunikationssysteme
- Nationale Sicherheit und Strafverfolgung (Ausnahmen)
Verhaeltnis zur DSGVO
Die ePrivacy-Richtlinie steht in einem besonderen Verhaeltnis zur DSGVO:
GRUNDSATZ (Art. 95 DSGVO):
Die DSGVO erlegt Anbietern oeffentlicher Kommunikationsdienste keine zusaetzlichen Pflichten auf, soweit die ePrivacy-Richtlinie dieselbe Zielsetzung verfolgt.
PRAKTISCHE BEDEUTUNG:
1. ePrivacy als "lex specialis"
- Fuer elektronische Kommunikation gelten primaer ePrivacy-Regeln
- DSGVO gilt ergaenzend, wo ePrivacy keine Regelung trifft
2. Cookie-Consent
- Art. 5 Abs. 3 ePrivacy regelt Cookies VORRANGIG
- DSGVO-Einwilligung gilt ZUSAETZLICH fuer personenbezogene Daten
3. Sanktionen
- DSGVO-Bussgelder (bis 20 Mio. / 4% Umsatz) gelten NICHT direkt
- Nationale Umsetzungsgesetze haben eigene Sanktionen
WICHTIG: Bei Cookies ist BEIDES erforderlich:
- ePrivacy-Einwilligung (fuer Zugriff auf Geraet)
- DSGVO-Rechtsgrundlage (fuer Verarbeitung personenbezogener Daten)
========================================
COOKIES UND TRACKING (Art. 5 Abs. 3)
========================================
Cookie-Einwilligungsregel (Art. 5 Abs. 3)
Art. 5 Abs. 3 der ePrivacy-Richtlinie regelt den Zugriff auf Endgeraete:
GRUNDSATZ:
Die Speicherung von Informationen oder der Zugriff auf bereits gespeicherte Informationen im Endgeraet eines Nutzers ist NUR zulaessig, wenn:
1. Der Nutzer VORHER informiert wurde (Transparenz)
2. Der Nutzer seine EINWILLIGUNG gegeben hat (Opt-In)
AUSNAHMEN (KEINE Einwilligung erforderlich):
a) TECHNISCH NOTWENDIGE COOKIES
- Fuer die Uebertragung einer Nachricht erforderlich
- Beispiel: Load Balancer Cookies
b) UNBEDINGT ERFORDERLICHE COOKIES
- Vom Nutzer ausdruecklich gewuenscht
- Fuer einen Dienst, den der Nutzer ausdruecklich angefordert hat
- Beispiele:
* Warenkorb-Cookies
* Login-Session-Cookies
* Spracheinstellungen
* Cookie-Consent-Cookie selbst
WICHTIG: Die Ausnahmen sind ENG auszulegen!
- Analytics-Cookies: KEINE Ausnahme, Einwilligung erforderlich
- Marketing-Cookies: KEINE Ausnahme, Einwilligung erforderlich
- Social Media Plugins: KEINE Ausnahme, Einwilligung erforderlich
Anforderungen an Cookie-Einwilligung
Die Einwilligung nach Art. 5 Abs. 3 ePrivacy muss den DSGVO-Standards entsprechen (Verweis auf Definition in DSGVO):
ANFORDERUNGEN:
1. FREIWILLIG
- Keine Nachteile bei Ablehnung
- Kein "Cookie Wall" (umstritten, nationale Unterschiede)
- Gleichwertige Ablehnungsoption
2. INFORMIERT
- Klare Information VORHER
- Welche Cookies, welcher Zweck
- Wer erhaelt Zugriff (Dritte)
- Speicherdauer
3. AKTIVE HANDLUNG
- Opt-In erforderlich (EuGH Planet49)
- Vorausgewaehlte Checkboxen sind UNGUELTIG
- Weitersurfen ist KEINE Einwilligung
4. SPEZIFISCH
- Getrennte Einwilligung pro Zweck
- "Alle akzeptieren" muss gleichwertig zu "Alle ablehnen" sein
5. WIDERRUFBAR
- Jederzeitiger Widerruf muss moeglich sein
- So einfach wie die Erteilung
CONSENT MANAGEMENT PLATFORM (CMP):
Professionelle Cookie-Banner muessen:
- Alle Kategorien einzeln anwaehlbar machen
- "Ablehnen" gleichwertig prominent anbieten
- Consent dokumentieren (Nachweis)
- Widerruf ermoeglichen
Cookie-Kategorien und Einwilligungspflicht
Uebersicht der Cookie-Kategorien und Einwilligungspflicht:
KATEGORIE 1: TECHNISCH NOTWENDIG (KEINE Einwilligung)
- Session-Cookies fuer Login
- Warenkorb-Cookies
- Load-Balancer-Cookies
- CSRF-Token-Cookies
- Cookie-Consent-Cookie
- Spracheinstellungs-Cookies
- Barrierefreiheits-Cookies
KATEGORIE 2: FUNKTIONAL (Einwilligung ERFORDERLICH)
- Praeferenz-Cookies (Design, Layout)
- Video-Player-Einstellungen
- Chat-Widget-Cookies
- Formular-Autofill-Cookies
KATEGORIE 3: ANALYTICS (Einwilligung ERFORDERLICH)
- Google Analytics
- Matomo/Piwik
- Hotjar, Crazy Egg
- Performance-Messung
SONDERFALL: Analytics ohne Einwilligung (UMSTRITTEN!)
- Matomo ohne Cookies und mit IP-Anonymisierung
- Serverseitige Analytics
- Aggregierte Statistiken
- Nationale Behoerden haben unterschiedliche Meinungen!
KATEGORIE 4: MARKETING/WERBUNG (Einwilligung ERFORDERLICH)
- Retargeting-Cookies
- Google Ads/Meta Pixel
- Affiliate-Tracking
- Cross-Site-Tracking
KATEGORIE 5: SOCIAL MEDIA (Einwilligung ERFORDERLICH)
- Facebook Like Button
- Twitter Widgets
- LinkedIn Plugins
- Embedded Content von Dritten
Szenario: Lokale KI-Anwendung (On-Premises)
Bei einer lokalen KI-Anwendung wie BreakPilot (On-Premises auf Mac Studio):
GRUNDSAETZLICH:
1. KEIN externer Cookie-Zugriff
- Alle Verarbeitung lokal auf Schulserver
- Keine Cookies an Dritte
- Keine Tracking-Pixel
2. TECHNISCH NOTWENDIGE COOKIES
- Session-Cookies fuer Login: KEINE Einwilligung
- CSRF-Schutz: KEINE Einwilligung
- Benutzereinstellungen (Sprache): Grauzone, besser Einwilligung
3. ANALYTICS
- Interne Nutzungsstatistiken (serverseitig): Kein ePrivacy-Problem
- Falls Cookie-basiert: Einwilligung erforderlich
- Empfehlung: Serverseitige Logs statt Cookies
EMPFEHLUNG FUER BREAKPILOT:
- Nur Session-Cookies fuer Login verwenden
- Keine Analytics-Cookies
- Keine Third-Party-Einbindungen
- Einfaches Cookie-Banner mit Hinweis auf notwendige Cookies
- Datenschutzerklaerung mit Cookie-Informationen
VORTEIL:
Durch rein lokale Verarbeitung entfallen die meisten ePrivacy-Probleme automatisch!
========================================
VERKEHRSDATEN (Art. 6)
========================================
Was sind Verkehrsdaten?
Verkehrsdaten (Art. 2 lit. b) sind Daten, die zum Zwecke der Weiterleitung einer Nachricht oder zum Zwecke der Fakturierung verarbeitet werden:
BEISPIELE:
1. Bei TELEFONIE
- Rufnummern (Anrufer und Angerufener)
- Datum und Uhrzeit
- Dauer des Gespraechs
- Art des Dienstes (Sprache, SMS)
2. Bei INTERNET
- IP-Adressen (dynamisch und statisch)
- Zeitpunkt der Verbindung
- Datenvolumen
- Geraetekennungen (MAC-Adresse, IMEI)
3. Bei E-MAIL
- E-Mail-Adressen (Sender, Empfaenger)
- Zeitstempel
- Betreffzeile (umstritten - eher Inhaltsdaten)
ABGRENZUNG:
- INHALTSDATEN: Der eigentliche Inhalt der Kommunikation (strenger Schutz)
- VERKEHRSDATEN: Metadaten der Kommunikation (weniger streng)
- STANDORTDATEN: Geografische Position (gesondert geregelt)
Verarbeitung von Verkehrsdaten (Art. 6)
Die Verarbeitung von Verkehrsdaten ist streng geregelt:
GRUNDSATZ (Art. 6 Abs. 1):
Verkehrsdaten muessen GELOESCHT oder ANONYMISIERT werden, sobald sie fuer die Uebertragung nicht mehr benoetigt werden.
AUSNAHMEN:
1. ABRECHNUNG (Art. 6 Abs. 2)
- Verarbeitung fuer Rechnungsstellung zulaessig
- Nur bis Ende der Frist fuer Rechnungsanfechtung
- In Deutschland: 6 Monate
2. VERMARKTUNG VON DIENSTEN (Art. 6 Abs. 3)
- Nur mit EINWILLIGUNG des Teilnehmers
- Nur fuer Vermarktung von Telekommunikationsdiensten
- Jederzeit widerrufbar
3. MEHRWERTDIENSTE (Art. 6 Abs. 4)
- Mit Einwilligung fuer elektronische Mehrwertdienste
- Nutzer muss informiert werden
- Zeitlicher Rahmen definiert
WICHTIG FUER ANBIETER:
- Technische Vorkehrungen zur automatischen Loeschung
- Dokumentation der Loeschfristen
- Keine Speicherung "auf Vorrat" ohne Rechtsgrundlage
========================================
STANDORTDATEN (Art. 9)
========================================
Verarbeitung von Standortdaten (Art. 9)
Standortdaten, die ueber Verkehrsdaten hinausgehen, unterliegen besonderen Regeln nach Art. 9:
DEFINITION (Art. 2 lit. c):
Daten, die den geografischen Standort des Endgeraets eines Nutzers angeben.
GRUNDSATZ:
Verarbeitung von Standortdaten NUR zulaessig wenn:
- Anonymisiert, ODER
- Mit EINWILLIGUNG des Nutzers
ANFORDERUNGEN BEI EINWILLIGUNG:
1. VOR der Verarbeitung einzuholen
2. Umfang und Dauer der Verarbeitung angeben
3. Zweck der Verarbeitung angeben
4. Ob Daten an Dritte weitergegeben werden
5. Widerruf jederzeit moeglich
PRAKTISCHE ANWENDUNG:
- Navigationsdienste: Einwilligung erforderlich
- Standortbasierte Werbung: Einwilligung erforderlich
- Flottenmanagement: Einwilligung der Fahrer
- Find-my-Device: Einwilligung (oft Teil der Nutzungsbedingungen)
SONDERFALL: Notrufe
Standortdaten duerfen fuer Notrufdienste ohne Einwilligung verarbeitet werden (Art. 10).
========================================
UNERBETENE NACHRICHTEN - SPAM (Art. 13)
========================================
E-Mail-Marketing und Direktwerbung (Art. 13)
Art. 13 regelt die Verwendung elektronischer Kommunikation fuer Direktwerbung:
GRUNDSATZ (Opt-In):
Die Verwendung von E-Mail, SMS, Fax oder automatischen Anrufsystemen fuer Direktwerbung ist NUR zulaessig mit VORHERIGER EINWILLIGUNG.
AUSNAHME - BESTANDSKUNDEN (Art. 13 Abs. 2):
E-Mail-Werbung OHNE Einwilligung ist zulaessig wenn ALLE Bedingungen erfuellt:
1. Der Absender hat die E-Mail-Adresse vom Kunden selbst erhalten
2. Im Zusammenhang mit einem KAUF von Waren/Dienstleistungen
3. Die Werbung bezieht sich auf AEHNLICHE Produkte/Dienstleistungen
4. Der Kunde hatte bei Erhebung die Moeglichkeit zu widersprechen
5. Bei JEDER weiteren Nachricht: Widerspruchsmoeglichkeit (Opt-Out)
WICHTIG: Die Ausnahme ist ENG auszulegen!
- Newsletter: Einwilligung erforderlich (kein "aehnliches Produkt")
- Werbung fuer Dritte: Einwilligung erforderlich
- B2B-Kaltakquise per E-Mail: Umstritten, nationale Unterschiede
TELEFON-WERBUNG:
- Automatische Anrufsysteme: Immer Einwilligung
- Manuelle Anrufe: Nationale Regelung (in D: Einwilligung erforderlich)
ABSENDERKENNUNG:
Die Identitaet des Absenders darf NICHT verschleiert werden!
Eine gueltige Antwortadresse muss vorhanden sein.
Double Opt-In fuer Newsletter
Das Double Opt-In Verfahren ist Best Practice fuer Newsletter-Anmeldungen:
ABLAUF:
1. Nutzer gibt E-Mail-Adresse ein (Single Opt-In)
2. System sendet Bestaetigungs-E-Mail mit Link
3. Nutzer klickt Link zur Bestaetigung (Double Opt-In)
4. Erst dann: Eintrag in Newsletter-Liste
VORTEILE:
- Nachweis der Einwilligung
- Schutz vor Missbrauch (fremde E-Mail-Adressen)
- Reduziert Spam-Beschwerden
- Bessere Zustellraten
ANFORDERUNGEN AN BESTAETIGUNGS-E-MAIL:
- KEINE Werbung enthalten (nur Bestaetigung)
- Klarer Hinweis auf den Zweck
- Bestaetigung muss aktiv erfolgen
- Protokollierung: IP, Zeitstempel, User-Agent
RECHTLICHE EINORDNUNG:
- Die Bestaetigungs-E-Mail selbst ist KEINE Werbung
- Aber: Nur EINE Erinnerung zulaessig
- Nach Nicht-Bestaetigung: Adresse loeschen
SPEICHERDAUER NACHWEIS:
- Einwilligungsnachweis aufbewahren
- Mindestens bis Widerruf + Verjaehrungsfrist
- In Deutschland: 3 Jahre empfohlen
========================================
KOMMUNIKATIONSGEHEIMNIS (Art. 5)
========================================
Vertraulichkeit der Kommunikation (Art. 5 Abs. 1)
Art. 5 Abs. 1 schuetzt die Vertraulichkeit elektronischer Kommunikation:
GRUNDSATZ:
Die Mitgliedstaaten stellen die Vertraulichkeit der mit oeffentlichen Kommunikationsnetzen uebertragenen Nachrichten sicher.
VERBOTEN IST:
- Abhoeren von Nachrichten
- Anzapfen von Leitungen
- Speicherung von Kommunikation durch Unbefugte
- Jede andere Art des Abfangens
AUSNAHMEN:
- Mit Einwilligung der betroffenen Nutzer
- Gesetzlich erlaubte Ueberwachung (Strafverfolgung)
- Technische Speicherung fuer Uebertragungszwecke
PRAKTISCHE BEDEUTUNG:
1. ARBEITGEBER
- Abhoeren von Mitarbeiter-E-Mails problematisch
- Private Nutzung verboten = mehr Spielraum
- Betriebsvereinbarung empfohlen
2. E-MAIL-PROVIDER
- Automatische Spam-Filter: Zulaessig (technisch notwendig)
- Werbefinanzierte Analyse: Einwilligung erforderlich
3. MESSENGER-DIENSTE
- Ende-zu-Ende-Verschluesselung schuetzt Vertraulichkeit
- "Client-Side Scanning" (geplant) hochumstritten
========================================
NATIONALE UMSETZUNG (DEUTSCHLAND)
========================================
Umsetzung in Deutschland: TTDSG
In Deutschland wurde die ePrivacy-Richtlinie durch das Telekommunikation-Telemedien-Datenschutz-Gesetz (TTDSG) umgesetzt.
TTDSG (seit 01.12.2021):
Paragraph 25 TTDSG - COOKIES UND AEHNLICHE TECHNOLOGIEN:
Entspricht Art. 5 Abs. 3 ePrivacy-Richtlinie
- Einwilligung erforderlich fuer nicht-notwendige Cookies
- Ausnahme: Technisch notwendige Speicherung/Zugriff
Paragraph 26 TTDSG - ANERKANNTE DIENSTE (PIMS):
Personal Information Management Services
- Nutzer kann zentral Einstellungen verwalten
- Websites muessen PIMS-Signale beachten
- Noch kaum praktische Umsetzung
WEITERE RELEVANTE GESETZE:
TKG (Telekommunikationsgesetz):
- Paragraph 88 TKG: Fernmeldegeheimnis
- Paragraph 96ff TKG: Verkehrsdaten
UWG (Gesetz gegen unlauteren Wettbewerb):
- Paragraph 7 UWG: Unzumutbare Belaestigungen
- Spam-Verbot, Telefon-Werbung
SANKTIONEN (Paragraph 28 TTDSG):
- Verstoss gegen Paragraph 25: Bussgeld bis 300.000 EUR
- Verstoss gegen Paragraph 26: Bussgeld bis 50.000 EUR
DSK Orientierungshilfe zu Telemedien
Die Datenschutzkonferenz (DSK) hat eine Orientierungshilfe fuer Anbieter von Telemedien veroeffentlicht:
KERNAUSSAGEN:
1. EINWILLIGUNG
- Muss VOR dem Setzen von Cookies eingeholt werden
- Vorausgewaehlte Checkboxen sind unwirksam
- "Nur notwendige akzeptieren" muss gleichwertig sein
2. TECHNISCH NOTWENDIG
- Enger Auslegung
- Session-Cookies: Ja
- Persistente Praeferenz-Cookies: Nein
3. INFORMATIONSPFLICHTEN
- Zweck jedes Cookies angeben
- Speicherdauer angeben
- Dritte benennen
4. DOKUMENTATION
- Einwilligungen dokumentieren
- Mindestens: Zeitstempel, Umfang, Version
5. WIDERRUF
- Jederzeit moeglich
- So einfach wie Erteilung
- Link im Footer oder Cookie-Banner
PRAXISTIPP:
Die DSK-Orientierungshilfe ist nicht rechtlich bindend, wird aber von Aufsichtsbehoerden als Massstab herangezogen.
========================================
EPRIVACY-VERORDNUNG (AUSBLICK)
========================================
ePrivacy-Verordnung (ePVO) - Ausblick
Die ePrivacy-Verordnung (ePVO) soll die Richtlinie 2002/58/EG ersetzen:
STATUS (Stand 2026):
- Kommissionsvorschlag: Januar 2017
- Rat: Kein Konsens erreicht
- Mehrere Kompromissvorschlaege gescheitert
- Inkrafttreten: Weiterhin unklar
GEPLANTE AENDERUNGEN:
1. VERORDNUNG STATT RICHTLINIE
- Direkt anwendbar in allen Mitgliedstaaten
- Keine Umsetzung erforderlich
- Einheitliche Regeln in der EU
2. ERWEITERTER ANWENDUNGSBEREICH
- Auch OTT-Dienste (WhatsApp, Zoom, etc.)
- Auch Maschine-zu-Maschine-Kommunikation (IoT)
3. COOKIE-WALLS
- Verschiedene Positionen
- Evtl. unter bestimmten Bedingungen zulaessig
4. BROWSER-EINSTELLUNGEN
- "Privacy by Default" im Browser
- Zentrale Einwilligungsverwaltung
5. HARMONISIERTE SANKTIONEN
- DSGVO-aehnliche Bussgelder
BIS ZUR EPVO:
Die Richtlinie 2002/58/EG und nationale Umsetzungen bleiben in Kraft!
========================================
PRAKTISCHE CHECKLISTEN
========================================
ePrivacy-Checkliste fuer Websites
COOKIES:
- Cookie-Audit durchgefuehrt (alle Cookies identifiziert)
- Kategorisierung (technisch notwendig vs. einwilligungspflichtig)
- Cookie-Banner implementiert
- Opt-In vor Setzen von nicht-notwendigen Cookies
- "Ablehnen" gleichwertig zu "Akzeptieren"
- Granulare Auswahlmoeglichkeit (Kategorien)
- Speicherdauer dokumentiert
- Einwilligungen protokolliert
- Widerruf jederzeit moeglich
DATENSCHUTZERKLAERUNG:
- Cookie-Informationen enthalten
- Alle Cookies mit Zweck aufgelistet
- Drittanbieter benannt
- Speicherdauer angegeben
E-MAIL-MARKETING:
- Double Opt-In implementiert
- Abmelde-Link in jeder E-Mail
- Einwilligungen dokumentiert
- Bei Bestandskunden: Widerspruchsmoeglichkeit
TRACKING/ANALYTICS:
- Nur mit Einwilligung aktiv
- Oder: Einwilligungsfreie Alternative (z.B. serverseitig)
- IP-Anonymisierung aktiviert
- Datenverarbeitung dokumentiert
ePrivacy-Checkliste fuer lokale Anwendungen
GRUNDSAETZE:
- Alle Daten bleiben lokal
- Keine Cloud-Anbindung fuer Nutzerdaten
- Keine Third-Party-Tracker
COOKIES/LOKALE SPEICHERUNG:
- Nur Session-Cookies fuer Login
- Keine persistenten Tracking-Cookies
- Keine Local Storage fuer Tracking
- Kein Fingerprinting
ANALYTICS:
- Serverseitige Logs statt Cookies
- Keine personenbezogenen Daten in Logs
- Oder: Einwilligung fuer Cookie-Analytics
KOMMUNIKATION:
- Keine automatisierten Werbe-E-Mails ohne Einwilligung
- Abmelde-Moeglichkeit bei Benachrichtigungen
- Push-Benachrichtigungen nur mit Zustimmung
DOKUMENTATION:
- Datenschutzerklaerung aktuell
- Cookie-Informationen (falls Cookies)
- Technische Dokumentation der Datenverarbeitung
VORTEIL LOKALER VERARBEITUNG:
Die meisten ePrivacy-Anforderungen entfallen bei rein lokaler Verarbeitung ohne externe Dienste!
========================================
RECHTLICHE REFERENZEN
========================================
EU-Recht:
- Richtlinie 2002/58/EG (ePrivacy-Richtlinie)
- Richtlinie 2009/136/EG (Cookie-Richtlinie)
- DSGVO Art. 95 (Verhaeltnis zu ePrivacy)
- EuGH Planet49 (C-673/17)
Deutsches Recht:
- TTDSG (Telekommunikation-Telemedien-Datenschutz-Gesetz)
- Paragraph 25 TTDSG (Cookies)
- Paragraph 7 UWG (Unzumutbare Belaestigungen)
- Paragraph 88 TKG (Fernmeldegeheimnis)
Behoerdenpraxis:
- DSK Orientierungshilfe fuer Anbieter von Telemedien (2022)
- DSK Beschluss zu Tracking (2021)
- CNIL Guidelines on Cookies (2020)