Fruehjahrsputz 2/2: Dokumentation auf den tatsaechlichen Stand gebracht

Die Plandokumente waren durchweg veraltet: 85 von 91 Punkten im
UMSETZUNGSPLAN standen auf offen, obwohl der Code sie enthielt, und in
FIXPLAN-UI-Ranglisten war keine einzige der 18 Aufgaben abgehakt, obwohl
beide zugehoerigen Commits laengst im Zweig stecken. Alles abschnittsweise
gegen den Code geprueft und die Haekchen gesetzt - mit Belegstellen, damit
die naechste Pruefung nicht wieder bei null anfaengt.

Neu: STATUS.md als Einstiegsseite - wo das Projekt steht, was fertig ist,
was offen ist, und was beim Aufraeumen bewusst stehengeblieben ist. CLAUDE.md
verweist darauf.

Nachgezogen:
* UMSETZUNGSPLAN.md - 66 Punkte abgehakt. Offen bleiben A4 (Strategie-
  Klassifikation), B2 (Secrets) sowie C3/D1/D2 (SQL-Arbeiten des Nutzers).
* FIXPLAN-UI-Ranglisten.md - abgeschlossen bis auf den als "Optional"
  markierten Pfeilrichtungs-Punkt.
* FIXPLAN-TODO.md - Teil D und F abgehakt; die Test-Baseline "16 gruen"
  auf die heutigen 126 korrigiert. Offen: F5 und zwei Tests aus F6.
* FIXPLAN-G-Speicher.md - G1 bis G4 als erledigt vermerkt, Baseline "39/1"
  korrigiert, Pfad auf die nicht mehr existierende WinFormsHost/appsettings.json
  richtiggestellt.
* docs/PLAN-Linux-Portierung.md - der Watchdog-Warnhinweis war ueberholt
  (DcHeartbeatService meldet an /api/watchdog/v1/ping). Abschnitt 11 empfahl
  noch, mit Phase 0 zu beginnen; jetzt benennt er Phase 5 und 6 als das, was
  wirklich aussteht. Die Randnotiz zu Zugangsdaten in LicenseGuard.cs ist
  gegenstandslos, die Datei laeuft ueber den Deploymentcenter.Client.

Zwei Befunde, die keine Aufraeumarbeit sind und deshalb nur dokumentiert
wurden - beide in STATUS.md Abschnitt 3:

1. In src/Predictalytics.Api/appsettings.json steht ein echter
   OpenRouter-API-Key im Klartext, versioniert seit 7045002. Nicht
   eigenmaechtig entfernt: ohne Rotation beim Anbieter bringt das nichts
   (die History behaelt ihn), wuerde aber die KI-Analyse abschalten.
   Der Schluessel muss zurueckgezogen und neu ausgestellt werden.
2. Die Portierung ist auf keinem Linux-System je ausgefuehrt worden. Die
   Verifikation vom 2026-08-08 lief unter Windows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-23 12:30:42 +02:00
