| name | toadapt-validation-and-qa |
| description | Validierung und Qualitätssicherung im ToAdapt-Repo: Was als Evidenz für eine Änderung zählt (CI < Smoke < Guardrail-Regression < Teacher-Alignment), Landkarte aller pytest-Tests, das Golden-Case-Inventar (alpes-bank-genai-001 + Rubric-JSONs als kalibrierte Artefakte), Copy-Paste-Muster zum Ergänzen neuer Tests, CI-Anatomie inkl. PYTHONPATH-Falle, Akzeptanz-Schwellen. Lade diese Skill, wenn du: (a) vor einem Commit/Push wissen willst, welche Prüfungen Pflicht sind und wie man sie lokal ausführt; (b) einen neuen Test schreiben willst (TestClient, Mongo-Env wegräumen, LLM mocken, Stores auf tmp_path patchen); (c) den Judge/Evaluator/Prompts geändert hast und wissen musst, welcher Nachweis akzeptiert wird; (d) Tests fehlschlagen (z. B. ModuleNotFoundError backend, 2-Sekunden-Hänger, 90-Tests-Baseline verletzt); (e) den Golden Case oder eine Rubric-JSON anfassen willst; (f) fragst "reicht dieser Beweis?" oder "welcher Test deckt X ab?". Keywords: pytest, ruff, CI, ci.yml, TestClient, monkeypatch, Golden Case, Rubric, Alignment, Pearson, MAE, Evidenz, Regression, QA, Testabdeckung.
|
ToAdapt — Validation & QA
Diese Skill definiert, was in diesem Repo als Beweis gilt, dass eine
Änderung funktioniert — und wie man diesen Beweis erbringt.
Wann diese Skill NICHT gilt
| Deine Frage | Richtige Skill |
|---|
| "Darf ich das überhaupt ändern? Welches Gate gilt?" | toadapt-change-control |
| "Warum schlägt X in Produktion fehl?" (401/503/429/Mongo/CORS) | toadapt-debugging-playbook |
| "Wie starte ich Backend/Frontend lokal, wie deploye ich?" | toadapt-run-and-operate |
| "Wie baue ich die Umgebung (venv, npm) von Null auf?" | toadapt-build-and-env |
| "Was bedeutet Bloom/TP/Canvas/Scaffolding fachlich?" | bwl-scaffolding-reference |
| "Wie messe ich Laufzeitverhalten (Diagnostics, Logs)?" | toadapt-diagnostics-and-tooling |
| "Wie verbessere ich konkret das q4-Judge-Alignment?" | toadapt-judge-alignment-campaign |
| "Welche Env-Variable steuert was?" | toadapt-config-and-flags |
1. Evidenz-Staffelung: Was zählt als Beweis?
Von schwach nach stark. Wähle die Stufe nach dem, was du geändert hast —
NICHT nach dem, was am bequemsten ist.
| Stufe | Evidenz | Reicht für |
|---|
| 1 | CI grün (ruff + pytest; eslint + tsc + next build) | Refactorings ohne Verhaltensänderung, Doku, Tests selbst |
| 2 | Lokaler Smoke-Test (Backend starten, Endpoint real anfragen) | API-Routen-Änderungen, Auth-/Rate-Limit-Änderungen, neue Endpoints |
| 3 | Guardrail-Regressionstests (gezielte pytest-Fälle für neue verbotene/erlaubte Muster) | Änderungen an backend/agents/orchestrator.py (Guardrails, Prompts, Routing), an backend/evaluator/formative_feedback.py (Denkanstöße laufen durch guardrail_check) und am Feedback-Sanitizing des Evaluators |
| 3b | Tutor-Eval-Regressionsvergleich (NAACL-Dimensionen vorher/nachher via scripts/evaluate_tutor_responses.py) | Studierendensichtbare Prompt-Änderungen an Agenten- oder Formative-Feedback-Prompts (Klasse A). Durchführung und Akzeptanzkriterien: toadapt-tutor-response-evaluation |
| 4 | Teacher-Alignment-Metriken (Vergleichs-Pipeline gegen Lehrkraft-Scores, siehe §6) | JEDE Änderung an EVALUATOR_SYSTEM, EVALUATE_PROMPT, den Kalibrierungsankern (calibration_notes im Case / BLOOM_CALIBRATION_ANCHORS) oder den Rubric-Daten (eingebettet oder JSON-Dateien) |
Harte Regeln:
- "Sieht besser aus" zählt NIE als Evidenz für Judge-Änderungen. Für den
LLM-Judge (der Rubric-Evaluator, der Studierenden-Antworten bepunktet) sind
Alignment-Metriken (Pearson, MAE gegen Lehrkraft-Bewertungen) die EINZIGE
akzeptierte Evidenz. Einzelbeispiele anschauen ist Exploration, kein Beweis.
- Jede höhere Stufe setzt die niedrigeren voraus (Alignment-Nachweis ersetzt
keine grüne CI).
- Die Entscheidung, WELCHE Stufe eine konkrete Änderung verlangt, trifft
toadapt-change-control — diese Skill beschreibt, WIE man die Stufe erfüllt.
Stufe 1+2 lokal ausführen (Kommandos, vom Repo-Root)
cd /Users/dianakozachek/ToAdapt
ruff check .
.venv/bin/python -m pytest tests/ -q
cd frontend && npm run lint && npx tsc --noEmit && npm run build
Minimaler Smoke-Test (kein LLM-Key nötig für /health):
.venv/bin/python -m uvicorn backend.main:app --port 8000 &
curl -s http://localhost:8000/health
Für vollständige Start-Anleitung (Env-Variablen, Frontend-Kopplung, Mongo):
toadapt-run-and-operate.
2. Test-Landkarte (Stand: 2026-07-11, 131 Tests)
Alle Tests liegen flach in tests/ (kein conftest.py, kein __init__.py).
Konfiguration in pyproject.toml: asyncio_mode = "auto" (async-Tests
brauchen keinen Decorator), testpaths = ["tests"].
| Datei | # | Deckt ab |
|---|
tests/test_skeleton.py | 7 | /health minimal (keine Infra-Details im Body), /health/diagnostics + /dashboard/overview fail-closed ohne Key, Session-Erstellung gegen den Golden Case, current_tp_phase()-Fensterlogik (TP-Phase aus Datum) |
tests/test_phase1_hardening.py | 9 | Studenten-Zugangscode (STUDENT_ACCESS_CODE offen/erzwungen), Rate-Limiter (429 + Retry-After, Keying per Path-Param), Session-/Dashboard-Store-Fallbacks ohne Mongo, geteilter OpenRouter-HTTP-Client, LLM-Concurrency-Semaphore |
tests/test_case_editor.py | 13 | Case-Validator (Framework-Namen-Verbot, NORDIC-HOME-Sperre, Bloom-Coverage-Warnung), PATCH-Editor (Revision-Bump, Draft-Reset), Approve-Gate (422 + force-Override), Retire, Teil-Regenerierung mit gemocktem LLM, JSON-Fence-Stripping |
tests/test_orchestrator_guardrails.py | 3 | guardrail_check(): blockt direkte Use-Case-Empfehlung, Case-Spekulation (FINMA/Azure), Slang + Emoji |
tests/test_rubric_evaluator.py | 11 | JSON-Extraktion (plain/fenced/umschlossen), Feedback-Sanitizing (Antwort-Templates raus), Canvas-Rationale-Sanitizing, technical_fallback-Payload (0 Punkte, needs_human_review), Totals-Neuberechnung aus Scores; NEU (2026-07-09): Repair-Kette (zweiter LLM-Call rettet invalides erstes JSON), Typ-Fallback (awarded_points="acht" → technical_fallback statt 500), Parallel-Auswertung behält Fragen-Reihenfolge, Prompt-Pinning der Band-Anker (13.8/6.2 Punkte = 25 × 0.55/0.25) |
tests/test_rubric_loader.py | 1 | Golden Case → alle 4 Rubrics ladbar, required_canvas_blocks pro q1–q4 korrekt verkettet |
tests/test_case_package.py | 13 | Gate für den Golden Case: eingebettetes Bewertungspaket ≡ Datei-Rubric (Äquivalenz DE+EN), migrierte calibration_notes wortgleich gepinnt, generische BLOOM_CALIBRATION_ANCHORS (Bloom 2–6) für unkalibrierte Fragen, Embedded-vor-Datei-Vorrang + Datei-Fallback, Validator-Warnungen (fehlende Rubric/Glossar/Guidance, Glossar-Begriff nicht im Text), Generator parst Komplettpaket, Orchestrator bevorzugt case.agent_guidance (Golden Case: -agent.json-Fallback) |
tests/test_formative_support.py | 7 | Formative Live-Unterstützung: Coverage-Endpoint (Canvas-Abdeckung, 404 bei unbekannter Frage), Denkanstoß-Feedback (max. 2 pro Frage → 429, Guardrail ersetzt verbotenen Output, leerer Draft abgelehnt), AnswerStats-Persistenz, paste_heavy-Schwellenlogik |
tests/test_groups_and_privacy.py | 10 | Pseudonymisierung (HMAC stabil/irreversibel, ohne Secret roh), normalize_group_code, Session/Submission tragen Pseudonym + group_code, GET /tp-Schedule, /dashboard/groups ohne Einzelkennungen, Einzelpersonen-Endpoints verlangen X-Research-Key (fail-closed), scripts/generate_tutor_codes.py |
tests/test_tutor_eval.py | 8 | NAACL-Taxonomie (8 Dimensionen), Judge-Prompt-Vollständigkeit, Annotation-Normalisierung, Desirability-Aggregation, Judge-JSON-Parsing, scripts/evaluate_tutor_responses.py (stabile Item-IDs, Annotation-Workbook, Summary) — inhaltlich: toadapt-tutor-response-evaluation |
tests/test_submission_store.py | 1 | Submission-Recovery aus Store in den In-Memory-Cache (routes._get_submission) |
tests/test_dashboard_difficulties.py | 3 | /dashboard/difficulties: API-Key-Pflicht, Priorisierung schwacher Studierender (attention_level, weak_objectives, Penalty-Aggregation case-insensitiv), Kohorten-Aggregation — mit synthetischen Submissions im tmp-Dateistore; seit 2026-07-09 zusätzlich mit X-Research-Key-Header (Endpoint verlangt RESEARCH_API_KEY) |
tests/test_export_review_workbooks.py | 1 | Forschungs-Skript: Rubric-/Blind-/Chat-Workbooks (Blind-Sheet ohne Judge-Scores — Grundlage der Alignment-Studie) |
tests/test_compare_teacher_rubric_scores.py | 1 | Forschungs-Skript: Lehrer-Workbook ist kanonischer Scope, rubric-only-Zeilen by design ausgeschlossen |
tests/test_import_prolific_runs.py | 1 | Forschungs-Skript: Rohdaten-Import + SHA-256-Manifest, ignoriert .DS_Store |
tests/test_publish_dashboard_scores.py | 1 | Forschungs-Skript: publiziert nur status=="evaluated" mit Scores, zählt Fallbacks/Review-Flags |
tests/test_group_uploads.py (NEU 2026-07-10) | 14 | Master-Upload Gruppenarbeiten: ZIP-/PDF-Extraktion pur (minimales synthetisches PDF, Deckblatt-Parsing Gruppe 12→G12, Cover-only-Matching), fail-closed Auth (503/401), Upload-Flow mit gemocktem LLM (Zuordnung, TP-Punkteskala), technical_fallback bei Parse- UND Transportfehlern, unlesbares PDF → Report statt Crash, PATCH-Gruppenzuordnung, Dashboard-Merge beider Datenquellen (Gruppen ohne Individual-Submissions bleiben sichtbar, keine Einzelkennungen) |
tests/test_llm_client.py (NEU 2026-07-10) | 9 | LLM-Client: Prompt-Caching-Verpackung (cache_control-Block, byte-identischer Inhalt), LLM_PROMPT_CACHING-Off-Switch, OPENROUTER_FALLBACK_MODELS-Parsing, models-Routing-Liste in extra_body, leere Antwort → RuntimeError — via Stub statt echtem LLM |
tests/test_group_code_validation.py (NEU 2026-07-11) | 9 | GROUP_CODE_MAX: Grenzen (G1/G360 ok, G0/G361/Freitext nicht), leer/ungültig = Validierung aus, /auth/student/verify-Feedback (normalisiert + valid-Flag, ohne Body abwärtskompatibel), 422 bei Session-/Submission-Erstellung |
tests/test_load_test_tools.py (NEU 2026-07-10) | 6 | Lasttest-Tooling: Stub-Chat-Antwort besteht guardrail_check (TP1–4), Stub-Judge-JSON besteht parse_evaluation_payload, Perzentil-/Status-Aggregation, GATE-W1-Auswertung im Report |
Was NICHT abgedeckt ist (ehrlich)
- Keine echten LLM-Pfade. Chat (
POST /sessions/{id}/chat),
Case-Generierung und die eigentliche LLM-Evaluation laufen in Tests nur
gemockt oder gar nicht. Ob ein Prompt gute Antworten produziert, beweist
KEIN Test — dafür gibt es nur Stufe 4 (Alignment) bzw. manuelle Smokes.
- Kein E2E-Test (Frontend→Backend durchgeklickt). Frontend-CI prüft nur
Lint/Typen/Build.
- Kein Load-Test. Rate-Limiter ist unit-getestet, Verhalten unter
echter Parallellast (Railway, mehrere Worker) ist unbelegt.
- Kein Mongo-Integrationstest — alle Tests laufen bewusst OHNE Mongo
(Datei-/No-op-Fallback). Mongo-Verhalten wird nur in Produktion via
/health/diagnostics beobachtet (→ toadapt-diagnostics-and-tooling).
retry_technical_fallback_scores.py ist das einzige Skript ohne Test —
es macht echte LLM-Calls (nur mit --dry-run gefahrlos).
3. Golden-Inventar: Fixtures mit eigener Gate-Stufe
Der Golden Case: alpes-bank-genai-001
Der EINZIGE kuratierte, freigegebene Case im Pool (backend/cases/pool/),
Stand 2026-07-09. Vier Dateien gehören zusammen:
| Datei | Rolle |
|---|
backend/cases/pool/alpes-bank-genai-001.json | Deutscher Case: Sections, Exhibits, q1–q4 |
backend/cases/pool/alpes-bank-genai-001-en.json | Englische Variante (Suffix -en schaltet EN-Agenten-Prompts) |
backend/cases/pool/alpes-bank-genai-001-agent.json | Agent-Guidance (key_tensions, common_mistakes → fließen in Agenten-Prompts) |
backend/cases/pool/alpes-bank-genai-001-en-agent.json | dito EN |
Fragenstruktur (Fixture-Anker — Tests und Kalibrierung hängen daran):
| Frage | Bloom | max_points | rubric_reference |
|---|
| q1 | 4 | 25 | tp1_rubric.json |
| q2 | 5 | 24 | tp2_rubric.json |
| q3 | 4 | 22 | tp3_rubric.json |
| q4 | 6 | 30 | tp4_rubric.json |
Seit 2026-07-09 trägt der Golden Case (DE und EN) ein eingebettetes
Bewertungspaket pro Frage: evaluation_focus, required_canvas_blocks,
calibration_notes, exemplar_threshold_pct/score_floor_pct. Der
rubric_loader arbeitet EMBEDDED-FIRST; die tp{n}_rubric.json-Dateien sind
nur noch Fallback für Cases ohne eingebettetes Paket.
Warum eigene Gate-Stufe: Eine Änderung an diesem Case ist gleichzeitig
(a) eine Fixture-Änderung — tests/test_rubric_loader.py (asserted konkrete
Canvas-Blöcke pro Frage), tests/test_skeleton.py und
tests/test_submission_store.py referenzieren die case_id hart —, (b) eine
studierendensichtbare Verhaltensänderung (der Case IST das Produkt) und
(c) potenziell eine Kalibrierungs-Invalidierung (siehe unten). Das
technische Gate für Änderungen am eingebetteten Paket sind die
Äquivalenz-Tests in tests/test_case_package.py: sie beweisen, dass das
eingebettete Paket dem Datei-Rubric entspricht und die migrierten
Kalibrierungsanker wortgleich erhalten sind — wer das Paket ändert, bricht
sie bewusst und braucht Stufe-4-Evidenz. Zusätzlich ist
das Glossar pro Case im Frontend hartkodiert
(CASE_GLOSSARY["alpes-bank-genai-001"] in frontend/app/cases/[id]/page.tsx;
Alpes-Hardcode hat Vorrang, neue Cases bringen case.glossary mit)
— Case-Inhalt und Glossar können auseinanderlaufen. Vor jeder Änderung:
toadapt-change-control laden.
Die 4 Rubric-JSONs sind KALIBRIERTE Artefakte
backend/config/rubrics/tp{1..4}_rubric.json enthalten pro Frage
evaluation_focus (worauf der Judge achtet) und required_canvas_blocks
(Business-Model-Canvas-Blöcke mit accepted_keywords und weight), plus
Schwellen (exemplar_threshold_pct/score_floor_pct: 80/75, 82/75, 80/72,
82/75 für tp1–tp4). Seit 2026-07-09 sind sie nur noch Datei-Fallback für
Alt-Cases ohne eingebettetes Paket (per lru_cache gecacht) — der Golden
Case liest de facto sein eingebettetes Paket, das per Äquivalenz-Test mit
den Dateien identisch gehalten wird. Kalibrierte Artefakte bleiben BEIDE.
Entscheidend — Kalibrierungsanker, Stand 2026-07-09 (zweistufig):
- Case-spezifische Anker liegen in
question.calibration_notes DIREKT im
Case-JSON (für den Golden Case: die wörtlich migrierten Anker der
Teacher-Alignment-Studie, §6 — gepinnt durch
tests/test_case_package.py::test_golden_case_calibration_notes_unchanged).
Sie ERSETZEN bei Vorhandensein alles andere.
- Fehlen sie, greifen generische
BLOOM_CALIBRATION_ANCHORS (pro Bloom 2–6)
in backend/evaluator/rubric_evaluator.py.
BEHOBEN (2026-07-09): Die früher in _format_calibration_notes HARTKODIERTEN
q1–q4-Anker existieren nicht mehr im Code — damit erben NEUE Cases nicht mehr
automatisch Anker, die für den Alpes-Case formuliert wurden (die frühere
bekannte Design-Schwäche). Unverändert gilt:
- Rubric-Daten (eingebettet oder Datei),
calibration_notes oder Case-Fragen
ändern → Kalibrierung potenziell hinfällig → Stufe-4-Evidenz nötig.
4. Tests ergänzen — die hiesigen Muster (Copy-Paste)
Neue Tests: Datei tests/test_<thema>.py, Funktionen test_*. Async-Tests
einfach als async def schreiben (asyncio_mode = "auto"). Keine echten
LLM-Calls, keine echten Teilnehmerdaten — immer synthetische Daten.
Muster A: API-Test mit TestClient + sauberer Env
Seit 2026-07-17 zentral erledigt: tests/conftest.py räumt die
MONGODB_*-Env pro Test autouse weg und neutralisiert die beim Import
initialisierten Singletons (experiment_logger, submission_store,
mongo._client). Anlass: Zwei lokale pytest-Läufe hatten über die Root-.env
real in die Produktions-Collections geschrieben (48 Test-Dokumente; Cleanup:
scripts/cleanup_test_artifacts_20260717.py) — gleicher Mechanismus wie die
find_dotenv-Falle beim Lasttest (2026-07-10). Nebeneffekt des Fixes: Suite
läuft in ~2 s statt ~20 s (die 2-Sekunden-Stalls waren echte
Atlas-Verbindungsversuche). Einzelne Tests müssen die Env also nicht mehr
selbst wegräumen; das explizite Muster unten bleibt korrekt und schadet nicht.
Hintergrund, warum das nötig war: Die Stores verbinden sich sonst real
(backend/db/mongo.py baut die URI aus MONGODB_URI ODER
MONGODB_MAS_NAME+MONGODB_MAS_KEY+MONGODB_HOST/_CLUSTER_HOST/
_CLUSTER; MongoClient(..., serverSelectionTimeoutMS=2000)).
import pytest
from fastapi.testclient import TestClient
from backend.main import app
API_KEY = "test-admin-key"
@pytest.fixture()
def client(monkeypatch):
for var in ("MONGODB_URI", "MONGODB_MAS_NAME", "MONGODB_MAS_KEY", "MONGODB_HOST"):
monkeypatch.delenv(var, raising=False)
monkeypatch.delenv("STUDENT_ACCESS_CODE", raising=False)
monkeypatch.setenv("TOADAPT_API_KEY", API_KEY)
return TestClient(app)
def test_example(client):
res = client.get("/dashboard/overview", headers={"X-API-Key": API_KEY})
assert res.status_code == 200
Vorlage im Repo: tests/test_case_editor.py (Fixture env).
Muster B: Datei-Stores auf tmp_path isolieren
Die Datei-Fallback-Stores schreiben sonst in den Working Tree
(backend/cases/pool/, backend/db/submissions/). Modul-Konstanten patchen:
import backend.cases.manager as manager_module
import backend.db.dashboard_store as dashboard_store_module
monkeypatch.setattr(manager_module, "POOL_DIR", tmp_path)
monkeypatch.setattr(dashboard_store_module, "RESULTS_DIR", tmp_path)
(Analog existiert RUNTIME_SUBMISSIONS_DIR in backend/db/submission_store.py.)
Muster C: LLM mocken
Genau EIN Interceptions-Punkt: OpenRouterClient.complete. Signatur ist
keyword-only — exakt so übernehmen:
monkeypatch.setenv("OPENROUTER_API_KEY", "test-key")
async def fake_complete(self, *, system, messages, max_tokens):
return '{"section_id": "s1", "title": "Neuer Titel", "content": "Neuer Inhalt."}'
monkeypatch.setattr("backend.llm.OpenRouterClient.complete", fake_complete)
Vorlage: tests/test_case_editor.py::test_regenerate_section_replaces_content.
Muster D: Synthetische Submissions für Dashboard-/Aggregations-Tests
Ergebnis-JSONs direkt in den (auf tmp_path gepatchten) Dateistore legen —
mit erfundenen Matrikelnummern, nie mit echten Daten:
(tmp_path / "r0.json").write_text(json.dumps({
"submission_id": "sub-1001-38.0", "matrikelnummer": "1001",
"case_id": "case-1", "target_tp": 1, "percentage": 38.0,
"evaluated_at": "2026-07-01T10:00:00",
"scores": [{"question_id": "q1", "bloom_level": 4, "max_points": 8,
"awarded_points": 2, "feedback": "…",
"learning_objective_tags": ["wirkungskette"],
"main_penalties": [], "missing_canvas_blocks": [],
"needs_human_review": False, "evaluation_status": "ok"}],
}), encoding="utf-8")
Vorlage: tests/test_dashboard_difficulties.py.
Muster E: scripts/ testen (kein Package)
scripts/*.py sind keine importierbaren Module — per importlib laden:
import importlib.util, sys
from pathlib import Path
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "mein_skript.py"
SPEC = importlib.util.spec_from_file_location("mein_skript", SCRIPT_PATH)
MODULE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MODULE
SPEC.loader.exec_module(MODULE)
Vorlage: tests/test_publish_dashboard_scores.py.
Guardrail-Regressionstests (Stufe 3)
Wenn du Guardrail-Patterns oder Agenten-Prompts änderst: Für JEDES neue
verbotene Muster einen Fall in tests/test_orchestrator_guardrails.py
ergänzen (und einen Positivfall, der NICHT blocken darf):
from backend.agents.orchestrator import guardrail_check
def test_guardrail_blocks_neues_muster():
ok, reason = guardrail_check("<verbotener Beispieltext>", 2)
assert ok is False
Achtung, bekannte Lücke: TP_CONFIGS[4] hat keinen
forbidden_framework_names-Key — in TP4 greifen nur die globalen Patterns.
Tests für TP-spezifische Verbote also mit tp 1–3 schreiben oder die Lücke
bewusst adressieren (dann: toadapt-change-control).
5. CI-Anatomie (.github/workflows/ci.yml, Stand 2026-07-08)
Läuft bei jedem Push auf main und bei Pull Requests. Zwei Jobs:
| Job | Umgebung | Schritte |
|---|
| backend | Python 3.11 (Prod-Version; lokal läuft 3.13) | pip install -r requirements.txt pytest pytest-asyncio ruff → ruff check . → pytest tests/ -q mit PYTHONPATH: . |
| frontend | Node 22, cwd frontend/ | npm ci → npm run lint (eslint) → npx tsc --noEmit → npm run build |
Wissenswert:
- pytest/pytest-asyncio/ruff sind dev-only — bewusst NICHT in
requirements.txt (die ist die Prod-Dependency-Liste für Railway). CI
installiert sie explizit dazu. Neue Test-Dependencies müssen in die
CI-Install-Zeile, nicht in requirements.txt.
- Die PYTHONPATH-Falle:
tests/ hat kein __init__.py und es gibt kein
conftest.py im Root — ein nacktes pytest tests/ -q findet das Package
backend NICHT (8 Collection-Errors, lokal am 2026-07-08 reproduziert).
Zwei funktionierende Varianten: python -m pytest tests/ -q vom Repo-Root
(fügt CWD zu sys.path hinzu) oder PYTHONPATH=. pytest tests/ -q (so
macht es CI). Wenn CI-pytest scheitert, lokal aber läuft: zuerst prüfen,
ob der env: PYTHONPATH: .-Block im Workflow noch da ist.
- Ruff-Konfig in
pyproject.toml: line-length 100, target py311, und
absichtliche E402-Ignores für backend/main.py und
backend/db/submission_store.py (dort MUSS load_dotenv() vor den
Imports laufen — Hintergrund: toadapt-failure-archaeology). Diese
Ignores nicht "aufräumen".
- CI ersetzt keine Stufe-2–4-Evidenz: sie startet keinen Server, ruft kein
LLM und misst kein Alignment.
6. Akzeptanz-Schwellen
Test-Baseline
- 131 Tests grün ist die Basis (Stand 2026-07-11; vorher 48→90) — nie
unterschreiten.
Tests dürfen nur ersetzt/entfernt werden, wenn das getestete Feature selbst
entfernt wird (dann:
toadapt-change-control). Neue Features ohne neuen
Test sind ein Review-Smell.
ruff check . und Frontend-Lint/tsc/build müssen sauber sein, bevor
gepusht wird — CI läuft auf jedem Push auf main (Solo-Workflow, keine
PR-Pflicht, d. h. rote CI landet sonst direkt "in Produktion").
Alignment-Bar (für Judge-/Rubric-/Prompt-Änderungen, Stufe 4)
ACHTUNG Scope-Differenz: Es gibt ZWEI Baseline-Zahlensätze für dieselbe
Studie. Der Report (docs/teacher_alignment_report_20260531_17submissions.md;
Dateiname sagt 17, Scope sind 16 Submissions / 64 Frage-Zeilen) nennt
r 0.796 / MAE 2.711 / RMSE 3.897 / Mean Diff −2.07 — aber sein 64er-Filter
existiert NICHT als Skript im Repo (ad-hoc, nicht reproduzierbar). Die
unten stehende Pipeline rechnet über alle 67 gematchten Lehrer-Zeilen
und liefert für den UNVERÄNDERTEN Judge r 0.777 / MAE 2.784 / RMSE 3.921 /
Mean Diff −1.784 (am 2026-07-08 real reproduziert, siehe
toadapt-judge-alignment-campaign §2 — dort ist auch das exakte GATE).
Maßgeblich für den Vergleich ist die reproduzierbare 67er-Skript-Baseline;
wer die 64er-Report-Werte als Bar nimmt, lehnt eine neutrale Änderung
fälschlich als "Verschlechterung" ab. q4 ist in beiden Scopes deckungsgleich.
| Metrik | 67er-Skript-Baseline (kalibrierter Judge) | Bar für jede neue Judge-Version |
|---|
| Pearson r (gesamt) | 0.777 | ≥ 0.78 halten (Gate der Kampagne) — keine Verschlechterung akzeptieren |
| MAE (Punkte, gesamt) | 2.784 | nicht verschlechtern |
| RMSE (Punkte) | 3.921 | nicht verschlechtern (sekundär) |
| Mean Diff Judge−Teacher | −1.784 (Judge systematisch strenger) | nicht weiter ins Negative |
| q4 (Bloom 6): MAE | 4.969 (in beiden Scopes identisch), systematische Unterbewertung | Zielwerte und Vorgehen: toadapt-judge-alignment-campaign |
Pipeline, um die Metriken für eine geänderte Judge-Version zu erzeugen
(Details und Datenherkunft: toadapt-run-and-operate; NIE mit echten
Teilnehmerdaten im Repo arbeiten — die liegen unter ~/ToAdapt_sensitive_data/
und bleiben dort):
python scripts/export_review_workbooks.py \
--submissions <submission_states.json> --events <experiment_events.json> \
--cases-dir backend/cases/pool --output-dir <out> --prefix review
python scripts/compare_teacher_rubric_scores.py \
--teacher-workbook <teacher.xlsx> --rubric-workbook <out>/review_*_rubric.xlsx \
--output-dir <out>
Das Lehrer-Workbook ist der kanonische Scope: Zeilen, die nur der Judge
bewertet hat, werden by design ausgeschlossen (getestet in
tests/test_compare_teacher_rubric_scores.py).
Deploy einer Judge-Änderung OHNE diesen Durchlauf verstößt gegen die
Projekt-Doktrin — siehe toadapt-change-control.
Provenance und Wartung
Update 2026-07-11 (HEAD 324d937): Test-Baseline 90 → 131; Landkarte um
test_group_uploads (14), test_llm_client (9), test_group_code_validation (9),
test_load_test_tools (6) ergänzt. Neues Muster darin: LLM-Stub-Antworten
werden gegen die ECHTEN Pfade getestet (guardrail_check,
parse_evaluation_payload), damit Lasttests reale Verarbeitung messen.
Erstellt: 2026-07-08. Alle Fakten am 2026-07-08 gegen das Repo verifiziert
(48/48 Tests lokal grün, ruff sauber, PYTHONPATH-Falle reproduziert).
Update 2026-07-09 (HEAD 64b62f9): Test-Baseline 48 → 90 (lokal reproduziert);
Landkarte um test_case_package (13), test_formative_support (7),
test_groups_and_privacy (10), test_tutor_eval (8) ergänzt;
test_rubric_evaluator 7 → 11 (Repair-Kette, Typ-Fallback,
Parallel-Reihenfolge, Prompt-Pinning); Golden Case trägt eingebettetes
Bewertungspaket, Äquivalenz-Tests in test_case_package.py sind das Gate;
Kalibrierungsanker aus dem Evaluator-Code in die Case-JSONs migriert
(+ generische BLOOM_CALIBRATION_ANCHORS) — Anker-Vererbungs-Schwäche BEHOBEN;
Evidenz-Staffel um Stufe 3b (Tutor-Eval-Regressionsvergleich, Querverweis
toadapt-tutor-response-evaluation) erweitert; /dashboard/difficulties
verlangt zusätzlich X-Research-Key.
Re-Verifikation drift-anfälliger Fakten (je ein Kommando, vom Repo-Root):
| Fakt | Kommando |
|---|
| Testanzahl (Baseline 90) | .venv/bin/python -m pytest tests/ -q | tail -1 |
| Test-Dateiliste | ls tests/test_*.py |
| CI-Schritte + PYTHONPATH-Env | cat .github/workflows/ci.yml |
| Golden Case einziger Pool-Inhalt | ls backend/cases/pool/ |
| q1–q4 Bloom/Punkte des Golden Case | python3 -c "import json;[print(q['question_id'],q['bloom_level'],q['max_points'],q['rubric_reference']) for q in json.load(open('backend/cases/pool/alpes-bank-genai-001.json'))['questions']]" |
| Kalibrierungsanker zweistufig (Case-Notes vor Bloom-Ankern) | grep -n 'calibration_notes|BLOOM_CALIBRATION_ANCHORS' backend/evaluator/rubric_evaluator.py |
| Golden-Case-Anker im Case-JSON gepinnt | grep -c calibration_notes backend/cases/pool/alpes-bank-genai-001.json (>0) |
| Rubric-Schwellen | grep -n 'threshold_pct|score_floor_pct' backend/config/rubrics/*.json |
| TP4-Guardrail-Lücke besteht noch | grep -c forbidden_framework_names backend/config/tp_configs.py (3 = Lücke besteht, 4 = geschlossen) |
| LLM-Mock-Signatur unverändert | grep -n 'async def complete' -A 6 backend/llm.py |
| Mongo-2s-Timeout | grep -n serverSelectionTimeoutMS backend/db/mongo.py |
| Alignment-Referenzmetriken | grep -nE 'Pearson|MAE' docs/teacher_alignment_report_20260531_17submissions.md |
| Glossar weiterhin frontend-hartkodiert | grep -n CASE_GLOSSARY "frontend/app/cases/[id]/page.tsx" |