docs: retire superpowers/ layout — keep design spec under docs/specs/

The superpowers workflow methodology is no longer in use. Move the
methodology-neutral design spec to docs/specs/ as the project's design
ledger, drop the superpowers-bound implementation plan (it referenced
superpowers sub-skills), and remove the now-dead /docs/superpowers/
.gitignore entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 18:02:38 +02:00
parent 1f4607ced1
commit ca352aafc7
3 changed files with 0 additions and 2517 deletions
@@ -0,0 +1,228 @@
# Design-Spec: alpha-id — ICD-Code-Vorschläge aus Diktat
**Datum:** 2026-05-18
**Status:** PoC implementiert; **Input-Vertrag revidiert 2026-05-18** (siehe §1a) — Eingabe ist nicht mehr das leerzeilen-segmentierte Diktat, sondern eine Liste vom Caller **extrahierter Diagnose-Formulierungen**.
**Stack:** Rust — Core-Library + dünne CLI (PoC)
---
## 1. Ziel & Kontext
Aus einem (bereits transkribierten, fehlerbehafteten) ärztlichen Diktat eine **einzige priorisierte Liste von max. 10 ICD-10-GM-Codes** mit Beschreibung erzeugen, mit Filterung nach Tags.
Zwei Zwecke:
1. **Vergessene abrechenbare Codes aufdecken** — im Stress vergessene Codes kosten Erlös. Das ist primär ein **Recall-Problem**. Leitmetrik: **Recall@10 abrechenbarer Codes**. Mit dem revidierten Input-Vertrag (§1a) ist der Recall *dieser Komponente* nach oben durch den **Extraktions-Recall** begrenzt: was der Caller nicht als Formulierung übergibt, kann der Matcher nicht retten. Der Ziel-1-Nutzen bleibt nur erhalten, wenn der Extraktionsvertrag **großzügig** ist (jede Diagnose-*Erwähnung* extrahieren, auch beiläufige in Anamnese/Verlauf — nicht nur das, was der Arzt bereits explizit als Diagnose kodiert).
2. **Hilfreiche klinische Impulse** — die bloße Sichtbarkeit verwandter Einträge (ICD-Hierarchie-Geschwister, Alpha-ID-Synonyme) erweitert das Bild und wirkt absichernd.
Das System ist ein **PoC**. Übernahme in das Schwesterprojekt `doctate` erfolgt **nur, wenn es nachweisbar taugt** (gemessen am Eval-Harness).
### I/O-Vertrag (revidiert 2026-05-18)
- **Eingabe:** eine **Liste vom Caller extrahierter Diagnose-Formulierungen**, eine pro Zeile (newline-getrennt; eine Zeile = eine Retrieval-Einheit). *Nicht* mehr das komplette, leerzeilen-segmentierte Diktat. Die Extraktion leistet das **LLM in doctate**; **dieses Projekt enthält weiterhin keinen generativen LLM** — es bekommt nur einen saubereren Input. Begründung siehe §1a.
- **Ausgabe:** **eine** priorisierte Liste von max. 10 ICD-Codes **für die gesamte Eingabe** — nicht pro Zeile. Pro Formulierung wird einzeln retrievet/gerankt, danach global fusioniert/dedupliziert.
### 1a. Warum extrahierte Formulierungen statt Diktat-Absätze (Revision 2026-05-18)
**Belegter Befund (Source-String-Analyse, 2026-05-18):** Der Match-Korpus (Alpha-ID Feld 8) besteht aus **knappen** Diagnose-Strings (24 Wörter, z. B. `I10.90 Arterielle Hypertonie`). Ein realer Befund-Absatz ist eine 1525-Wort-Anamnese-Prosa, in der die Diagnose von nicht-diagnostischen Tokens (Alter, Medikament, Messwerte) umgeben ist. `bge-m3` mittelt den ganzen Absatz → der Query-Vektor wird vom Kontext dominiert, nicht vom Diagnosekern; die knappe Ziel-Phrase liegt zwar im Korpus, wird aber von Mess-/Kontext-„Fallen" (z. B. `R03.0 Erhöhter Blutdruckwert ohne Diagnose`) verdrängt. Dies ist **kein Bug**, sondern eine strukturelle Folge der Absatz-als-Einheit-Repräsentation — durch keine Fusions-/Reranker-Verbesserung behebbar.
**Auflösung:** Der Caller (doctate-LLM) extrahiert die relevanten Diagnose-Formulierungen; nur diese knappen Phrasen werden gematcht. Das stellt die **Kurz↔Kurz-Symmetrie** her — genau das Regime, in dem dichtes Retrieval + Reranker stark sind. Scope bleibt sauber: kein Mode B, kein LLM in diesem Projekt, nur ein präziserer Eingabevertrag. Der Pipeline-Kern (Lexik Semantik → RRF → Reranker → Cross-Einheit-Dedupe → Tag-Filter → Top-10) bleibt unverändert; lediglich die Einheiten-Bildung wechselt von „Split an Leerzeilen" zu „eine Formulierung pro Zeile".
**Extraktionsvertrag (Verantwortung von doctate, nicht dieses Projekts):**
- **Großzügig:** jede Diagnose-/Befund-*Erwähnung* aus dem gesamten Diktat (Anamnese, Verlauf, Beurteilung) als eigene Zeile — auch beiläufige und Nebendiagnosen. Ziel 1 lebt von genau den nur beiläufig erwähnten, nicht kodierten Diagnosen.
- **Knapp & diagnosezentriert:** die Diagnose-Nominalphrase, ohne Anamnese-Floskeln, Medikamente, Messwerte, Datumsangaben. Negationen/Ausschlüsse („kein Hinweis auf …") **nicht** als Diagnose extrahieren (das Projekt verarbeitet Negation nicht).
- **Wörtlich genug:** nahe an der Diktatformulierung; leichte Transkriptionsfehler sind toleriert (Lexik/Semantik sind dafür robust, Umlaut-Faltung ist implementiert).
- **Keine Codes, keine Wertung:** doctate liefert Text, nicht ICD; Priorisierung/Abrechenbarkeit entscheidet diese Komponente.
**Ehrlicher Trade-off (vom Nutzer bewusst akzeptiert):** Der Extraktor wird zum alleinigen Recall-Flaschenhals (§1, Ziel 1). Konsequenz für das Eval (§6): der Harness muss Extraktor-Verhalten modellieren (Auslassungen, Paraphrasen), nicht nur Transkriptionsfehler.
### Datenquellen (vorhanden in `/home/brummel/dev/alpha-id/`)
- `data/icd10gm2026_alphaidse_edvtxt_20250926.txt` — Alpha-ID-SE, 90.898 Sätze, 8 `|`-getrennte Felder (UTF-8). Feld 8 = ~90k natürlichsprachliche Diagnose-Formulierungen inkl. Synonyme (primärer Match-Korpus). Feld 1 = Gültigkeit (0/1). Felder 36 = ICD-Codes (Primär/Stern/Ausrufezeichen/Primär2). Feld 7 = Orpha.
- `icd-claml/Klassifikationsdateien/icd10gm2026syst_claml_20250912.xml` — ICD-10-GM 2026 als ClaML 2.0.0 (14 MB): Beschreibungen, Hierarchie, `<Meta>`-Tags (Para295/Para301, Kapitel/Gruppe, Exotic, Sex, Age, IfSG, Content).
### Modelle / Infrastruktur
IONOS AI Model Hub (`https://openai.inference.de-txl.ionos.com/v1`), Token `~/.ionos_token`. Nur zwei Modelle:
- **Embeddings:** `BAAI/bge-m3` (multilingual, deutscher Medizintext)
- **Reranker:** `Qwen/Qwen3-VL-Reranker-8B` (Cross-Encoder)
Kein generativer Chat-LLM. IONOS-Client-Pattern aus `doctate/server/src/analyze/backend.rs` und `doctate/docs/ionos-llm-api.md` wiederverwenden (Token-Handling, Retry, Quirks).
---
## 2. Architektur
Zwei Modi über Config-Schalter, **gleiche Schnittstelle**, im selben Eval-Harness vergleichbar:
- **Modus C — Lexik-Baseline:** BM25/Trigramm + Tag-Filter. Kein IONOS, offline, sofort. Die Baseline, die A schlagen muss.
- **Modus A — Hybrid + Reranker (Kern):** Lexik Semantik (`bge-m3`) → `Qwen3-VL-Reranker-8B` → Fusion → Tag-Filter → Top-10.
**Modus B (generativer LLM-Rerank/-Vorschlag) ist explizit NICHT Scope.** Embedding- und Reranker-Modelle sind keine generativen LLMs und bleiben in A.
### Datenfluss
```
Eingabe = extrahierte Diagnose-Formulierungen (eine pro Zeile)
▼ [1] Parser → Einheiten (eine nicht-leere Zeile = eine
│ Formulierung; im Code weiterhin „segment" genannt)
▼ [2] Normalisierung → pro Einheit: lowercasing, Medizin-Abkürzungen,
│ Umlaut-Faltung, Tokenisierung (kein LLM)
├─►[3a] Lexik (tantivy BM25/Trigramm) ┐ pro Einheit:
└─►[3b] Semantik (bge-m3-Query-Embedding vs. ┘ Pool ~50100 (RRF-Mix)
vorab persistierter Vektorindex) [A; in C nur 3a]
▼ [4] Reranker → Qwen3-VL-Reranker-8B: (Formulierung, Alpha-ID-Text)
│ paarweise → rerank-Score [nur A]
▼ [5] Fusion → alle Einheiten-Kandidaten in EINE Liste; je
│ normalisiertem ICD-Code dedupliziert
│ (score=max, alle Herkünfte erhalten)
▼ [6] Tag-Filter → harte Constraints: Abrechenbarkeit (Para295/301),
│ Gültigkeit (Feld 1), Kapitel/Gruppe,
│ klinische Marker (Exot/Sex/Alter/IfSG/Content)
▼ [7] Top-10 → globales Budget; je Treffer: Code, Beschreibung,
Score, Quell-Phrase, Quell-Segmente, Tags
```
### Module (klar abgegrenzt)
| Modul | Aufgabe | Abhängigkeit |
|---|---|---|
| `corpus` | Alpha-ID parsen → In-Memory-Index (ICD↔Phrasen, Gültigkeit, Orpha) | `data/` |
| `claml` | ClaML-XML einmal parsen → `code → IcdMeta` | `icd-claml/` |
| `embed` | IONOS `bge-m3`-Client (Batch, Cache, Retry, Resume) | IONOS |
| `rerank` | IONOS `Qwen3-VL-Reranker-8B`-Client (Paar-Scoring, Batch, Retry) | IONOS |
| `index` | Lexik-Index (tantivy) + persistierter Vektorindex (ANN) bauen/laden | `corpus`,`embed` |
| `tags` | Filter-Prädikate aus ClaML-Meta + Alpha-ID-Gültigkeit | `claml` |
| `pipeline` | Orchestrierung [1][7], Modus C/A per Config | alle obigen |
| `eval` | Gold-Set-Generator + Recall@k-Metrik | `pipeline` |
| `bin` (CLI) | dünner Wrapper: `index`, `suggest`, `eval` | `pipeline`,`eval` |
Jedes Modul ist für sich testbar; `pipeline` ist die einzige Stelle, die Modi kennt. Die Core-Library bleibt frei von CLI-Belangen, damit doctate sie später unverändert einbinden kann.
---
## 3. Datenmodell (Kerntypen)
```
AlphaIdEntry { alpha_id, valid: bool, icd_primary, icd_star,
icd_addon, icd_primary2, orpha, text }
// icd_addon = Zusatzschlüssel (Feld 5, „!"); NICHT ICD-Exklusiva
IcdMeta { code, description, chapter, group, para295, para301,
exotic, sex, age_low, age_high, ifsg, content }
Candidate { icd_code, alpha_text, segment_idx,
lexical: Option<f32>, semantic: Option<f32>,
rerank: Option<f32> }
Suggestion { icd_code, description, score, matched_phrase,
source_segments: Vec<usize>, tags: Tags }
Filter { billable_only, valid_only, chapters: Option<Set>,
exclude_exotic, … }
Config { mode: C|A, top_k, pool_size, filter, ionos }
```
**Dedupe-Schlüssel = normalisierter ICD-Code** (ohne Kreuz `+`, Stern `*`, Ausrufezeichen `!`). Ein getroffener Alpha-ID-Eintrag steuert seinen Primärcode bei; Stern-/`!`-Codes werden als ergänzend markiert. Abrechenbarkeit entscheidet der Tag-Filter (Schritt 6), nicht der Match.
**Terminologie-Hinweis (Revision 2026-05-18):** Eine **Einheit** = eine extrahierte Diagnose-Formulierung (eine Eingabezeile). Die Code-Feldnamen `segment_idx` / `source_segments` (sowie `split_segments`) bleiben aus DRY-/Churn-Gründen unverändert; „segment" bezeichnet im Code ab dieser Revision diese Formulierungs-Einheit, nicht mehr einen leerzeilen-getrennten Befund-Absatz. Bewusste Entscheidung: der Vertragswechsel ist semantisch groß, der Code-Eingriff minimal (nur die Einheiten-Bildung in `segment.rs`).
### Fusion (Schritt 5, präzisiert)
- **Innerhalb** eines Segments: Kandidatenpool = Lexik-Top-N Semantik-Top-N, gemischt per **Reciprocal Rank Fusion (RRF)**. In Modus C ist die RRF-Ordnung die finale Vor-Filter-Reihung; in A folgt der Reranker.
- **Über** Segmente: dedupliziert je normalisiertem ICD-Code, `score = max` über alle Vorkommen, **alle Herkunfts-(Segment, Phrase) erhalten**. Zahl getroffener Segmente = Tiebreaker und „Breiten"-Signal für Ziel 2.
---
## 3a. ICD-Code-Granularität & ClaML-Subklassifikation (Revision 2026-05-18)
**Befund (an Echtdaten belegt):** ~26,5 % der gültigen Alpha-ID-Primärcodes sind 6-stellig (5. ICD-Stelle, z. B. `E11.72`, `I10.91`). ClaML modelliert die 4./5. Stelle vieler Gruppen — u. a. **alle Diabetes-Codes E10E14****nicht** als eigene `<Class kind="category">`, sondern über `<Modifier>`/`<ModifierClass>` (Subklassifikation), referenziert per `<ModifiedBy code=… all=…>` am übergeordneten `<Class>`. Ein naiver `meta.get(code)` (bzw. Strip-/Root-Fallback) klassifiziert dadurch **1.193 distinkte gültige Codes (≈ 4.394 Alpha-ID-Zeilen)** fälschlich als nicht-abrechenbar (Rückfall auf 3-Steller mit `Para295=V`) → unter `--billable-only` werden real abrechenbare Codes still verworfen. **Das untergräbt Ziel 1.**
**Entschiedene Auflösung (Nutzer, 2026-05-18): korrekte ClaML-Modifier-Expansion.** `claml.rs` wertet zusätzlich `<Modifier>` (mit `<SubClass>`), `<ModifierClass>` (mit `<SuperClass>`, preferred `<Rubric><Label>`, ggf. eigenem `<ModifiedBy>` zur Verkettung der nächsten Stelle) und `<ModifiedBy>` an `<Class>` aus. Für jeden `<Class>` mit `ModifiedBy`-Referenzen werden die terminalen Stellen-Kombinationen synthetisiert (4. dann 5. Stelle, der ClaML-Subklassifikations-Algorithmus) und als zusätzliche `IcdMeta` unter dem kombinierten Code (z. B. `E11`+`.7`+`2``E11.72`) in die Map eingetragen. Regeln:
- Explizite `<Class>`-Einträge haben **Vorrang**; Synthese füllt nur fehlende Schlüssel (additiv, kein Überschreiben).
- **Abrechenbarkeit:** Terminale synthetisierte 5.-Stellen-Codes dieser modifizierten Gruppen sind `Para295=P`/`Para301=P` (der bare 3-Steller bleibt `V`). `Content` des Parent (`J` = „erfordert Subklassifikation") ist das Signal, dass erst die terminale Stelle kodierbar ist.
- **Beschreibung:** zusammengesetzt aus Parent-`preferred`-Label + 4.-Stellen-ModifierClass-Label + 5.-Stellen-ModifierClass-Label.
- `chapter`/`exotic`/`ifsg` vom Parent geerbt, sofern der Modifier nichts Spezifischeres trägt.
- Keine kartesische Explosion über fremde Modifier: nur die vom jeweiligen `<Class>` per `ModifiedBy` (in Reihenfolge) referenzierten Modifier kombinieren; ModifierClass-eigene `ModifiedBy` verketten die nächste Stelle.
Alle nötigen Informationen liegen **allein in der ClaML-XML** (die gelöschte Meta-TXT ist nicht erforderlich). Konsequenz im Plan: eigener Task „ClaML-Modifier-Expansion" + Revision des Mode-C-Pipeline-Lookups (kein Strip-/Root-Hack mehr) + Abrechenbarkeits-Regressionstest (z. B. E11.x unter `--billable-only` sichtbar & `billable=true`).
---
## 4. Einmaliges, kostensicheres Embedding (kritische Anforderung)
Das Embedden von ~90k Texten via IONOS kostet Geld und soll **genau einmal** erfolgen. Maßnahmen:
1. **Content-Hash-gekeyter, persistenter Cache.** Jeder Alpha-ID-Text → Embedding, gekeyt durch einen stabilen Hash (z. B. SHA-256 von normalisiertem Text + Modellname + Modellversion). Persistenz on-disk (z. B. `index/embeddings.db` o. ä.). Vor jedem Embed-Aufruf Cache-Lookup; nur fehlende Keys werden gesendet.
2. **Resumierbar / idempotent.** Bricht der Full-Run ab (Crash, Netz, Timeout), setzt der nächste Lauf bei den fehlenden Keys fort — **kein Neustart von 0**, kein doppelt bezahltes Token.
3. **Cost-Guard vor dem Full-Run.** `index build` schätzt zuerst Items, Tokens und Kosten und zeigt sie an; der **Full-Run erfordert explizite Bestätigung** (`--confirm` Flag oder interaktiv).
4. **Verpflichtender Smoke-Test zuerst.** `index build --sample N` (Default klein, z. B. 50) embedded nur ein Sample, validiert: HTTP-OK, korrekte Vektordimension, Cache schreibt/liest, ANN-Index baut, eine Beispiel-`suggest`-Abfrage liefert plausible Treffer. **Erst nach grünem Sample** wird der Full-Run freigegeben.
5. **Delta-fähig.** Ändert sich künftig der Korpus (neue Alpha-ID-Jahresversion), werden nur geänderte/neue Texte nachembedded — der Hash-Key macht das automatisch.
Reihenfolge im Staging: **C zuerst** (braucht kein Embedding, sofort lauffähig + Eval-Harness), dann **A-Embedding-Pfad am Sample verifiziert**, dann **ein einziger** Full-Embedding-Run.
---
## 5. Fehlerbehandlung
**Leitprinzip: degradieren, nie hart scheitern** — fehlende Codes kosten Geld; der Arzt bekommt *immer* eine Liste.
- IONOS Timeout/5xx: Retry mit Backoff (doctate-Pattern). Dauerhafter Ausfall in Modus A → **automatischer Fallback auf Modus C**; Ergebnis trägt `degraded: true`.
- Token `~/.ionos_token` fehlt → klare Meldung; A nicht verfügbar, C läuft.
- Leeres/kaputtes Diktat → leeres Ergebnis mit Begründung. Segment ohne Kandidaten → übersprungen, in `diagnostics` vermerkt.
- Korpus-/ClaML-Ladefehler → **harter Startfehler** (ohne Daten kein Betrieb).
- **Keine Halluzination per Konstruktion:** Treffer stammen ausschließlich aus Korpus-Einträgen; der Reranker sortiert nur um, erfindet nichts.
---
## 6. Eval-Harness (erstklassige Komponente)
Kein gelabeltes Datenmaterial vorhanden → der Harness ist Teil des PoC, kein Nachgedanke. Der Alpha-ID-Korpus ist sein eigenes Gold-Set (jeder Text hat einen bekannten ICD-Code).
**Repräsentativitäts-Konsequenz des Vertragswechsels (wichtig, ehrlich):** Unter dem alten Diktat-Absatz-Vertrag war der synthetische Eval *nicht* repräsentativ — er verrauschte kurze Korpus-Strings und matchte gegen denselben Korpus (Kurz↔Kurz), während der reale Input lange Prosa war; die so gemessene Zahl (erster Lauf: Modus C 0,520 vs. A **0,995** BillableRecall@10) **überschätzte** die Realität (Self-Retrieval-Kontamination). Unter dem neuen Vertrag ist der reale Input *selbst* eine knappe Formulierung → das Kurz↔Kurz-Regime des Harness wird **legitim repräsentativ** für die *Rausch-/Varianten-Robustheit*. Damit verbleibt als dominante Unbekannte nicht mehr die Längen-Asymmetrie, sondern das **Extraktor-Verhalten**.
- **Generator (Rausch- & Varianten-Achse):** sampelt gültige, abrechenbare Alpha-ID-Einträge → `(phrase, erwarteter ICD)`. Injiziert **deterministisch (Seed)**: (a) Transkriptionsfehler (deutsche Medizin-Homophone, Wortgrenzen-Merge/Split, Zifferndreher, Umlaut↔ae/oe/ue, ASR-Verwechslungen) **und (neu) (b) Extraktor-Paraphrase**: Synonym-/Wortstellungs-/Knappheits-Varianten der Formulierung (nicht nur die wörtliche Korpus-Phrase), damit nicht die exakte Korpus-Zeichenkette getestet wird.
- **Plus** ein kleines, handgelabeltes Set: **realistische, extrahierte Formulierungs-Listen** (eine Phrase pro Zeile — die *neue* Eingabeform), gepaart mit dem Gold-Satz abrechenbarer ICD-Codes, den ein Kodierer dafür vergäbe. Zu jedem Beispiel wird **zusätzlich das Quelldiktat** abgelegt, um die **Extraktions-Recall-Decke** zu dokumentieren (= Anteil der Gold-Diagnosen, die ein großzügiger Extraktor aus dem Diktat überhaupt als Zeile liefern würde). Diese Decke ist **doctates Verantwortung, nicht die dieser Komponente** — wird gemessen/berichtet, aber nicht hier optimiert.
- **Metriken:** Recall@1/5/10 gesamt; **gesondert Recall@10 abrechenbarer Codes** (Leitzahl Ziel 1) — gemessen *gegeben die extrahierten Formulierungen*; Modus C vs. A nebeneinander; **separat ausgewiesen:** Extraktions-Recall-Decke (Diktat→Formulierungen) vs. Matcher-Recall (Formulierungen→Codes), damit die zwei Recall-Verluste nicht vermischt werden; Latenz + IONOS-Calls/Kosten; Breiten-Maß (distinkte assoziierte Codes) für Ziel 2.
„Taugt's" ist eine **Go/No-Go-Entscheidung des Nutzers** anhand der Eval-Tabelle: A vs. C bei Recall@10 abrechenbarer Codes auf dem rausch-+varianten-injizierten Gold-Set **und** gegen das handgelabelte Formulierungs-Set, abgewogen gegen Latenz/IONOS-Kosten und die separat berichtete Extraktions-Decke. Eine feste numerische Schwelle wird bewusst **nicht** erfunden; sie wird nach dem ersten Eval-Lauf gemeinsam festgelegt.
---
## 7. CLI-Oberfläche
```
alpha-id index build [--sample N | --full] [--confirm]
# Smoke-Test (Sample) bzw. einmaliger Full-Run;
# Korpus+ClaML parsen, bge-m3-Embeddings,
# Lexik-+Vektorindex persistieren (resumierbar)
alpha-id suggest [FILE|-] # Formulierungs-Liste (eine pro Zeile) → Top-10
--mode c|a --top 10 --billable-only --valid-only
--chapters I,J --exclude-exotic --json|--table
alpha-id eval --mode c|a --cases N --seed S [--gold PATH]
```
- Config-TOML: IONOS-Endpoint/Modellnamen/Pool-Größen/Pfad-Defaults, per Flags überschreibbar.
- `suggest`-Ausgabe (JSON) je Treffer: `icd_code, description, score, matched_phrase, source_segments, tags{billable, chapter, exotic, …}` + Top-Level `diagnostics{ mode, degraded, timings, segment_count, ionos_calls }`.
---
## 8. Abgrenzung (Out of Scope)
- Generativer Chat-LLM in der Pipeline (Modus B).
- **Extraktion der Diagnose-Formulierungen aus dem Rohdiktat** — Verantwortung des Callers (doctate-LLM) gemäß Extraktionsvertrag §1a; dieses Projekt segmentiert/extrahiert nicht.
- Negationsverarbeitung („kein Hinweis auf …") — Negierte Diagnosen werden laut Vertrag gar nicht erst extrahiert; der Matcher modelliert Negation nicht.
- Eigene/projektspezifische Tag-Whitelists (nur ClaML-abgeleitete Filter).
- Produktions-Deployment / doctate-Integration (erst nach erfolgreichem PoC).
- UI (CLI genügt für den PoC).
---
## 9. Offene Punkte / Annahmen
- Genaues IONOS-Antwortformat von `bge-m3` (Vektordimension) und `Qwen3-VL-Reranker-8B` (Score-Skala, Batch-API) wird im Sample-Smoke-Test (Abschnitt 4.4) empirisch verifiziert, bevor der Full-Run läuft.
- Wahl der konkreten Rust-Crates (tantivy für Lexik; ANN-Lib z. B. `usearch`/`hnsw_rs`; Embedding-Persistenz) wird im Implementierungsplan fixiert.
- **Revision 2026-05-18 (Input-Vertrag):** Eingabe = extrahierte Diagnose-Formulierungen statt Diktat-Absätze (§1a). Plan-Konsequenz: Task 6 (`segment.rs`) wechselt von „Split an Leerzeilen" zu „eine nicht-leere Zeile = eine Einheit"; Eval-Tasks (11/19) ergänzen Paraphrase-Injektion + handgelabeltes Formulierungs-Set + getrennte Berichterstattung Extraktions-Decke vs. Matcher-Recall. Pipeline-Kern unverändert.
- **Offener Punkt:** Der Extraktor (doctate-LLM) ist der alleinige Recall-Flaschenhals für Ziel 1; sein Real-Recall ist erst mit dem handgelabelten Diktat→Formulierungs-Set quantifizierbar und gehört nicht in dieses Projekt — wird aber getrennt berichtet (§6).
- **Aufgelöst 2026-05-18:** Korpus↔ClaML-Code-Granularität (5. Stelle nur via `<ModifierClass>`) — siehe §3a; korrekte ClaML-Modifier-Expansion beschlossen und in den Plan aufgenommen.
- **Aufgelöst 2026-05-18:** Git-Repository initialisiert; PoC implementiert, auf `main` gemergt, Umlaut-Faltung gefixt, einmaliger IONOS-Embedding-Lauf abgeschlossen (90.896 Vektoren).