From ffa3540d1aac384c5cccfe80ff858e20debe5c43 Mon Sep 17 00:00:00 2001 From: BreakPilot Dev Date: Sun, 8 Feb 2026 23:41:29 -0800 Subject: [PATCH] feat(claude): Add testing, documentation rules and project hooks - rules/testing.md: TDD workflow for Go and Python - rules/documentation.md: Auto-documentation guidelines - plans/embedding-service-separation.md: Migration plan - settings.json: Post-edit hooks for docs/tests validation - .gitignore: Exclude settings.local.json (contains API keys) Co-Authored-By: Claude Opus 4.5 --- .claude/plans/embedding-service-separation.md | 263 ++++++++++++++++++ .claude/rules/documentation.md | 91 ++++++ .claude/rules/testing.md | 202 ++++++++++++++ .claude/settings.json | 36 +++ .gitignore | 145 ++++++++++ 5 files changed, 737 insertions(+) create mode 100644 .claude/plans/embedding-service-separation.md create mode 100644 .claude/rules/documentation.md create mode 100644 .claude/rules/testing.md create mode 100644 .claude/settings.json create mode 100644 .gitignore diff --git a/.claude/plans/embedding-service-separation.md b/.claude/plans/embedding-service-separation.md new file mode 100644 index 0000000..b841e62 --- /dev/null +++ b/.claude/plans/embedding-service-separation.md @@ -0,0 +1,263 @@ +# Plan: Embedding-Service Separation + +## Ziel +Trennung der ML/Embedding-Komponenten vom klausur-service in einen eigenständigen `embedding-service`, um die Build-Zeit von ~20 Minuten auf ~30 Sekunden zu reduzieren. + +## Aktuelle Situation + +| Service | Build-Zeit | Image-Größe | Problem | +|---------|------------|-------------|---------| +| klausur-service | ~20 min | ~2.5 GB | PyTorch + sentence-transformers werden bei jedem Build installiert | + +## Ziel-Architektur + +``` +┌─────────────────┐ HTTP ┌──────────────────┐ +│ klausur-service │ ───────────→ │ embedding-service │ +│ (FastAPI) │ │ (FastAPI) │ +│ Port 8086 │ │ Port 8087 │ +│ ~200 MB │ │ ~2.5 GB │ +│ Build: 30s │ │ Build: 15 min │ +└─────────────────┘ └──────────────────┘ + │ │ + └────────────┬───────────────────┘ + ▼ + ┌───────────────┐ + │ Qdrant │ + │ Port 6333 │ + └───────────────┘ +``` + +## Phase 1: Neuen Embedding-Service erstellen + +### 1.1 Verzeichnisstruktur anlegen +``` +klausur-service/ +├── embedding-service/ # NEU +│ ├── Dockerfile +│ ├── requirements.txt +│ ├── main.py # FastAPI App +│ ├── eh_pipeline.py # Kopie +│ ├── reranker.py # Kopie +│ ├── hyde.py # Kopie +│ ├── hybrid_search.py # Kopie +│ ├── pdf_extraction.py # Kopie +│ └── config.py # Embedding-Konfiguration +├── backend/ # Bestehend (wird angepasst) +└── frontend/ # Bestehend +``` + +### 1.2 Dateien in embedding-service erstellen + +**requirements.txt** (ML-spezifisch): +``` +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 +torch>=2.0.0 +sentence-transformers>=2.2.0 +qdrant-client>=1.7.0 +unstructured>=0.12.0 +pypdf>=4.0.0 +httpx>=0.26.0 +pydantic>=2.0.0 +python-dotenv>=1.0.0 +``` + +**main.py** - API-Endpoints: +- `POST /embed` - Generiert Embeddings für Text/Liste von Texten +- `POST /embed-single` - Einzelnes Embedding +- `POST /rerank` - Re-Ranking von Suchergebnissen +- `POST /extract-pdf` - PDF-Text-Extraktion +- `GET /health` - Health-Check +- `GET /models` - Verfügbare Modelle + +### 1.3 Dockerfile (embedding-service) +```dockerfile +FROM python:3.11-slim + +WORKDIR /app + +# PyTorch CPU-only für kleinere Images +RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Modelle vorab laden (Layer-Cache) +RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('BAAI/bge-m3')" +RUN python -c "from sentence_transformers import CrossEncoder; CrossEncoder('BAAI/bge-reranker-v2-m3')" + +COPY . . + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8087"] +``` + +## Phase 2: Klausur-Service anpassen + +### 2.1 ML-Dependencies aus requirements.txt entfernen +Entfernen: +- `torch` +- `sentence-transformers` +- `unstructured` +- `pypdf` + +Behalten: +- `fastapi`, `uvicorn`, `httpx` +- `qdrant-client` (für Suche) +- `cryptography` (BYOEH) +- Alle Business-Logic-Dependencies + +### 2.2 Embedding-Client erstellen +Neue Datei `backend/embedding_client.py`: +```python +class EmbeddingClient: + def __init__(self, base_url: str = "http://embedding-service:8087"): + self.base_url = base_url + + async def generate_embeddings(self, texts: list[str]) -> list[list[float]]: + async with httpx.AsyncClient() as client: + response = await client.post(f"{self.base_url}/embed", json={"texts": texts}) + return response.json()["embeddings"] + + async def rerank(self, query: str, documents: list[str]) -> list[dict]: + async with httpx.AsyncClient() as client: + response = await client.post(f"{self.base_url}/rerank", + json={"query": query, "documents": documents}) + return response.json()["results"] +``` + +### 2.3 Bestehende Aufrufe umleiten +Dateien anpassen: +- `backend/main.py`: `generate_single_embedding()` → `embedding_client.generate_embeddings()` +- `backend/admin_api.py`: Embedding-Aufrufe über Client +- `backend/qdrant_service.py`: Bleibt für Suche, Indexierung nutzt Client + +## Phase 3: Docker-Compose Integration + +### 3.1 docker-compose.dev.yml erweitern +```yaml +services: + klausur-service: + build: + context: ./klausur-service + dockerfile: Dockerfile + ports: + - "8086:8086" + environment: + - EMBEDDING_SERVICE_URL=http://embedding-service:8087 + depends_on: + - embedding-service + - qdrant + + embedding-service: + build: + context: ./klausur-service/embedding-service + dockerfile: Dockerfile + ports: + - "8087:8087" + environment: + - EMBEDDING_BACKEND=local + - LOCAL_EMBEDDING_MODEL=BAAI/bge-m3 + - LOCAL_RERANKER_MODEL=BAAI/bge-reranker-v2-m3 + volumes: + - embedding-models:/root/.cache/huggingface # Model-Cache + deploy: + resources: + limits: + memory: 4G + + qdrant: + image: qdrant/qdrant:latest + ports: + - "6333:6333" + volumes: + - qdrant-data:/qdrant/storage + +volumes: + embedding-models: + qdrant-data: +``` + +## Phase 4: Tests und Validierung + +### 4.1 Unit Tests für Embedding-Service +- Test Embedding-Generierung +- Test Re-Ranking +- Test PDF-Extraktion +- Test Health-Endpoint + +### 4.2 Integration Tests +- Test klausur-service → embedding-service Kommunikation +- Test RAG-Query End-to-End +- Test EH-Upload mit Embedding + +### 4.3 Performance-Validierung +- Build-Zeit klausur-service messen (Ziel: <1 min) +- Embedding-Latenz messen (Ziel: <500ms für einzelnes Embedding) +- Re-Ranking-Latenz messen (Ziel: <1s für 10 Dokumente) + +## Implementierungsreihenfolge + +1. **embedding-service/main.py** - FastAPI App mit Endpoints +2. **embedding-service/config.py** - Konfiguration +3. **embedding-service/requirements.txt** - Dependencies +4. **embedding-service/Dockerfile** - Container-Build +5. **backend/embedding_client.py** - HTTP-Client +6. **backend/requirements.txt** - ML-Deps entfernen +7. **backend/main.py** - Aufrufe umleiten +8. **backend/admin_api.py** - Aufrufe umleiten +9. **docker-compose.dev.yml** - Service hinzufügen +10. **Tests** - Validierung + +## Zu bewegende Dateien (Referenz) + +| Datei | Zeilen | Aktion | +|-------|--------|--------| +| eh_pipeline.py | 777 | Kopieren → embedding-service | +| reranker.py | 253 | Kopieren → embedding-service | +| hyde.py | 209 | Kopieren → embedding-service | +| hybrid_search.py | 285 | Kopieren → embedding-service | +| pdf_extraction.py | 479 | Kopieren → embedding-service | + +## Umgebungsvariablen + +### embedding-service +``` +EMBEDDING_BACKEND=local +LOCAL_EMBEDDING_MODEL=BAAI/bge-m3 +LOCAL_RERANKER_MODEL=BAAI/bge-reranker-v2-m3 +OPENAI_API_KEY= # Optional für OpenAI-Backend +PDF_EXTRACTION_BACKEND=auto +LOG_LEVEL=INFO +``` + +### klausur-service (neu) +``` +EMBEDDING_SERVICE_URL=http://embedding-service:8087 +``` + +## Risiken und Mitigationen + +| Risiko | Mitigation | +|--------|------------| +| Netzwerk-Latenz zwischen Services | Model-Caching, Connection-Pooling | +| embedding-service nicht erreichbar | Health-Checks, Retry-Logik, Graceful Degradation | +| Inkonsistente Embedding-Modelle | Versionierung, Model-Hash-Prüfung | +| Erhöhter RAM-Bedarf (2 Container) | Memory-Limits in Docker, Model-Offloading | + +## Erwartete Ergebnisse + +| Metrik | Vorher | Nachher | +|--------|--------|---------| +| Build-Zeit klausur-service | ~20 min | ~30 sec | +| Build-Zeit embedding-service | - | ~15 min | +| Image-Größe klausur-service | ~2.5 GB | ~200 MB | +| Image-Größe embedding-service | - | ~2.5 GB | +| Entwickler-Iteration | Langsam | Schnell | + +## Nicht vergessen (Task A) + +Nach Abschluss der Service-Trennung: +- [ ] EH-Upload-Wizard mit Test-Klausur testen +- [ ] Security-Infobox im Wizard verifizieren +- [ ] End-to-End RAG-Query testen diff --git a/.claude/rules/documentation.md b/.claude/rules/documentation.md new file mode 100644 index 0000000..e5e035f --- /dev/null +++ b/.claude/rules/documentation.md @@ -0,0 +1,91 @@ +# Dokumentations-Regeln + +## Automatische Dokumentations-Aktualisierung + +**WICHTIG:** Bei JEDER Code-Änderung muss die entsprechende Dokumentation aktualisiert werden! + +## Wann Dokumentation aktualisieren? + +### API-Änderungen +Wenn du einen Endpoint änderst, hinzufügst oder entfernst: +- Aktualisiere `/docs/api/consent-service-api.md` (Go Endpoints) +- Aktualisiere `/docs/api/backend-api.md` (Python Endpoints) + +### Neue Funktionen/Klassen +Wenn du neue Funktionen, Klassen oder Module erstellst: +- Aktualisiere `/docs/consent-service/README.md` (für Go) +- Aktualisiere `/docs/backend/README.md` (für Python) + +### Architektur-Änderungen +Wenn du die Systemarchitektur änderst: +- Aktualisiere `/docs/architecture/system-architecture.md` +- Aktualisiere `/docs/architecture/data-model.md` (bei DB-Änderungen) + +### Neue Konfigurationsoptionen +Wenn du neue Umgebungsvariablen oder Konfigurationen hinzufügst: +- Aktualisiere die entsprechende README +- Füge zur `guides/local-development.md` hinzu + +## Dokumentations-Format + +### API-Endpoints dokumentieren + +```markdown +### METHOD /path/to/endpoint + +Kurze Beschreibung. + +**Request Body:** +\`\`\`json +{ + "field": "value" +} +\`\`\` + +**Response (200):** +\`\`\`json +{ + "result": "value" +} +\`\`\` + +**Errors:** +- `400`: Beschreibung +- `401`: Beschreibung +``` + +### Funktionen dokumentieren + +```markdown +### FunctionName (file.go:123) + +\`\`\`go +func FunctionName(param Type) ReturnType +\`\`\` + +**Beschreibung:** Was macht die Funktion? + +**Parameter:** +- `param`: Beschreibung + +**Rückgabe:** Beschreibung +``` + +## Checkliste nach Code-Änderungen + +Vor dem Abschluss einer Aufgabe prüfe: + +- [ ] Wurden neue API-Endpoints hinzugefügt? → API-Docs aktualisieren +- [ ] Wurden Datenmodelle geändert? → data-model.md aktualisieren +- [ ] Wurden neue Konfigurationen hinzugefügt? → README aktualisieren +- [ ] Wurden neue Abhängigkeiten hinzugefügt? → requirements.txt/go.mod UND Docs +- [ ] Wurde die Architektur geändert? → architecture/ aktualisieren + +## Beispiel: Vollständige Dokumentation einer neuen Funktion + +Wenn du z.B. `GetUserStats()` im Go Service hinzufügst: + +1. **Code schreiben** in `internal/services/stats_service.go` +2. **API-Doc aktualisieren** in `docs/api/consent-service-api.md` +3. **Service-Doc aktualisieren** in `docs/consent-service/README.md` +4. **Test schreiben** (siehe testing.md) diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 0000000..d7441f6 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,202 @@ +# Test-Regeln + +## Automatische Test-Erweiterung + +**WICHTIG:** Bei JEDER Code-Änderung müssen entsprechende Tests erstellt oder aktualisiert werden! + +## Wann Tests schreiben? + +### IMMER wenn du: +1. **Neue Funktionen** erstellst → Unit Test +2. **Neue API-Endpoints** hinzufügst → Handler Test +3. **Bugs fixst** → Regression Test (der Bug sollte nie wieder auftreten) +4. **Bestehenden Code änderst** → Bestehende Tests anpassen + +## Test-Struktur + +### Go Tests (Consent Service) + +**Speicherort:** Im gleichen Verzeichnis wie der Code + +``` +internal/ +├── services/ +│ ├── auth_service.go +│ └── auth_service_test.go ← Test hier +├── handlers/ +│ ├── handlers.go +│ └── handlers_test.go ← Test hier +└── middleware/ + ├── auth.go + └── middleware_test.go ← Test hier +``` + +**Test-Namenskonvention:** +```go +func TestFunctionName_Scenario_ExpectedResult(t *testing.T) + +// Beispiele: +func TestHashPassword_ValidPassword_ReturnsHash(t *testing.T) +func TestLogin_InvalidCredentials_Returns401(t *testing.T) +func TestCreateDocument_MissingTitle_ReturnsError(t *testing.T) +``` + +**Test-Template:** +```go +func TestFunctionName(t *testing.T) { + // Arrange + service := &MyService{} + input := "test-input" + + // Act + result, err := service.DoSomething(input) + + // Assert + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if result != expected { + t.Errorf("Expected %v, got %v", expected, result) + } +} +``` + +**Table-Driven Tests bevorzugen:** +```go +func TestValidateEmail(t *testing.T) { + tests := []struct { + name string + email string + expected bool + }{ + {"valid email", "test@example.com", true}, + {"missing @", "testexample.com", false}, + {"empty", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ValidateEmail(tt.email) + if result != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} +``` + +### Python Tests (Backend) + +**Speicherort:** `/backend/tests/` + +``` +backend/ +├── consent_client.py +├── gdpr_api.py +└── tests/ + ├── __init__.py + ├── test_consent_client.py ← Tests für consent_client.py + └── test_gdpr_api.py ← Tests für gdpr_api.py +``` + +**Test-Namenskonvention:** +```python +class TestClassName: + def test_method_scenario_expected_result(self): + pass + +# Beispiele: +class TestConsentClient: + def test_check_consent_valid_token_returns_status(self): + pass + + def test_check_consent_expired_token_raises_error(self): + pass +``` + +**Test-Template:** +```python +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +class TestMyFeature: + def test_sync_function(self): + # Arrange + input_data = "test" + + # Act + result = my_function(input_data) + + # Assert + assert result == expected + + @pytest.mark.asyncio + async def test_async_function(self): + # Arrange + client = MyClient() + + # Act + with patch("httpx.AsyncClient") as mock: + mock_instance = AsyncMock() + mock.return_value = mock_instance + result = await client.fetch_data() + + # Assert + assert result is not None +``` + +## Test-Kategorien + +### 1. Unit Tests (Höchste Priorität) +- Testen einzelne Funktionen/Methoden +- Keine externen Abhängigkeiten (Mocks verwenden) +- Schnell ausführbar + +### 2. Integration Tests +- Testen Zusammenspiel mehrerer Komponenten +- Können echte DB verwenden (Test-DB) + +### 3. Security Tests +- Auth/JWT Validierung +- Passwort-Hashing +- Berechtigungsprüfung + +## Checkliste vor Abschluss + +Vor dem Abschluss einer Aufgabe: + +- [ ] Gibt es Tests für alle neuen Funktionen? +- [ ] Gibt es Tests für alle Edge Cases? +- [ ] Gibt es Tests für Fehlerfälle? +- [ ] Laufen alle bestehenden Tests noch? (`go test ./...` / `pytest`) +- [ ] Ist die Test-Coverage angemessen? + +## Tests ausführen + +```bash +# Go - Alle Tests +cd consent-service && go test -v ./... + +# Go - Mit Coverage +cd consent-service && go test -cover ./... + +# Python - Alle Tests +cd backend && source venv/bin/activate && pytest -v + +# Python - Mit Coverage +cd backend && pytest --cov=. --cov-report=html +``` + +## Beispiel: Vollständiger Test-Workflow + +Wenn du z.B. eine neue `GetUserStats()` Funktion im Go Service hinzufügst: + +1. **Funktion schreiben** in `internal/services/stats_service.go` +2. **Test erstellen** in `internal/services/stats_service_test.go`: + ```go + func TestGetUserStats_ValidUser_ReturnsStats(t *testing.T) {...} + func TestGetUserStats_InvalidUser_ReturnsError(t *testing.T) {...} + func TestGetUserStats_NoConsents_ReturnsEmptyStats(t *testing.T) {...} + ``` +3. **Tests ausführen**: `go test -v ./internal/services/...` +4. **Dokumentation aktualisieren** (siehe documentation.md) diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..2d6b1d3 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,36 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "python3 ~/.claude/hooks/breakpilot-post-edit.py", + "timeout": 15000 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "prompt", + "prompt": "Überprüfe ob bei dieser Aufgabe:\n1. Dokumentation aktualisiert werden muss (neue API, neue Funktion, Architektur-Änderung)\n2. Tests geschrieben/aktualisiert werden müssen (neue Funktion, Bug-Fix, Code-Änderung)\n3. Ein ADR (Architecture Decision Record) erstellt werden sollte (neues Modul, Technologiewechsel, signifikante Architektur-Entscheidung)\n\nWenn etwas fehlt, antworte mit {\"decision\": \"block\", \"reason\": \"Fehlend: [Details]\"}\nWenn alles erledigt ist, antworte mit {\"decision\": \"approve\", \"reason\": \"Alle Dokumentation, Tests und ADRs sind aktuell\"}", + "timeout": 30000 + } + ] + } + ] + }, + "permissions": { + "allow": [ + "Read(docs/**)", + "Read(.claude/**)", + "Read(backend/tests/**)", + "Read(consent-service/**/*_test.go)", + "Write(docs/adr/**)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2f5548 --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# ============================================ +# BreakPilot PWA - Git Ignore +# ============================================ + +# Environment files (keep examples only) +.env +.env.local +*.env.local + +# Keep examples and environment templates +!.env.example +!.env.dev +!.env.staging +# .env.prod should NOT be in repo (contains production secrets) + +# ============================================ +# Python +# ============================================ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +ENV/ +.venv/ +*.egg-info/ +.eggs/ +*.egg +.pytest_cache/ +htmlcov/ +.coverage +.coverage.* +coverage.xml +*.cover + +# ============================================ +# Node.js +# ============================================ +node_modules/ +.next/ +out/ +dist/ +build/ +.npm +.yarn-integrity +*.tsbuildinfo + +# ============================================ +# Go +# ============================================ +*.exe +*.exe~ +*.dll +*.dylib +*.test +*.out +vendor/ + +# ============================================ +# Docker +# ============================================ +# Don't ignore docker-compose files +# Ignore volume data if mounted locally +backups/ +*.sql.gz +*.sql + +# ============================================ +# IDE & Editors +# ============================================ +.idea/ +.vscode/ +*.swp +*.swo +*~ +.project +.classpath +.settings/ +*.sublime-workspace +*.sublime-project + +# ============================================ +# OS Files +# ============================================ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# ============================================ +# Secrets & Credentials +# ============================================ +secrets/ +*.pem +*.key +*.crt +*.p12 +*.pfx +credentials.json +service-account.json + +# ============================================ +# Logs +# ============================================ +*.log +logs/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# ============================================ +# Build Artifacts +# ============================================ +*.zip +*.tar.gz +*.rar + +# ============================================ +# Temporary Files +# ============================================ +tmp/ +temp/ +*.tmp +*.temp + +# ============================================ +# Test Results +# ============================================ +test-results/ +playwright-report/ +coverage/ + +# ============================================ +# ML Models (large files) +# ============================================ +*.pt +*.pth +*.onnx +*.safetensors +models/ +.claude/settings.local.json