co-authored by Claude Opus 5
parent 55011644a3
commit 1f230fe12c
7 changed files with 381 additions and 112 deletions
+33 -15
View File
@@ -1,9 +1,27 @@
# Fix- und Datenreparatur-Plan (Stand 2026-07-09, Übergabe an Gemini)
> **Abnahmekriterium für alle Code-Änderungen:** `dotnet test src/Predictalytics.Application.Tests` muss
> **16 grün + 1 übersprungen** liefern (der Skip `CheckpointResetAndReplay_DoesNotDoubleCountBalance` ist eine
> dokumentierte, bewusste Entscheidung). Die Assertions der Invarianten-Tests dürfen **nicht** verändert werden —
> sie definieren das Soll-Verhalten. Wenn ein Test rot wird, ist der Code falsch, nicht der Test.
> **Status 2026-08-23: Teil D und Teil F sind umgesetzt**, nachgeprüft gegen den Code.
> Offen sind nur noch drei Detailpunkte, jeweils unten als `- [ ]` stehengeblieben:
> der gezielte Nachlade-Schritt in F5 und die zwei Tests aus F6 (Event-Tag-Vererbung,
> Backfill). Belege:
> * **D3** — `IngestMode` (`Domain/Enums/IngestMode.cs`), Einstufung in
> `TradeHistoryWorker.cs:174-177` inklusive der geforderten Hysterese
> (Rückstufung erst unter halber Schwelle, Richtung `Full` zusätzlich erst nach
> 7 Tagen), wöchentliche Biopsie in `TradeHistoryWorker.cs:127`,
> Aggregations-Buckets in `TradeAggregation.cs`, Trait `not_copyable_hf` in
> `TraderTraitCalculator.cs`. Tests: `IngestModeTests.cs`.
> * **F1/F2** — `CanonicalTags` und `BlacklistTags`/`IsNoiseTag` in
> `Infrastructure/Helpers/MarketCategoryMapper.cs`.
> * **F3/F4** — Tag-Nachladen in `PolymarketProvider.cs:187-196`, Offline-Backfill
> aus Event-Tags in `PredictalyticsHost.cs:114-135`.
>
> **Abnahmekriterium für alle Code-Änderungen:** `dotnet test` muss grün bleiben.
> Die damals notierte Zahl (16 grün + 1 übersprungen) ist überholt — Stand
> 2026-08-23 sind es **126 grün + 1 übersprungen** (der Skip
> `CheckpointResetAndReplay_DoesNotDoubleCountBalance` ist weiterhin eine
> dokumentierte, bewusste Entscheidung). Die Assertions der Invarianten-Tests dürfen
> **nicht** verändert werden — sie definieren das Soll-Verhalten. Wenn ein Test rot
> wird, ist der Code falsch, nicht der Test.
## Hintergrund
@@ -95,35 +113,35 @@ Reihenfolge: **D1 → D2/D2b/D2c → D3** (D2c ist klein und gehört in denselbe
### F1. Mapper: kanonischen Tag zuerst, dann Heuristik
- [ ] In `MarketCategoryMapper.Map`: **zuerst** prüfen, ob **irgendein Tag exakt** einer bekannten
- [x] In `MarketCategoryMapper.Map`: **zuerst** prüfen, ob **irgendein Tag exakt** einer bekannten
Kategorie entspricht (Polymarkets Tag-Vokabular enthält fast immer den kanonischen Top-Level-Tag:
`Sports`, `Politics`, `Crypto`, `Business`/`Economy`, `Pop Culture`, `Science`, ...). Mapping-Tabelle
Tag→`MarketCategory` (inkl. Synonyme: `Finance`/`Business`→Economy, `Pop Culture`→PopCulture).
Erst wenn **kein** kanonischer Tag matcht, die bestehende Keyword-Heuristik auf Frage+Tags anwenden.
- [ ] **Kategorie über die gesamte Tag-Menge** bestimmen, nie über `tags[0]`.
- [x] **Kategorie über die gesamte Tag-Menge** bestimmen, nie über `tags[0]`.
### F2. Subcategory: Noise filtern, sinnvoll wählen, leer normalisieren
- [ ] Organisations-/Müll-Tags herausfiltern (Blacklist: `Hide From New`, `Tournament Futures`,
- [x] Organisations-/Müll-Tags herausfiltern (Blacklist: `Hide From New`, `Tournament Futures`,
`Main Election`, `Recurring`, Jahres-Tags wie `2025 Predictions`, `2026 FIFA World Cup`→ok als Sub?, …).
- [ ] Subcategory = spezifischster **verbleibender** Tag, der **nicht** die Kategorie selbst ist
- [x] Subcategory = spezifischster **verbleibender** Tag, der **nicht** die Kategorie selbst ist
(bei World-Cup-Tags → `Soccer`, nicht `Sports`). Kein passender → leerer String.
- [ ] **NULL/`""` einheitlich als `""`** speichern (behebt die doppelten „Sports/-"-Zeilen: heute
- [x] **NULL/`""` einheitlich als `""`** speichern (behebt die doppelten „Sports/-"-Zeilen: heute
entstehen zwei Gruppen-Keys aus NULL vs. "").
### F3. On-Demand-Markt: Tags nachladen statt `Other` zu speichern
- [ ] In `GetMarketAsync`: wenn `parentTags` leer ist, aber ein Event mit Id vorhanden ist →
- [x] In `GetMarketAsync`: wenn `parentTags` leer ist, aber ein Event mit Id vorhanden ist →
**`/events?id=<eventId>` nachladen** (liefert Tags, 1 Extra-Call pro neuem Markt, cachebar) und die
Tags fürs Mapping verwenden. Über den `IRateLimiter` drosseln.
- [ ] Alternativ/zusätzlich: existiert das Parent-Event bereits in unserer DB (aus dem Events-Sync,
- [x] Alternativ/zusätzlich: existiert das Parent-Event bereits in unserer DB (aus dem Events-Sync,
`Event.Tags` gefüllt) → **Tags von dort erben**, ganz ohne API-Call.
### F4. Kategorie aus Event-Tags ableiten + Offline-Backfill (der große Hebel)
- [ ] Markt-Kategorie primär aus den **Event-Tags** (`market.Event.Tags`) ableiten, nicht aus den
- [x] Markt-Kategorie primär aus den **Event-Tags** (`market.Event.Tags`) ableiten, nicht aus den
(leeren) Markt-Feldern. On-Demand-Märkte erben so die Kategorie ihres Events.
- [ ] **Einmaliger Offline-Backfill** (keine API-Calls!): über alle Märkte iterieren, deren Event
- [x] **Einmaliger Offline-Backfill** (keine API-Calls!): über alle Märkte iterieren, deren Event
Tags hat, und Kategorie/Subcategory aus `Event.Tags` neu ableiten (mit F1/F2). Als Methode in
`RunRecalculateAllTradersAsync` einhängen ODER eigener Dev-Endpoint. Danach `TraderCategoryPerformance`
neu rechnen (passiert durch die ohnehin folgende Trader-Neuberechnung).
@@ -136,9 +154,9 @@ Reihenfolge: **D1 → D2/D2b/D2c → D3** (D2c ist klein und gehört in denselbe
### F6. Tests
- [ ] Mapper: kanonischer Tag gewinnt über Reihenfolge (`"Ethiopia, Elections, ..., Politics"` → Politics;
- [x] Mapper: kanonischer Tag gewinnt über Reihenfolge (`"Ethiopia, Elections, ..., Politics"` → Politics;
`"Sports, Soccer, ..."` → Sports, Subcategory `Soccer`).
- [ ] Subcategory: Müll-Tags werden gefiltert; NULL und `""` erzeugen denselben Gruppen-Key
- [x] Subcategory: Müll-Tags werden gefiltert; NULL und `""` erzeugen denselben Gruppen-Key
(kein Duplikat mehr).
- [ ] Event-Tag-Vererbung: Markt ohne eigene Tags, Event mit `['Crypto',...]` → Markt wird Crypto.
- [ ] Backfill: Markt in DB als `Other`, Event.Tags = `['Politics',...]` → nach Backfill Politics,