From fa3e66fb3151166670ede2f3488778447eca67dc Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 17 Apr 2026 12:55:12 +0200 Subject: [PATCH] feat: Update activity documentation and enhance API responses with session metrics - Added new updates for Phase A and Phase B in `CLAUDE.md`, detailing the completion of Phase A and the introduction of enriched session metrics in the API response for `GET /api/activity`. - Enhanced the README to include references to new documentation files for scalar canon and composite metrics implementation. - Updated `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` to reflect the current status of phases and added navigation rules for data access. - Improved `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` with links to new implementation concepts for composite metrics. - Refactored the activity router to integrate enriched session metrics into the activity list responses, ensuring a more comprehensive data presentation. --- .claude/docs/README.md | 2 + ...OMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md | 317 ++++ ...VITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md | 45 +- .../technical/ACTIVITY_SCALAR_KANON_TABLE.md | 95 ++ ...CTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md | 6 +- .../functional_concept_composite_data.md | 1480 +++++++++++++++++ CLAUDE.md | 5 + backend/data_layer/activity_data_canon.py | 3 +- backend/routers/activity.py | 19 +- 9 files changed, 1956 insertions(+), 16 deletions(-) create mode 100644 .claude/docs/technical/ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md create mode 100644 .claude/docs/technical/ACTIVITY_SCALAR_KANON_TABLE.md create mode 100644 .claude/docs/technical/functional_concept_composite_data.md diff --git a/.claude/docs/README.md b/.claude/docs/README.md index 43f1596..0cff71e 100644 --- a/.claude/docs/README.md +++ b/.claude/docs/README.md @@ -115,7 +115,9 @@ _Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Sp | `TRAINING_TYPE_PROFILES_TECHNICAL.md` | Trainingsprofile technisch | | `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` | Universal CSV: Registry, Executor, Vorlagen, Agent-Checkliste | | `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` | Session-Metriken EAV, Attributprofile, Layer-1, Prod-Migration | +| `ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md` | Composite-Metriken in EAV (JSONB), Archetypen, CSV-Slots, Layer-1-Expand, Migration/Test-Checkliste | | `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` | **Zielarchitektur** Aktivität (Spine/EAV/Composites/Import/Layer 1–2) + **Phasenplan A–F** Produktionsreife | +| `ACTIVITY_SCALAR_KANON_TABLE.md` | **Skalar-Kanon** Aktivität (eine Semantik → eine Quelle); Phase A | | *(Code)* `backend/data_layer/activity_data_canon.py` | **Kanon** activity CSV-Modul vs. EAV-primär; Legacy-Lesefallback | | `V9D_PHASE2_VITALS_SLEEP.md` | v9d Vitalwerte/Schlaf (Release-Bezug) | diff --git a/.claude/docs/technical/ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md b/.claude/docs/technical/ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md new file mode 100644 index 0000000..249cee5 --- /dev/null +++ b/.claude/docs/technical/ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md @@ -0,0 +1,317 @@ +# Activity Session Metrics: Composite-Daten (EAV) – Umsetzungskonzept + +**Stand:** 2026-04-16 +**Status:** Normatives Konzept zur nahtlosen Weiterarbeit durch Code-Agenten +**Bezieht sich auf:** `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` (§2.3–2.4, Phasen D–E), `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md`, Issue #53 (Layer-1-Prinzip: Auswertungen nur über `data_layer`) + +--- + +## 1. Ziel und Abgrenzung + +### 1.1 Ziel + +- **Composite-Messgrößen** (strukturierte Werte mit mehreren benannten Slots) werden wie **normale Trainingsparameter** im Katalog geführt, **Kategorie-/Typ-Profilen** zugeordnet und pro Session in der **EAV-Tabelle** persistiert. +- **Persistenz:** ein JSON-Dokument pro Session und `training_parameter_id` (kanonisch **JSONB**), kompatibel mit der bestehenden „eine Zeile pro Parameter“-Semantik. +- **Import:** CSV liefert typischerweise **eine Spalte pro atomarem Slot**; das Mapping verweist auf **`(Parameter-Key, Slot-Key)`** (stabile Strings, nicht Spaltenreihenfolge). +- **Layer 1:** liefert für Consumer weiterhin **eine konsistente API**: Rohdokument **und** optional **aufgelöste Einzelwerte** (flach oder namenspaced), ohne dass Charts/Platzhalter direkt JSON parsen müssen. + +### 1.2 Nicht-Ziele (explizit) + +- Kein „freies“ JSON-Schema im Admin ohne Archetyp-Bindung (verhindert Datenmüll und nicht validierbare Dokumente). +- Keine Abschwächung bestehender **Skalar-Parameter** (`integer`, `float`, `string`, `boolean`): alle bisherigen Pfade bleiben gültig. +- Kein Ersatz für `activity_log`-**Spine** oder Session-Qualitätsblobs (`evaluation`, …). + +### 1.3 Kompatibilitätsgarantie („keine Regression“) + +| Bereich | Maßnahme | +|---------|----------| +| DB | Nur **additive** Migrationen; bestehende `CHECK`-Regeln für Skalare bleiben für Zeilen **ohne** Composite erhalten bzw. werden zu einer **Oder-Verknüpfung** erweitert (siehe §4). | +| `training_parameters` | Neuer `data_type`-Wert **`composite`** zusätzlich zu den vier bestehenden; bestehende CHECK-Constraint muss erweitert werden (Migration). | +| `activity_session_metrics` | Skalare Zeilen unverändert; Composite-Zeilen nutzen **`value_json`** (neu), alle `value_*` NULL. | +| Layer 1 | `resolve_activity_attribute_schema`, Merge, Replace: Composite erscheint als **ein** Schema-Eintrag; Lese-/Schreibpfade erweitern, nicht ersetzen. | +| CSV | Bestehende Map-Ziele auf Skalare/Registry unverändert; neue Zielnotation nur für Composites. | +| Admin | tcp/ttp-UI: gleiche Zuordnung wie heute; Zusatzfelder nur bei `data_type === composite`. | + +### 1.4 Abgleich mit `functional_concept_composite_data.md` (fachliches Konzept) + +Das **fachliche Konzeptpapier** (Composite Scalar/Layer-Trennung) und dieses **Umsetzungskonzept** sind **vereinbar**, wenn die Rollen klar getrennt bleiben: + +| Thema | Fachliches Konzept (`functional_concept_composite_data.md`) | Dieses Umsetzungskonzept (technisch) | +|--------|-------------------------------------------------------------|--------------------------------------| +| **Speicher in der DB** | Einheitlicher Store; Composite = `jsonb` mit **kleinem Basisschema** (`v`, `kind`, `domain`, `items`, optional `basis`, `meta`) | `activity_session_metrics.value_json`; CHECK Skalar vs. Composite | +| **Technische Container** | Genau **vier** `kind`-Werte: `group_set`, `distribution_set`, `sequence_set`, `model_set` | Layer-1-Validierung **muss** diese Hülle durchsetzen; kein freies JSON ohne `kind`/`v`/`items` | +| **„Archetypen“** | **Fachliche** Ausprägungen werden in **Layer 2a** aus L1-Objekten abgeleitet | Benannte **Preset-/Validierungsprofile** im Code (z. B. Zonenverteilung HF) sind **kein** zweites Persistenz-Schema: sie legen fest, *welches* der vier `kind`-Muster, *welches* `domain`, *welche* Item-Keys/Typen erlaubt sind — inkl. CSV-Slot-Mapping | +| **Layer 1** | Validiert, minimal normalisiert, **keine** Scores/Bewertungen/KI-Texte | Validator + Merge + optional `expand_*` (**technische** Flachstellung für Consumer, z. B. `param.slot` → Skalar) | +| **Layer 2** | Diagramme, Kennzahlen, KI-Platzhalter-**Formulierung** | unverändert; konsumiert L1 (und ggf. L2a) | + +**Konsequenz für die Registry:** Statt „8 freie JSON-Archetypen“ implementiert die Code-Registry **Validierungs-Presets**, die alle auf die **vier technischen `kind`-Formen** abbilden. Die Tabelle in §3 beschreibt weiterhin **fachlich benannte MVP-Anker** — technisch übersetzen sie sich in `(kind, domain, Item-Regeln, v)`. + +**Konsequenz für Platzhalter:** Roh-JSON aus der DB **nicht** ungefiltert in Prompts; L2b nutzt L1/L2a-Aufbereitung (wie im fachlichen Konzept). + +--- + +## 2. Begriffe + +| Begriff | Bedeutung | +|---------|-----------| +| **Archetyp** | Im **Repo versionierte** Strukturvorlage (erlaubte Slots, Typen, Pflichtfelder, Validator, Version). **7–8** Stück geplant; Erweiterung nur per Code-Release. | +| **Slot** | Benanntes Teilfeld innerhalb des Composite-Dokuments, z. B. `z1_sec`, `z2_sec`, `avg_cadence`. | +| **Parameter-Instanz** | Eine Zeile in `training_parameters` mit `data_type = composite` und Metadaten, **welcher** Archetyp gilt (siehe §5). | +| **Dokument** | Ein JSON-Objekt, das alle Slots abbildet; gespeichert in `activity_session_metrics.value_json`. | + +--- + +## 3. Archetypen-Katalog (Planungsstand) — fachliche Namen → technische `kind`-Presets + +Die **konkrete** Slot-Liste und Validierung wird im Code als **Registry** geführt (z. B. `backend/data_layer/activity_composite_archetypes.py`). Jedes Preset **mappt** auf genau eines von **`group_set` | `distribution_set` | `sequence_set` | `model_set`** und erfüllt das **Basisschema** aus `functional_concept_composite_data.md` §7. + +Inhaltlich orientiert an `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` §2.4. + +**Beispielhafte fachliche MVP-Anker** (8 Kandidaten; im Code als Preset-Key + `kind`/`domain` abbilden): + +| `archetype_key` (stabil) | Kurzbeschreibung | Typische Slots (Beispiel) | +|--------------------------|------------------|---------------------------| +| `hr_zone_distribution` | Zeit-/Anteil je HF-Zone | `z1_sec`…`z5_sec` oder `zones[]` | +| `power_zone_distribution` | Leistungszonen | analog | +| `pace_band_profile` | Pace-Bänder / Histogramm | bucket-Struktur | +| `interval_block_summary` | Intervallblöcke aggregiert | `blocks[]` mit Dauer, Ziel, Ist | +| `event_marker_sequence` | Ereignisse mit Zeitstempel | `events[]` | +| `coupling_efficiency_profile` | Kopplungs-/Effizienzmetriken | sportabhängig | +| `model_parameter_profile` | Modell-/Schwellenparameter | key-value-ähnlich, validiert | +| `readiness_recovery_snapshot` | optional: kurzes Multi-Signal-Bundle | nur wenn fachlich gewünscht | + +**Regel:** Jeder Archetyp hat `version` (Integer). Validator lehnt Dokumente mit falscher/fehlender Version ab oder migriert definiert (nur wenn spezifiziert). + +--- + +## 4. Datenmodell-Erweiterungen + +### 4.1 `training_parameters` + +**Migration (additiv):** + +1. `CHECK (data_type IN (...))` erweitern um **`composite`**. +2. Optional eigene Spalte **`composite_archetype_key` `VARCHAR(64)`** (NOT NULL wenn `data_type = composite`, sonst NULL) — **oder** ausschließlich in `validation_rules` speichern (siehe unten). + **Empfehlung:** Spalte `composite_archetype_key` + `composite_archetype_version INT` für einfache Admin-Queries und klare Semantik; `validation_rules` für archetyp-spezifische Feinheiten (z. B. erlaubte Zonenanzahl). + +**Konsistenz-Constraint (DB oder App):** + +- Wenn `data_type = composite`: `composite_archetype_key` gesetzt, `source_field` typischerweise **NULL** (kein `activity_log`-Skalar-Shadowing). +- `unit` am Parameter: optional für „Anzeige-Einheit“ des Gesamtwerts oder leer; Slots haben Einheiten im Archetyp oder in Slot-Metadaten. + +### 4.2 `activity_session_metrics` + +**Migration (additiv):** + +```text +value_json JSONB NULL +``` + +**CHECK-Constraint ersetzen/erweitern** (Konzept): + +- **Modus Skalar:** genau eine der Spalten `value_num`, `value_int`, `value_text`, `value_bool` ist NOT NULL; `value_json` IS NULL. +- **Modus Composite:** `value_json` IS NOT NULL; alle vier Skalar-Spalten IS NULL. + +Damit bleibt die bestehende Semantik „eine Zeile = ein Parameter“ erhalten. + +**Kommentar:** Tabelle trägt weiterhin „EAV“; Composites sind **keine** zusätzlichen Zeilen pro Slot. + +### 4.3 Profil-Zuordnung (tcp / ttp) + +**Keine** Tabellenänderung: `training_category_parameter` und `training_type_parameter` verweisen weiter nur auf `training_parameter_id`. Composite-Parameter verhalten sich wie Skalare in Bezug auf **Zuordnung**, **sort_order**, **required**, **ui_group**. + +**`required`:** bedeutet „Dokument muss nach Validator vollständig sein“, nicht „jede CSV-Spalte muss in jeder Zeile vorkommen“. + +--- + +## 5. Metadaten pro Composite-Parameter + +Minimal in der DB (Beispiel): + +| Feld | Zweck | +|------|--------| +| `data_type` | `composite` | +| `composite_archetype_key` | Verweis auf Code-Registry | +| `composite_archetype_version` | Schema-Version | +| `validation_rules` | optional: Overrides (z. B. `max_zones`, sport-spezifisch) — nur was der Validator explizit auswertet | + +**Admin-API:** bestehende Endpoints erweitern (Payload-Validierung): bei `composite` müssen Archetyp + Version gesetzt sein und in der **Registry** existieren. + +--- + +## 6. Layer 1 – Kontrakt (`activity_session_metrics.py` + Helfer) + +### 6.1 Schema-Auflösung + +`resolve_activity_attribute_schema` liefert pro Composite **einen** Eintrag wie bei Skalaren, mit: + +- `data_type: "composite"` +- `composite_archetype_key`, `composite_archetype_version` (aus DB oder Join) +- ggf. `composite_slot_catalog`: **nur wenn** für Admin/UI gewünscht — alternativ separater Endpoint `GET .../composite-archetypes` (read-only) aus Registry, um Bundle-Größe klein zu halten. + +### 6.2 Lesen / Merge + +- `fetch_activity_session_metrics`: SELECT inkl. `value_json`. +- `merge_column_backed_and_eav_metrics`: Composites **nur** aus EAV (`value_json`), kein `activity_log`-Shadowing (außer später explizit im Kanon — Standard: nein). +- Ausgabe in `metrics`-Liste: ein Eintrag pro Parameter mit z. B. + `value: { "_composite": true, "document": { ... } }` **oder** kanonisch getrennt: `value_document` + `value` null — **festlegen beim Implementieren** und in API-Doku halten; Empfehlung: **`value` = deserialisiertes Objekt (dict)** für Composites, damit Frontend dieselbe Struktur wie Speicher hat. + +### 6.3 „Einzelwerte für Layer 1 / Issue 53“ + +Neue **pure** Funktion (kein SQL im Router), z. B.: + +```text +expand_composite_metrics_for_session( + schema: list[dict], + metrics: list[dict], +) -> dict[str, Any] +``` + +- Input: effektives Schema + gemergte Metriken. +- Output: flaches Dict **`slot_path → typisierter Wert`**, z. B. + `hr_zones.z1_sec → 1200`, oder namespaced Keys `training_param_key.slot_key` zur Kollisionssicherheit. +- Nutzung: `activity_metrics`, Chart-Builder, später Platzhalter-Registry (`data_layer_function`), **ohne** JSON-Parsing in Layer 2. + +**Wichtig:** Skalare Parameter erscheinen im expandierten Dict mit ihrem `parameter_key` wie bisher (kein Breaking Change für Consumer, die nur Skalare erwarten). + +### 6.4 Validierung / Schreiben + +- **`replace_activity_session_metrics`:** Payload-Item für Composite: `value` ist **Objekt** (dict) oder JSON-String — Server normalisiert zu dict, validiert mit Archetyp-Validator, speichert als `value_json`. +- **`upsert_session_metrics_from_csv_mapped`:** siehe §7 (Zusammenbau aus Partial-Updates pro Zeile). + +**Pflicht:** Keine Teil-Updates in DB, die ein halbes Dokument hinterlassen, ohne Validierung — außer explizit als „Draft“-Modus spezifiziert (nicht Teil dieses Konzepts). + +--- + +## 7. CSV / Universal Import + +### 7.1 Map-Ziel-Notation + +Stabiles Muster (Vorschlag, im Import-Modul zentral parsen): + +```text +"." +``` + +Beispiel: `my_hr_zones.z1_sec` → nach Import-Zusammenfügung in den Parameter `my_hr_zones` unter Slot `z1_sec`. + +**Alternative:** explizites Präfix `composite:` in der Vorlage — nur nötig, wenn Kollisionen mit normalen Keys befürchtet werden; sonst Punkt-Notation reicht. + +### 7.2 Executor-Flow (Konzept) + +1. `build_row_after_mapping` liefert flache Keys inkl. `param.slot`. +2. Nach Schreiben von `activity_log` / Skalar-EAV: **Composite-Accumulator** pro `activity_log_id` und `parameter_key`: + - Sammelt alle Slot-Werte aus der Zeile. +3. Vor Commit der Zeile (oder am Ende der Datei — **pro Zeile empfohlen**, damit SAVEPOINT pro Row funktioniert): + - Dokument aus Slots bauen → Validator → Upsert `activity_session_metrics` mit `value_json`. + +**Teilbefüllung:** Validator entscheidet (Archetyp: optional vs. required Slots). CSV darf nur Teilmengen liefern, wenn Archetyp erlaubt. + +### 7.3 Typkonvertierung + +Pro **Slot** im Archetyp: definierter skalarer Typ (`float`, `int`, …). Converter wie bei Skalaren (Executor / zentrale Converter), **keine** Parallel-Logik in Routern. + +--- + +## 8. Admin-UI / Mapping-UX + +### 8.1 Parameter anlegen + +- Auswahl **Datentyp „Composite“** → Dropdown **Archetyp** (aus Registry-API), Version readonly oder wählbar gemäß Policy. +- Rest wie Skalar: Name, Kategorie (`training_parameters.category`), Aktiv-Flag. + +### 8.2 Profil zuordnen + +Unverändert: Kategorie-/Typ-Matrix wie heute. + +### 8.3 Universal-CSV-Vorlage + +- Mapping-Ziele: neben bisherigen Keys **Slot-Ziele** `parameter_key.slot_key`. +- UI-Gruppierung: optisch **Composite-Block** (wie in `ACTIVITY_PRODUCTION_ARCHITECTURE` §2.5 angedeutet), um Verwechslung mit Spine-Spalten zu vermeiden. + +--- + +## 9. API-Oberflächen (Erweiterungen) + +| Bereich | Änderung | +|---------|-----------| +| `GET /api/activity/{id}` | `metrics` enthält Composite-Werte als Objekt; `schema` kennzeichnet `data_type: composite`. | +| `PUT /api/activity/{id}/metrics` | Eintrag `{ parameter_key, value: { ... } }` für Composites. | +| Admin `training-parameters` | Create/Update mit Composite-Feldern. | +| Optional | `GET /api/admin/composite-archetypes` | Registry export für UI (Keys, Slot-Liste, Version). | + +**Rückwärtskompatibilität:** Clients, die nur Skalare senden, unverändert. + +--- + +## 10. Frontend (Kurz) + +- `ActivityPage` / Session-Metrik-Editor: für `data_type === composite` **strukturierte Teilfelder** aus Slot-Katalog rendern (oder JSON-Editor nur als Entwickler-Fallback — Produkt: strukturierte Felder). +- Sortierung/Gruppierung: bestehende `param_category` / `ui_group` / `sort_order` gelten unverändert. + +--- + +## 11. Tests (pytest) + +| Test | Beschreibung | +|------|----------------| +| Archetyp-Validator | gültige / ungültige Dokumente je Version | +| DB-Constraint | Skalar vs. Composite Ausschluss | +| `expand_composite_metrics_for_session` | flache Keys, Kollisionen | +| CSV-Zusammenbau | mehrere Spalten → ein `value_json` | +| Regression | bestehende `test_activity_session_metrics.py` unverändert grün halten | + +--- + +## 12. Rollout-Phasen (operativ) + +Stimmt mit `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` überein: + +1. **Phase D – MVP:** ein Preset (z. B. HF-Zonen → `distribution_set`, `domain: heart_rate`), Migration `value_json` + `composite` data_type, Validator gegen Basisschema §7, Import 3–5 Spalten → `items`, GET/PUT, minimale Admin-Anbindung. +2. **Phase E:** weitere Presets / `kind`-Varianten, Mapping-UX, `expand_*` für ausgewählte Layer-1-Consumer. +3. **Phase F:** Observability, Performance, Doku, Gitea-Issues schließen. + +### 12.1 Empfohlene Reihenfolge: Skalar-Pipeline vs. Composite-Speicherung + +**Frage:** Zuerst Skalar-EAV vollständig bis Platzhalter/Orchestrator abschließen, oder zuerst Composite-Speicherung? + +| Option | Vorteil | Risiko | +|--------|---------|--------| +| **A: Nur Skalar zuerst** (Kanon, L1-Härtung, Platzhalter aus EAV/L1) | Eine klare, end-to-end **Referenzpipeline**; weniger gleichzeitige Variablen | Composite-Datenstrome verzögern sich | +| **B: Composite-Speicher zuerst** | JSON landet früh in der DB | Platzhalter/Charts nutzen noch **alte** Pfade → **zwei Wahrheiten** (Detail-API vs. KI) bis L1 vereinheitlicht ist | +| **C (Empfehlung): Skalar L1 + Platzhalter-Orchestrierung *vor* Composite-MVP**, oder **eng parallel** mit gemeinsamem L1-Einstieg | `get_activity_session_logical_unit` / `activity_metrics` werden **kanonisch**; Platzhalter lesen **dieselbe** Schicht; Composite wird **additiv** (`value_json` + Validator + später `expand_*`) | Erfordert kurze Planungsdisziplin: Composite-MVP **ohne** sofort alle KI-Platzhalter | + +**Konkrete Empfehlung** + +1. **`ACTIVITY_PRODUCTION` Phase A–B** nicht überspringen: Kanon „eine Semantik / eine Quelle“ + alle relevanten Consumer über **Layer 1** (mind. Session-Detail, Listen-Anreicherung, erste Platzhalter-Pfade für **Skalare**). +2. **Dann Phase D (Composite-MVP):** Migration + Speichern/Lesen mit **Basisschema** (`kind`/`items`/…); L1 liefert dasselbe API-Objekt wie Skalare, nur `value` als strukturiertes Dokument. +3. **Platzhalter für Composite:** erst **nach** L1 liefert stabil `value_json` **und** optional `expand_composite_metrics_*` — ein Orchestrator-Endpoint bzw. Resolver-Aufruf, der **eine** L1-Funktion nutzt, vermeidet doppelte Logik für Skalar vs. Composite. + +**Kurz:** Composite **persistieren** kann kurz nach stabiler **Skalar-Lese-/Merge-API** folgen; **KI/Platzhalter für Composite** sinnvoll **gemeinsam** mit der erweiterten L1-Ausgabe bauen, nicht gegen eine noch nicht vereinheitlichte Skalar-Pipeline. + +--- + +## 13. Checkliste für den nächsten Agenten + +- [ ] Migration: `value_json`, erweiterte CHECKs, `training_parameters.data_type` + ggf. `composite_archetype_*` Spalten. +- [ ] Registry-Modul: Archetypen + Versionen + Slot-Metadaten + Validator-Einstieg. +- [ ] `activity_session_metrics.py`: Fetch/Merge/Replace/Upsert-Integration; keine Regression für Skalare. +- [ ] Optional: `expand_composite_metrics_for_session` + erste Nutzung in einem Layer-1-Consumer (Tests). +- [ ] CSV: Parser für `parameter_key.slot_key`, Row-Accumulator, Fehler melden wie bestehender Import. +- [ ] Admin-API + UI: Composite anlegen, tcp/ttp unverändert nutzbar. +- [ ] Doku: dieses Dokument mit **festgelegter** JSON-Beispielstruktur pro MVP-Archetyp ergänzen. + +--- + +## 14. Referenzen + +- `functional_concept_composite_data.md` – **fachliches** Schichtenmodell, vier technische `kind`-Container, Basisschema JSON +- `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` – Zielbild, Phasen A–F +- `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` – Ist-Layer-1, APIs +- `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` – Executor, Vorlagen +- Migration `054_activity_session_metrics_eav.sql` – Ist-Constraint Skalar +- Migration `013_training_parameters.sql` – Ist-`data_type`-Enum + +--- + +**Version:** 1.1 · Abgleich mit fachlichem Konzept (§1.4, §3, §12.1); MVP auf `distribution_set` o. ä. konkretisieren. diff --git a/.claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md b/.claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md index 4ffb079..fe0e2f9 100644 --- a/.claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md +++ b/.claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md @@ -1,8 +1,10 @@ # Aktivität: Zielarchitektur & Phasenplan (Produktionsreife) -**Stand:** 2026-04-14 +**Stand:** 2026-04-16 **Status:** Normative Zielrichtung für `activity_log`, EAV, Composites, Import, Layer 1/2. -**Ergänzt:** `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` (Ist-Modell, APIs, Tests). +**Ergänzt:** `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` (Ist-Modell, APIs, Tests). +**Phase A:** abgeschlossen — Kanon-Tabelle [`ACTIVITY_SCALAR_KANON_TABLE.md`](./ACTIVITY_SCALAR_KANON_TABLE.md). +**Phase B:** in Arbeit — Consumer-Audit und Lesepfad-Härtung (siehe §4 Phase B). --- @@ -39,6 +41,18 @@ KI / UI / Export - **Orchestrator:** Schreibpfad, Konsistenz nach Write (kein zweites „Lesen der Wahrheit“ neben Layer 1; optional nur Post-Write-Hooks). - **Resolver:** für Aktivität **kein** direkter DB-Zugriff; nur Aufruf von Layer 1. +### 2.1a Navigationsregel: wo nachsehen (ohne Datei-Zwang) + +Die **physische** Aufteilung ist dreigeteilt: **`activity_log`** (Spine + heiße Spalten), **EAV-Skalare** (`activity_session_metrics` + numerische/textuelle `value_*`), **EAV-Composites** (ein Parameter, Nutzlast z. B. JSON/JSONB im EAV-Datensatz). **Fachlich** soll nach außen **eine homogene Session-Sicht** entstehen — Consumer sollen nicht selbst entscheiden, aus welcher Tabelle/Welche Form ein Wert kommt. + +| Thema | Wo nachsehen (Ist; Ziel: Schnittstelle stabil, Datei optional splittbar) | +|--------|--------------------------------------------------------------------------| +| **Homogene Session lesen** (Merge Spalte + EAV-Skalare + später Composite-Payload) | `data_layer/activity_session_metrics.py` — u. a. `get_activity_session_logical_unit`, `enrich_sessions_with_metrics`, `merge_column_backed_and_eav_metrics` | +| **Schreiben / Import / API-Persistenz** | `data_layer/activity_persistence_orchestrator.py` (+ Router) | +| **Berechnungen, Aggregationen, Scores** über viele Sessions oder Zeitfenster | `data_layer/activity_metrics.py` — arbeitet auf der **vereinheitlichten** Session-Datenlage (über die Read-Funktionen oben), nicht durch paralleles Mergen der drei Quellen im Caller | + +**Hinweis:** Orchestrator und Read-Merge **müssen nicht** in derselben Datei stehen. Entscheidend ist, dass es **genau eine dokumentierte Read-Fassade** für „Session inkl. aller effektiven Metriken“ gibt und Layer‑1‑Berechnungen **nur** diese Fassade (oder deren Ergebnisstrukturen) nutzen. Eine spätere Umbenennung oder Auslagerung in z. B. `activity_read_gateway.py` ändert die Rolle nicht — nur der **eine Einstieg** muss in dieser Doku und im Code auffindbar bleiben. + ### 2.2 `activity_log` (Spine + heiße Skalare) **Maschinenlesbarer Kanon:** `backend/data_layer/activity_data_canon.py` (`ACTIVITY_MODULE_REGISTRY_FIELD_KEYS`, `ACTIVITY_EAV_PRIMARY_PARAMETER_KEYS`, Legacy-Lesefallback für EAV-primäre Parameter). @@ -97,17 +111,17 @@ KI / UI / Export Phasen sind **sequentiell** wo „Abhängigkeit“ steht; Teile können parallel (z. B. UI-Polish) laufen, wenn der Kanon steht. -### Phase A – Kanon & Abschaltplan (Grundlage) +### Phase A – Kanon & Abschaltplan (Grundlage) ✅ **Inhalt:** Schriftliche **Kanon-Tabelle**: pro Messgröße genau eine Quelle (`activity_log` | `eav_scalar` | `eav_composite` | `session_quality`). Liste der Keys, für die **Sync/Spiegelung** endet. **Definition of Done:** Review im Team; Referenz in diesem Dokument oder Verweis auf Gitea-Kommentar; keine Code-Änderung zwingend. -**Erster konkreter Schritt:** Kanon-Tabelle als Checkliste (Spreadsheet oder Gitea-Issue) – **eine Zeile pro Semantik**. +**Erledigt (2026-04-16):** [`ACTIVITY_SCALAR_KANON_TABLE.md`](./ACTIVITY_SCALAR_KANON_TABLE.md) — eine Semantik pro Zeile, verlinkt mit `activity_data_canon.py` und Merge-Logik. --- -### Phase B – Lesepfad härten (Layer 1) +### Phase B – Lesepfad härten (Layer 1) 🔄 **Inhalt:** Sicherstellen, dass **alle** relevanten Consumer (mind. `activity_metrics` für Platzhalter/Charts, Activity-Detail-API) dieselbe Merge-/Fallback-Logik nutzen; Legacy-Spalten nur noch als dokumentierter Fallback bis Enddatum. @@ -115,6 +129,19 @@ Phasen sind **sequentiell** wo „Abhängigkeit“ steht; Teile können parallel **Abhängigkeit:** Phase A für „welche Spalten noch Fallback sind“. +**Audit-Stand (2026-04-16):** + +| Consumer | Nutzt Layer-1-Merge (`enrich_sessions_with_metrics` / `get_activity_session_logical_unit`) | Anmerkung | +|----------|---------------------------------------------------------------------------------------------|-----------| +| `GET /api/activity/{eid}` | ✅ `get_activity_session_logical_unit` | Referenz-Detail | +| `GET /api/activity` (Liste) | ✅ seit 2026-04-16 `enrich_sessions_with_metrics` auf jeder Listen-Antwort | vorher nur Roh-Spalten | +| `activity_metrics.get_activity_detail_data` | ✅ | Platzhalter `{{activity_detail}}` | +| `activity_metrics.get_training_sessions_recent_weeks_data` | ✅ | KI-Kontext | +| `placeholder_resolver` (Aktivität) | ✅ nur `activity_metrics` | kein paralleles SQL | +| `get_activity_summary_data` | n. a. | rein aggregiert (`SUM`/`COUNT`), keine Session-EAV | +| `routers/charts.py` (A1–A8) | Spalten-Aggregate | bewusst: Dauer/RPE/HF aus **`activity_log`**-Kanon; kein EAV-Join nötig für definierte Charts | +| `activity_stats` (`GET /api/activity/stats`) | nur Spalten | Kacheln: `kcal`/`duration` aus Kernspalten | + --- ### Phase C – Schreibpfad entschlacken @@ -153,19 +180,21 @@ Phasen sind **sequentiell** wo „Abhängigkeit“ steht; Teile können parallel ## 5. Was zuerst? -**Sofort (nächster Schritt):** **Phase A – Kanon-Tabelle** (eine Semantik pro Zeile, eine Quelle). Ohne diese Entscheidung riskieren Phase B/C falsche Abschaltungen. +**Erledigt:** Phase A — [`ACTIVITY_SCALAR_KANON_TABLE.md`](./ACTIVITY_SCALAR_KANON_TABLE.md). -Direkt danach: **Phase B** (Lesepfad), dann **Phase C** (Schreibpfad), dann **Phase D** (ein Composite-MVP). +**Aktuell:** Phase B fortsetzen (weitere Consumer prüfen: Export, Import-Vorschau, ggf. zukünftige Chart-Metriken aus EAV), dann **Phase C** (Schreibpfad), dann **Phase D** (Composite-MVP). --- ## 6. Referenzen +- `ACTIVITY_SCALAR_KANON_TABLE.md` – **Skalar-Kanon** (Phase A) - `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` – Tabellen, APIs, Tests, Backfill-Hinweise +- `ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md` – Composite-EAV (JSONB), Archetypen, Import-Slots, Layer-1-Expand, Migrations- und Testplan - `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` – Executor, Vorlagen, Typen - `PLACEHOLDER_REGISTRY_FRAMEWORK.md` – Layer-2-Registrierung - `functional/DATA_ARCHITECTURE.md` – fachliche Datenarchitektur (Querschnitt) --- -**Version:** 1.0 · Bei Meilensteinen Phasen A–F hier Status-Zeile ergänzen (Datum + kurz „erledigt/in Arbeit“). +**Version:** 1.2 · §2.1a Navigationsregel (Read-Merge vs. Orchestrator-Schreiben vs. `activity_metrics`-Berechnungen). diff --git a/.claude/docs/technical/ACTIVITY_SCALAR_KANON_TABLE.md b/.claude/docs/technical/ACTIVITY_SCALAR_KANON_TABLE.md new file mode 100644 index 0000000..c51816b --- /dev/null +++ b/.claude/docs/technical/ACTIVITY_SCALAR_KANON_TABLE.md @@ -0,0 +1,95 @@ +# Aktivität: Skalar-Kanon (eine Semantik → eine Quelle) + +**Stand:** 2026-04-16 +**Normativer Code:** `backend/data_layer/activity_data_canon.py` +**Kontext:** `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` (Phase A abgeschlossen) + +--- + +## 1. Spine & Identität (`activity_log`, nicht EAV) + +Diese Felder sind **keine** `training_parameters`-Skalare. Sie gehören zur Session-Zeile. + +| Semantik | DB / API | Kanonische Quelle | Lesefallback | Sync Spalte↔EAV | +|----------|----------|-------------------|--------------|-----------------| +| Primärschlüssel | `activity_log.id` | `activity_log` | — | — | +| Profil | `profile_id` | `activity_log` | — | — | +| Kalendertag | `date` | `activity_log` | — | — | +| Start / Ende (Zeit) | `start_time`, `end_time`, `started_at`, `ended_at` | `activity_log` | — | — | +| Trainingsart (Freitext/Legacy) | `activity_type` | `activity_log` | — | — | +| Referenz Trainingstyp | `training_type_id`, `training_category`, … | `activity_log` (+ `training_types`) | — | — | +| Notiz | `notes` | `activity_log` | — | — | +| Quelle / Import | `source`, `created`, … | `activity_log` | — | — | +| Session-Auswertung | `evaluation`, `quality_label`, `overall_score`, … | `activity_log` (Blob/Ergebnis) | — | Kein EAV-Raster | + +--- + +## 2. Kernfelder CSV-Modul `activity` (= „heiße“ Skalare) + +Abgeleitet aus `csv_parser.module_registry.MODULE_DEFINITIONS["activity"].fields` — maschinenlesbar über `ACTIVITY_MODULE_REGISTRY_FIELD_KEYS` in `activity_data_canon.py`. + +| Semantik | Key (Registry/API) | Kanonische Quelle | Lesefallback | Bemerkung | +|----------|-------------------|-------------------|--------------|-----------| +| Dauer | `duration_min` | **`activity_log`** | — | Aggregates, Listen | +| Aktive Energie | `kcal_active` | **`activity_log`** | — | | +| Ruhe-Energie | `kcal_resting` | **`activity_log`** | — | | +| Distanz | `distance_km` | **`activity_log`** | — | | +| Ø HF | `hr_avg` (Parameter oft `avg_hr` in EAV-Schema) | **`activity_log`** | EAV nur wenn `source_field` / Profil-Schema | `merge_column_backed_and_eav_metrics`: Spalte schlägt EAV | +| Max-HF | `hr_max` | **`activity_log`** | analog | | +| RPE | `rpe` | **`activity_log`** | analog | | + +Schreibpfad: Universal-CSV und API sollen diese Keys auf **`activity_log`** mappen, sofern nicht ausdrücklich ein EAV-primärer Parameter (§3) gewählt ist. + +--- + +## 3. EAV-primäre Parameter (erweiterte Skalare) + +`ACTIVITY_EAV_PRIMARY_PARAMETER_KEYS` in `activity_data_canon.py`. **`training_parameters.source_field`** = NULL (nach Kanon / Migration 057): kanonischer Speicher ist **`activity_session_metrics`**. + +| Parameter-Key (`training_parameters.key`) | Legacy-Spalte `activity_log` (nur Lesefallback) | Kanonische Quelle | Lesefallback | +|-------------------------------------------|-------------------------------------------------|-------------------|--------------| +| `min_hr` | `hr_min` | **EAV** | Spalte, wenn EAV leer | +| `pace_min_per_km` | `pace_min_per_km` | **EAV** | Spalte, wenn EAV leer | +| `cadence` | `cadence` | **EAV** | Spalte, wenn EAV leer | +| `avg_power` | `avg_power` | **EAV** | Spalte, wenn EAV leer | +| `elevation_gain` | `elevation_gain` | **EAV** | Spalte, wenn EAV leer | +| `temperature_celsius` | `temperature_celsius` | **EAV** | Spalte, wenn EAV leer | +| `humidity_percent` | `humidity_percent` | **EAV** | Spalte, wenn EAV leer | +| `avg_hr_percent` | `avg_hr_percent` | **EAV** | Spalte, wenn EAV leer | +| `kcal_per_km` | `kcal_per_km` | **EAV** | Spalte, wenn EAV leer | + +Merge-Implementierung: `merge_column_backed_and_eav_metrics` in `activity_session_metrics.py`. + +--- + +## 4. Profil-/Typ-dynamische Skalare (EAV, nicht in Registry-Kernliste) + +| Semantik | Kanonische Quelle | Lesefallback | +|----------|-------------------|--------------| +| Admin-definierte Parameter (Attributprofil Kategorie/Typ) | **`activity_session_metrics`** + `training_parameters` | — | +| Parameter mit `source_field` → Spalte | **`activity_log`** (Spalte) | EAV ergänzend; Leseregel: Spalte bevorzugt (kein veraltetes EAV) | + +--- + +## 5. Composites (Zielbild, noch nicht Kanon-Zeile pro Slot) + +| Semantik | Kanonische Quelle (Ziel) | +|----------|---------------------------| +| Strukturierte Composite-Dokumente (z. B. Zonen/Bänder) | **EAV** ein Dokument pro Parameter/Session (siehe `ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md`) | + +Kein dauerhaftes Spiegeln derselben Semantik in `activity_log`-Spalten. + +--- + +## 6. Sync & Übergang + +- **Kein** automatischer Dauer-Sync „Spalte → EAV“ für dieselbe Semantik; Lesepfad vereinheitlicht die Sicht (`merge_column_backed_and_eav_metrics`). +- Optionale **Backfill**-Migration/Skript (idempotent) nur nach fachlicher Freigabe — siehe EAV-Agent-Guide §6. + +--- + +## 7. Referenzen + +- `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` — Phasen A–F +- `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` — APIs, Tests +- `activity_data_canon.py` — `ACTIVITY_LOG_PATCHABLE_COLUMNS`, Legacy-Map diff --git a/.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md b/.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md index 604834f..c0a8df3 100644 --- a/.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md +++ b/.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md @@ -6,6 +6,8 @@ **Zielarchitektur, Phasenplan (Produktionsreife):** [`ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md`](./ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md) – Kanon `activity_log`/EAV, Composites, Import, Layer 1/2, Reihenfolge A–F. +**Composite-Parameter (EAV, JSONB, Archetypen):** detailliertes Umsetzungskonzept für Agenten: [`ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md`](./ACTIVITY_COMPOSITE_METRICS_IMPLEMENTATION_CONCEPT.md). + **Kanon (Code):** `backend/data_layer/activity_data_canon.py` (Repo-Root) — CSV-Modul `activity` vs. EAV-primär; Migration **057**. --- @@ -89,8 +91,8 @@ Router: `backend/routers/admin_training_parameters.py`, `backend/routers/admin_a Siehe **Phasen A–F** in [`ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md`](./ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md). Kurz: -- [ ] **Phase A:** Kanon-Tabelle (eine Quelle pro Semantik). -- [ ] **Phase B:** Lesepfad Layer 1 härten (Consumer-Audit). +- [x] **Phase A:** Kanon-Tabelle (eine Quelle pro Semantik) — [`ACTIVITY_SCALAR_KANON_TABLE.md`](./ACTIVITY_SCALAR_KANON_TABLE.md). +- [ ] **Phase B:** Lesepfad Layer 1 härten (Consumer-Audit fortlaufend — siehe `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` §4 Phase B). - [ ] **Phase C:** Schreibpfad: Doppelhaltung / Sync stufenweise abschalten. - [ ] **Phase D:** Composite-MVP (ein Archetyp E2E). - [ ] **Phase E:** Archetypen ausbauen + CSV-Typkonvertierung vollständig + Mapping-UX. diff --git a/.claude/docs/technical/functional_concept_composite_data.md b/.claude/docs/technical/functional_concept_composite_data.md new file mode 100644 index 0000000..44f4a38 --- /dev/null +++ b/.claude/docs/technical/functional_concept_composite_data.md @@ -0,0 +1,1480 @@ +# Konzeptpapier: Speicherung und Bereitstellung von Scalar- und Composite-Sportdaten + +## Status + +Arbeitsstand auf Basis der fachlichen und architektonischen Diskussion. +Dieses Dokument dient als Rahmenwerk für einen Coding Agent und beschreibt die Zielarchitektur, die Abgrenzung der Schichten sowie die empfohlene technische Struktur für Scalar- und Composite-Werte. + +--- + +## 1. Ziel und Einordnung + +Das System soll zu beliebigen Sportarten Daten speichern und für unterschiedliche nachgelagerte Zwecke bereitstellen, insbesondere: + +- Diagramme +- Kennzahlen +- Scores +- regelbasierte Bewertungen +- KI-Platzhalter und KI-Kontextdaten + +Das System ist bereits mehrschichtig aufgebaut. Diese Schichtung ist fachlich und technisch sinnvoll und soll ausdrücklich erhalten bleiben. + +### Bestehende Schichten + +- **Persistenz / Datenbank** + - reiner Wertespeicher + - Speicherung von Scalars und Composites +- **Layer 1** + - Lesen der Daten + - technische Aufbereitung + - minimale Normalisierung + - Validierung +- **Layer 2a** + - fachliche Aufbereitung für Diagramme, Kennzahlen, Regeln, Auswertungen +- **Layer 2b** + - fachliche Aufbereitung für KI-Platzhalter, KI-Zusammenfassungen, KI-Argumente + +### Zentrale Erkenntnis + +Die Persistenz darf **nicht** mit fachlichen Interpretationen aus Layer 2 überladen werden. + +Das bedeutet konkret: + +- Die Datenbank speichert **Fakten und strukturierte Werte** +- Layer 1 stellt **kanonische, technische Leseobjekte** bereit +- Layer 2 erzeugt daraus **fachliche Bedeutung** + +--- + +## 2. Problemstellung + +Bisher werden überwiegend **Scalar-Werte** gespeichert: + +- Datentyp +- Ausprägung (`value`) +- Einheit + +Nun sollen zusätzlich **Composite-Werte** gespeichert werden. +Aktuell ist der gewünschte Ansatz: + +- derselbe Value Store +- `datatype = composite` +- `value = jsonb` + +Die Herausforderung besteht darin, Composite-Werte so einzuführen, dass: + +1. die Architektur einfach bleibt +2. die Schichtentrennung nicht verletzt wird +3. das System sportartenübergreifend nutzbar bleibt +4. spätere Auswertungslogik nicht bereits in die Persistenz gepresst wird +5. die Lösung für Diagramme, Kennzahlen und KI gleichermaßen nutzbar ist + +--- + +## 3. Architektonische Leitentscheidung + +### 3.1 Grundsatz + +**Composite-Werte werden als strukturierte Fakten gespeichert, nicht als fachlich fertig interpretierte Analyseobjekte.** + +Das bedeutet: + +- Die Datenbank kennt nur wenige **technische Composite-Container** +- Fachliche Archetypen entstehen erst in Layer 2 +- Layer 1 vermittelt zwischen Persistenz und fachlicher Verarbeitung + +### 3.2 Was bewusst vermieden wird + +Nicht in die Persistenz gehören als Teil des Basis-Composite: + +- Scores +- fachliche Bewertungen +- Trainingsqualitätsurteile +- KI-Texte +- narrative Einordnungen +- Interpretationen wie „intervallartig“, „polarisiert“, „gut regeneriert“ +- abgeleitete Kennzahlen wie `switch_rate`, `transition_entropy`, `readiness_index`, `plan_match_score` + +Diese Dinge sind **sekundäre Ableitungen** und gehören in Layer 2. + +--- + +## 4. Fachliche Kernaussage des Konzepts + +Die frühere Diskussion über viele fachliche Composite-Archetypen war fachlich sinnvoll, aber für die Persistenz zu komplex. + +Deshalb wird das Modell in zwei Ebenen getrennt: + +### Ebene A: technische Speicherformen + +Diese werden in der Datenbank als `jsonb` gespeichert. + +### Ebene B: fachliche Analyse-Archetypen + +Diese werden in Layer 2 aus den technischen Speicherformen abgeleitet. + +Damit gilt: + +- **wenige Speicherformen** +- **viele fachliche Projektionen** + +Das ist die zentrale Designentscheidung dieses Dokuments. + +--- + +## 5. Zielbild + +### 5.1 Persistenzebene + +Einheitlicher Value Store für: + +- Scalars +- Composites + +#### Scalar + +- `datatype != composite` +- `value` ist scalar +- `unit` als separate Spalte oder Feld + +#### Composite + +- `datatype = composite` +- `value` ist `jsonb` +- `jsonb` folgt einem kleinen, stabilen Basisschema + +### 5.2 Layer 1 + +Layer 1 liefert für Composite-Werte: + +- validierte Daten +- minimal normalisierte Daten +- kanonische Leseobjekte +- keine fachlichen Scores oder Bewertungen + +### 5.3 Layer 2a + +Layer 2a erzeugt aus Layer-1-Daten: + +- Diagrammserien +- Kennzahlen +- Übergangsmetriken +- Intervallstrukturen +- Auswertungsobjekte +- sportartspezifische Projektionen + +### 5.4 Layer 2b + +Layer 2b erzeugt aus Layer-1- oder Layer-2a-Daten: + +- KI-Platzhalter +- KI-Faktenlisten +- zusammengefasste Trainingsmerkmale +- explizite, strukturierte Aussagen für Prompts + +--- + +## 6. Technische Speicherformen für Composite-Werte + +Statt vieler spezieller Composite-Typen werden nur vier technische Container eingeführt: + +1. `group_set` +2. `distribution_set` +3. `sequence_set` +4. `model_set` + +Diese vier Typen reichen als Speicherebene aus. + +--- + +## 7. Der gemeinsame Basiskern jedes Composite-Objekts + +Jedes Composite-JSONB soll auf einem kleinen gemeinsamen Kern basieren. + +### 7.1 Basisschema + + ```json + { + "v": 1, + "kind": "group_set", + "domain": "recovery", + "basis": null, + "items": [], + "meta": { + "source": "reported", + "definition_ref": null, + "quality": null + } + } + ``` + +### 7.2 Pflichtfelder + +- `v` +- `kind` +- `domain` +- `items` + +### 7.3 Optionale, aber empfohlene Felder + +- `basis` +- `meta.source` +- `meta.definition_ref` +- `meta.quality` + +### 7.4 Semantik der Felder + +#### `v` + +Schema-Version des gespeicherten JSON-Objekts. + +#### `kind` + +Technischer Composite-Container. +Erlaubte Werte: + +- `group_set` +- `distribution_set` +- `sequence_set` +- `model_set` + +#### `domain` + +Fachlicher Bezugsraum der Daten, zum Beispiel: + +- `heart_rate` +- `power` +- `speed` +- `recovery` +- `intervals` +- `technique` +- `critical_power` + +#### `basis` + +Optionale Bezugsgröße, zum Beispiel: + +- `time` +- `distance` +- `repetitions` +- `work` +- `count` +- `score` + +#### `items` + +Liste der eigentlichen Teilwerte oder Segmente. + +#### `meta.source` + +Ursprung des Composite-Werts, z. B.: + +- `reported` +- `imported` +- `detected` +- `derived_l1` + +#### `meta.definition_ref` + +Referenz auf eine externe oder interne Definition, etwa: + +- HF-Zonenmodell +- Power-Zonenmodell +- Intervall-Template +- Testprotokoll +- Schwellenmodell + +#### `meta.quality` + +Optionale Qualitätskennzeichnung, z. B.: + +- `raw` +- `validated` +- `estimated` +- `reported` +- `derived` + +--- + +## 8. Composite-Typ 1: `group_set` + +### 8.1 Zweck + +Speicherung einer kleinen Menge benannter Teilwerte ohne zwingende Reihenfolge und ohne Bandgrenzen. + +### 8.2 Typische Einsatzfälle + +- HRV-Bundle +- Wellness-/Readiness-Bundle +- Technik-Bundle +- Submetriken eines Tests +- kleine Sammlungen korrelierter Werte + +### 8.3 Struktur + + ```json + { + "v": 1, + "kind": "group_set", + "domain": "recovery", + "items": [ + { "key": "rmssd", "value": 42.1, "unit": "ms" }, + { "key": "resting_hr", "value": 54, "unit": "bpm" }, + { "key": "sleep_score", "value": 78, "unit": "score" } + ], + "meta": { + "source": "reported", + "definition_ref": "morning_readiness_protocol_v1", + "quality": "validated" + } + } + ``` + +### 8.4 Pflichtfelder pro Item + +- `key` +- `value` + +### 8.5 Optionale Item-Felder + +- `unit` +- `label` +- `note` + +### 8.6 Regeln + +- `key` muss innerhalb eines Objekts eindeutig sein +- `value` darf scalar oder `null` sein +- keine fachliche Interpretation im Objekt selbst + +### 8.7 Nicht speichern in `group_set` + +Nicht in `group_set` speichern: + +- `readiness_index` +- `recovery_flag` +- `trend_direction` +- generierte Scores + +Diese gehören in Layer 2. + +--- + +## 9. Composite-Typ 2: `distribution_set` + +### 9.1 Zweck + +Speicherung einer Verteilung über Bänder, Klassen oder Bereiche. + +### 9.2 Typische Einsatzfälle + +- Herzfrequenzzonen +- Power-Zonen +- Geschwindigkeitszonen +- Pace-Bänder +- Kadenz-Zonen +- Beschleunigungsbänder +- Sprunghöhenklassen + +### 9.3 Struktur + + ```json + { + "v": 1, + "kind": "distribution_set", + "domain": "heart_rate", + "basis": "time", + "items": [ + { + "key": "z1", + "value": 420, + "unit": "s", + "lower": 50, + "upper": 60, + "range_unit": "%hr_max" + }, + { + "key": "z2", + "value": 780, + "unit": "s", + "lower": 60, + "upper": 70, + "range_unit": "%hr_max" + }, + { + "key": "z3", + "value": 360, + "unit": "s", + "lower": 70, + "upper": 80, + "range_unit": "%hr_max" + } + ], + "meta": { + "source": "reported", + "definition_ref": "hr_zone_model_5_hrmax", + "quality": "validated" + } + } + ``` + +### 9.4 Pflichtfelder pro Item + +- `key` +- `value` + +### 9.5 Empfohlene Item-Felder + +- `unit` +- `lower` +- `upper` +- `range_unit` + +### 9.6 Regeln + +- `key` muss eindeutig sein +- `items` sollen logisch sortierbar sein +- `lower` und `upper` sind optional, aber bei bandbasierten Sets empfohlen +- `basis` definiert, worauf sich `value` bezieht, etwa Zeit, Distanz, Wiederholungen + +### 9.7 Nicht speichern in `distribution_set` + +Nicht in `distribution_set` speichern: + +- Wechselhäufigkeit +- mittlere Verweildauer +- Polarized Index +- Pyramidal Index +- Intervallklassifikation + +Das sind Auswertungsergebnisse aus Layer 2. + +--- + +## 10. Composite-Typ 3: `sequence_set` + +### 10.1 Zweck + +Speicherung geordneter Sequenzen, Segmente, Zustandsfolgen oder Intervallblöcke. + +### 10.2 Typische Einsatzfälle + +- Work-/Recovery-Blöcke +- erkannte HF-Zonen-Segmente +- Pace-Wechsel +- Satz-/Rundenabfolgen +- Drill-Sequenzen +- Ereignisfolgen + +### 10.3 Struktur: Intervallblöcke + + ```json + { + "v": 1, + "kind": "sequence_set", + "domain": "intervals", + "basis": "time", + "items": [ + { "seq": 1, "label": "work", "start": 0, "duration": 180, "unit": "s" }, + { "seq": 2, "label": "recovery", "start": 180, "duration": 90, "unit": "s" }, + { "seq": 3, "label": "work", "start": 270, "duration": 180, "unit": "s" } + ], + "meta": { + "source": "detected", + "definition_ref": "interval_template_optional", + "quality": "derived_l1" + } + } + ``` + +### 10.4 Struktur: Zustandssegmente + + ```json + { + "v": 1, + "kind": "sequence_set", + "domain": "heart_rate_zone_state", + "basis": "time", + "items": [ + { "seq": 1, "label": "z2", "start": 0, "duration": 300, "unit": "s" }, + { "seq": 2, "label": "z4", "start": 300, "duration": 90, "unit": "s" }, + { "seq": 3, "label": "z2", "start": 390, "duration": 120, "unit": "s" } + ], + "meta": { + "source": "detected", + "definition_ref": "hr_zone_model_5_hrmax" + } + } + ``` + +### 10.5 Pflichtfelder pro Item + +- `seq` +- mindestens eines von: + - `label` + - `key` +- mindestens eines von: + - `duration` + - `start` und `end` + +### 10.6 Optionale Item-Felder + +- `unit` +- `start` +- `end` +- `value` +- `note` + +### 10.7 Regeln + +- `seq` muss eindeutig und aufsteigend sein +- Segmente müssen logisch geordnet sein +- bei zeitbasierten Segmenten sollen Start/Dauer-Ende konsistent sein +- keine fachlich interpretierten Kennzahlen im Objekt + +### 10.8 Nicht speichern in `sequence_set` + +Nicht in `sequence_set` speichern: + +- `switch_count` +- `switch_rate_per_min` +- `transition_entropy` +- `density_index` +- `plan_match_score` + +Diese Metriken entstehen erst in Layer 2. + +--- + +## 11. Composite-Typ 4: `model_set` + +### 11.1 Zweck + +Speicherung kleiner, strukturierter Parametersätze eines Modells. + +### 11.2 Typische Einsatzfälle + +- Critical Power / W′ +- Critical Speed +- Last-Geschwindigkeits-Profil +- Schwellenmodelle +- Testmodellparameter + +### 11.3 Struktur + + ```json + { + "v": 1, + "kind": "model_set", + "domain": "critical_power", + "items": [ + { "key": "cp", "value": 286, "unit": "W" }, + { "key": "w_prime", "value": 16800, "unit": "J" }, + { "key": "r_squared", "value": 0.98, "unit": "ratio" } + ], + "meta": { + "source": "derived_l1", + "definition_ref": "critical_power_2_parameter", + "quality": "validated" + } + } + ``` + +### 11.4 Pflichtfelder pro Item + +- `key` +- `value` + +### 11.5 Optionale Item-Felder + +- `unit` +- `ci_low` +- `ci_high` +- `error` +- `note` + +### 11.6 Regeln + +- `key` eindeutig +- Modellname und Herleitung über `meta.definition_ref` +- Modellinterpretation nicht im Objekt selbst speichern + +### 11.7 Nicht speichern in `model_set` + +Nicht in `model_set` speichern: + +- Trainingsdiagnosen +- Handlungsempfehlungen +- automatische Coachingtexte + +--- + +## 12. Warum genau diese vier Typen + +Diese vier Typen decken den größten Teil realistisch auftretender Composite-Fälle ab, ohne die Persistenz fachlich zu überfrachten. + +### `group_set` + +Für kleine Bündel benannter Werte + +### `distribution_set` + +Für Verteilungen über Klassen oder Bänder + +### `sequence_set` + +Für geordnete Folgen, Segmente und Intervallstrukturen + +### `model_set` + +Für modellbasierte Parametersätze + +Weitere Speichertypen sollen nur eingeführt werden, wenn ein neuer Fall mit diesen vier Typen **nicht sinnvoll oder nur mit klaren Verformungen** modellierbar ist. + +--- + +## 13. Was ausdrücklich nicht Teil der Persistenz-Basisschicht ist + +Die Persistenzschicht speichert keine fachlichen Endprodukte. + +Nicht Bestandteil der Basis-Composite-Struktur sind: + +- Visualisierungsvorgaben +- Diagrammfarblogik +- Scoring-Ergebnisse +- Kennzahlen aus Übergangsanalysen +- narrative KI-Bausteine +- Trainingsklassifikationen +- Empfehlungen +- Bewertungen +- Interpretationen +- sportartspezifische Schlussfolgerungen + +--- + +## 14. Layer-1-Konzept + +### 14.1 Zweck von Layer 1 + +Layer 1 ist die technische Aufbereitungsschicht zwischen Persistenz und Fachlogik. + +Layer 1 darf: + +- Composite-JSON validieren +- Schema-Version prüfen +- `kind` prüfen +- Items lesen +- Einheiten normalisieren +- Basisreferenzen auflösen +- technische DTOs erzeugen +- leichte Plausibilitätsprüfungen durchführen + +Layer 1 darf nicht: + +- Scores berechnen +- fachliche Bewertungen erzeugen +- Intervallqualität beurteilen +- KI-Platzhalter formulieren +- Trainingscharakter interpretieren + +### 14.2 Layer-1-Output + +Layer 1 erzeugt kanonische interne Datenobjekte, zum Beispiel: + +- `ScalarValueDTO` +- `GroupSetDTO` +- `DistributionSetDTO` +- `SequenceSetDTO` +- `ModelSetDTO` + +### 14.3 Beispielhafte DTOs + + ```ts + type ScalarValueDTO = { + id: string + datatype: string + value: number | string | boolean | null + unit?: string | null + } + + type GroupItemDTO = { + key: string + value: unknown + unit?: string | null + } + + type GroupSetDTO = { + id: string + domain: string + items: GroupItemDTO[] + meta?: Record + } + + type DistributionItemDTO = { + key: string + value: number | null + unit?: string | null + lower?: number | null + upper?: number | null + rangeUnit?: string | null + } + + type DistributionSetDTO = { + id: string + domain: string + basis?: string | null + items: DistributionItemDTO[] + meta?: Record + } + + type SequenceItemDTO = { + seq: number + label?: string | null + start?: number | null + end?: number | null + duration?: number | null + unit?: string | null + value?: unknown + } + + type SequenceSetDTO = { + id: string + domain: string + basis?: string | null + items: SequenceItemDTO[] + meta?: Record + } + + type ModelItemDTO = { + key: string + value: unknown + unit?: string | null + } + + type ModelSetDTO = { + id: string + domain: string + items: ModelItemDTO[] + meta?: Record + } + ``` + +### 14.4 Layer-1-Validierungsregeln + +#### Allgemein + +- JSON muss parsebar sein +- `v` muss unterstützt sein +- `kind` muss bekannt sein +- `domain` darf nicht leer sein +- `items` muss Array sein + +#### `group_set` + +- jeder Eintrag braucht `key` +- `key` eindeutig + +#### `distribution_set` + +- jeder Eintrag braucht `key` +- `key` eindeutig +- `value` numerisch oder `null` +- `lower <= upper`, wenn beide gesetzt sind + +#### `sequence_set` + +- jeder Eintrag braucht `seq` +- `seq` eindeutig +- `seq` sortierbar +- zeitliche Felder konsistent + +#### `model_set` + +- jeder Eintrag braucht `key` +- `key` eindeutig + +### 14.5 Layer-1-Normalisierungen + +Layer 1 darf minimale Normalisierung durchführen: + +- Sekunden, Minuten, Stunden intern vereinheitlichen +- Meter/Kilometer intern vereinheitlichen +- Prozentwerte intern konsistent repräsentieren +- Feldnamen aus Legacy-Importen normalisieren + +Wichtig: +Diese Normalisierung ist technisch, nicht fachlich interpretierend. + +--- + +## 15. Layer-2a-Konzept: fachliche Analyse- und Diagrammprojektionen + +Layer 2a konsumiert Daten aus Layer 1 und erzeugt daraus fachliche Sichtweisen. + +Die früher diskutierten fachlichen Archetypen leben **hier**, nicht in der Persistenz. + +### 15.1 Fachliche Archetypen in Layer 2a + +1. `BandDistribution` +2. `TransitionProfile` +3. `IntervalBlockProfile` +4. `EventActionProfile` +5. `CouplingEfficiencyProfile` +6. `ModelParameterProfile` +7. `TechniqueCycleProfile` +8. `ReadinessRecoveryProfile` + +### 15.2 Mapping von Speicherformen auf Layer-2a-Archetypen + +#### `distribution_set` + +kann projiziert werden auf: + +- `BandDistribution` + +#### `sequence_set` + +kann projiziert werden auf: + +- `TransitionProfile` +- `IntervalBlockProfile` +- `EventActionProfile` + +#### `group_set` + +kann projiziert werden auf: + +- `TechniqueCycleProfile` +- `ReadinessRecoveryProfile` +- Teile eines `EventActionProfile` + +#### `model_set` + +kann projiziert werden auf: + +- `ModelParameterProfile` + +#### Kombination mehrerer Werte + +kann projiziert werden auf: + +- `CouplingEfficiencyProfile` + +### 15.3 Beispiel: TransitionProfile aus `sequence_set` + +Aus einer Zustandssequenz wie: + +- z2 -> 300s +- z4 -> 90s +- z2 -> 120s +- z5 -> 60s + +kann Layer 2a berechnen: + +- Anzahl Wechsel +- Wechselrate +- mittlere Verweildauer pro Zustand +- längste Verweildauer +- Zustandsübergänge +- Intervallcharakter + +Diese Werte werden **nicht** im Basis-Composite gespeichert, sondern von Layer 2a berechnet. + +### 15.4 Beispiel: BandDistribution aus `distribution_set` + +Aus HF-Zonen-Zeiten kann Layer 2a erzeugen: + +- Diagrammserien +- Prozentanteile +- Schwerpunktzone +- Exposition oberhalb einer Schwelle +- Intensitätsverteilung + +Auch dies entsteht erst in Layer 2a. + +--- + +## 16. Layer-2b-Konzept: KI-Projektionen + +Layer 2b soll keine Rohwerte direkt in Prompts kippen, sondern fachlich brauchbare Platzhalter erzeugen. + +### 16.1 Grundsatz + +Layer 2b konsumiert bevorzugt: + +- Layer-1-Daten für einfache Fakten +- Layer-2a-Daten für fachlich verdichtete Aussagen + +### 16.2 Ziel von Layer 2b + +Erzeugung strukturierter KI-Bausteine wie: + +- „Zeit in hoher Intensität“ +- „Intervallstruktur erkannt“ +- „Belastungsdichte hoch“ +- „geringe Variabilität“ +- „Readiness reduziert“ +- „Technikmetriken auffällig“ +- „intern-externe Lastkopplung stabil“ + +### 16.3 Beispielhafte KI-Platzhalter + + ```json + { + "high_intensity_time_s": 480, + "interval_structure_detected": true, + "switch_count": 12, + "dominant_hr_zone": "z2", + "readiness_state": "normal", + "technique_variability_flag": "elevated" + } + ``` + +Wichtig: +Auch diese Objekte sind **keine Persistenz-Basisobjekte**, sondern bereitgestellte Kontexte für KI. + +--- + +## 17. Composite-Speicherung im bestehenden Value Store + +### 17.1 Annahme über den aktuellen Store + +Der bestehende Store speichert Werte als Datensätze mit: + +- Datentyp +- Value +- Einheit +- weiteren Metadaten + +### 17.2 Empfohlene Nutzung + +#### Scalar + +unverändert speichern + +#### Composite + +als Datensatz mit: + +- `datatype = composite` +- `value = jsonb` +- `unit = null` oder optional leer, sofern Einheit innerhalb der Items gespeichert wird + +### 17.3 Empfohlene Zusatzmetadaten außerhalb von `value` + +Soweit euer bestehendes Modell das unterstützt, sind diese Felder außerhalb des JSONB sinnvoll: + +- `id` +- `athlete_id` +- `session_id` +- `source_system` +- `measured_at` +- `datatype` +- `subtype` oder `kind` als Hilfsspalte +- `domain` als Hilfsspalte +- `created_at` +- `updated_at` + +### Empfehlung + +`kind` und `domain` sollten nach Möglichkeit zusätzlich indexierbar sein, selbst wenn sie primär im JSONB liegen. + +--- + +## 18. JSONB-Schema-Regeln + +### 18.1 Schema-Versionierung + +Jedes Composite trägt `v`. + +Regel: + +- neue inkompatible Strukturen erhöhen `v` +- Layer 1 unterstützt definierte Versionen +- unbekannte Versionen werden abgelehnt oder in Fallback-Modus versetzt + +### 18.2 Rückwärtskompatibilität + +Wenn möglich: + +- neue optionale Felder hinzufügen, ohne `v` zu erhöhen +- nur strukturelle Brüche führen zu neuer Version + +### 18.3 Dokumentgröße + +Composite-Objekte sollen fachlich zusammenhängend, aber nicht unnötig groß sein. + +Regel: + +- ein Composite speichert einen **atomaren strukturierten Sachverhalt** +- keine beliebig großen Sammelobjekte +- keine Vermischung mehrerer unabhängiger Analyseebenen + +--- + +## 19. Definitionen, Referenzen und Kataloge + +Die Basis-Composite-Objekte können Definitionen referenzieren, sollen diese aber nicht voll duplizieren. + +### 19.1 Beispiele für `definition_ref` + +- `hr_zone_model_5_hrmax` +- `hr_zone_model_5_hrr` +- `power_zone_model_7_ftp` +- `morning_readiness_protocol_v1` +- `critical_power_2_parameter` +- `boxing_round_structure_3x3` +- `karate_interval_template_v2` + +### 19.2 Nutzen von `definition_ref` + +Damit kann Layer 2: + +- bandbezogene Logik verstehen +- Diagrammbeschriftungen korrekt erzeugen +- Schwellenmodelle zuordnen +- KI-Kontext korrekt ableiten + +Wichtig: +`definition_ref` verweist auf eine Definition, ersetzt diese aber nicht. + +--- + +## 20. Abgeleitete Werte zurückspeichern + +### 20.1 Grundsatz + +Falls Layer-2-Ergebnisse persistiert werden sollen, sollen sie **nicht** in das Basis-Composite zurückgeschrieben werden. + +Stattdessen werden sie als eigene abgeleitete Werte gespeichert. + +### 20.2 Begründung + +So werden vermieden: + +- doppelte Wahrheit +- inkonsistente alte Berechnungsergebnisse +- Vermischung von Fakt und Interpretation +- schwierige Rebuilds + +### 20.3 Empfohlenes Muster + +Ein abgeleiteter Wert bekommt: + +- eigenen Datensatz +- Herkunftskennzeichnung +- Regel-/Berechnungsversion +- Referenz auf Quellwerte + +### 20.4 Beispiel + + ```json + { + "origin": "derived", + "layer": "2a", + "rule": "transition_profile_v2", + "source_ids": ["value_4711", "value_4712", "value_4713"] + } + ``` + +--- + +## 21. Beispielhafte Ende-zu-Ende-Flows + +### 21.1 Flow A: Herzfrequenzzonen + +#### Persistenz + +`distribution_set` mit Zonenzeiten + +#### Layer 1 + +`DistributionSetDTO` + +#### Layer 2a + +- Prozentanteile +- Diagrammwerte +- dominante Zone +- Zeit oberhalb bestimmter Zonen +- Intensitätsprofil + +#### Layer 2b + +- KI-Platzhalter wie: + - `dominant_hr_zone` + - `high_intensity_time_s` + - `intensity_distribution_type` + +### 21.2 Flow B: Intervalltraining + +#### Persistenz + +`sequence_set` mit Work-/Recovery-Segmenten + +#### Layer 1 + +`SequenceSetDTO` + +#### Layer 2a + +- Anzahl Blöcke +- Work-Recovery-Verhältnis +- mittlere Blockdauer +- Dichte +- Compliance +- Intervallcharakter + +#### Layer 2b + +- KI-Platzhalter wie: + - `interval_block_count` + - `work_recovery_ratio` + - `interval_density_state` + +### 21.3 Flow C: Readiness + +#### Persistenz + +`group_set` mit RMSSD, Ruhepuls, Schlafscore + +#### Layer 1 + +`GroupSetDTO` + +#### Layer 2a + +- Baseline-Abweichung +- Trend +- Zustandsklassifikation + +#### Layer 2b + +- `readiness_state` +- `recovery_attention_flag` + +### 21.4 Flow D: Critical Power + +#### Persistenz + +`model_set` mit `cp`, `w_prime`, `r_squared` + +#### Layer 1 + +`ModelSetDTO` + +#### Layer 2a + +- Modellgüte +- Vergleich zum Verlauf +- Zonen-/Schwellenableitung + +#### Layer 2b + +- KI-konforme Modellzusammenfassung + +--- + +## 22. Regeln zur Entscheidung: Scalar oder Composite + +### 22.1 Scalar verwenden, wenn + +- genau ein Wert gespeichert wird +- keine strukturierte Untergliederung nötig ist +- keine zusammenhängende Teilwertgruppe existiert + +Beispiele: + +- Durchschnittspuls +- Maximalpuls +- Distanz +- Dauer +- Durchschnittsleistung + +### 22.2 Composite verwenden, wenn + +- mehrere Teilwerte logisch zusammengehören +- die Teilwerte nur gemeinsam sinnvoll interpretierbar sind +- Reihenfolge, Verteilung oder Modellstruktur relevant ist + +Beispiele: + +- HF-Zonen +- Intervallblöcke +- Readiness-Bundle +- Critical-Power-Parameter + +### 22.3 Nicht jedes Mehrfachfeld ist automatisch Composite + +Wenn mehrere Werte unabhängig voneinander existieren und auch einzeln sinnvoll sind, sollen sie weiterhin als Scalars gespeichert werden. + +Composite nur dann verwenden, wenn die Gruppe selbst eine eigenständige semantische Einheit ist. + +--- + +## 23. Regeln zur Einführung neuer Composite-Formate + +Ein neues Composite-Format darf nur eingeführt werden, wenn mindestens eine der folgenden Bedingungen erfüllt ist: + +1. Der neue Fall lässt sich mit den vier Basis-Typen nicht sinnvoll ausdrücken +2. Eine Abbildung wäre nur durch unsaubere oder missbräuchliche Nutzung möglich +3. Die Lesbarkeit und Wartbarkeit würden mit den vorhandenen Typen stark leiden + +Vor Einführung eines neuen Typs ist zu prüfen: + +- Kann es als `group_set` modelliert werden? +- Kann es als `distribution_set` modelliert werden? +- Kann es als `sequence_set` modelliert werden? +- Kann es als `model_set` modelliert werden? + +Nur wenn alle vier Antworten fachlich und technisch nicht tragfähig sind, soll ein neuer Speichertyp diskutiert werden. + +--- + +## 24. Implementierungsrichtlinien + +### 24.1 Parser + +Implementiere einen zentralen Composite-Parser: + +- Eingang: Value-Record mit `datatype = composite` +- Ausgabe: konkretes Layer-1-DTO je `kind` + +### 24.2 Validator + +Implementiere einen zentralen Validator: + +- allgemeine Prüfung +- kind-spezifische Prüfung +- strukturierte Fehlermeldungen + +### 24.3 Mapper + +Implementiere Mapper: + +- `CompositeRecord -> GroupSetDTO` +- `CompositeRecord -> DistributionSetDTO` +- `CompositeRecord -> SequenceSetDTO` +- `CompositeRecord -> ModelSetDTO` + +### 24.4 Projection Services in Layer 2 + +Beispiele: + +- `BandDistributionProjectionService` +- `TransitionProfileService` +- `IntervalAnalysisService` +- `ReadinessProjectionService` +- `CouplingAnalysisService` +- `TechniqueProjectionService` + +### 24.5 KI-Mapping in Layer 2b + +Beispiele: + +- `AiPlaceholderBuilder` +- `AiFactsBuilder` +- `AiNarrativeInputBuilder` + +--- + +## 25. Fehler- und Fallback-Strategie + +### 25.1 Ungültiges Composite + +Wenn ein Composite ungültig ist: + +- Layer 1 liefert strukturierten Fehler +- der Datensatz wird nicht als fachlich gültig weitergereicht +- optional kann ein „raw passthrough“ für Debugging existieren + +### 25.2 Unbekannte Version + +Wenn `v` unbekannt ist: + +- Datensatz markieren +- nicht stillschweigend interpretieren +- optional Migration oder Fallback-Parser nutzen + +### 25.3 Teilweise fehlende Felder + +Wenn optionale Felder fehlen: + +- lesen, sofern Kernschema gültig bleibt +- fehlende Informationen in Layer 2 berücksichtigen +- keine stillschweigende Erfindung fachlicher Werte + +--- + +## 26. Performance- und Wartungsaspekte + +### 26.1 Grundsatz + +Die Persistenz soll flexibel, aber strukturiert bleiben. + +### 26.2 Empfehlungen + +- kleine, atomare Composite-Objekte +- keine übergroßen Sammelcontainer +- `kind` und `domain` indexierbar halten +- Definitionen referenzieren statt duplizieren +- fachliche Projektionen nicht als Pflichtbestandteil der Persistenz speichern + +### 26.3 Vorteil dieser Struktur + +- geringe Kopplung +- stabile Persistenz +- evolvierbare Fachlogik +- gute Eignung für mehrere Sportarten +- gute Wiederverwendbarkeit in Layer 2a und 2b + +--- + +## 27. Migrationsstrategie + +Falls bereits Composite-Ansätze existieren, wird folgendes Vorgehen empfohlen: + +### Phase 1 + +- Einführung der vier `kind`-Typen +- Aufbau von Validator und Parser +- Speicherung neuer Composites nach neuem Muster + +### Phase 2 + +- Layer-1-DTOs einführen +- bestehende Reader umstellen + +### Phase 3 + +- Layer-2a-Projektionen implementieren +- Diagramme und Kennzahlen umstellen + +### Phase 4 + +- Layer-2b-KI-Platzhalter auf neue Projektionen aufsetzen + +### Phase 5 + +- bestehende Altformate optional migrieren oder kompatibel lesen + +--- + +## 28. Nicht-Ziele dieses Dokuments + +Dieses Dokument definiert bewusst nicht: + +- konkrete UI-Diagrammgestaltung +- konkrete Score-Formeln +- sportartspezifische Bewertungslogiken +- konkrete KI-Prompttexte +- konkrete Datenbanktabellenmigrationen im Detail +- konkrete API-Endpunkte + +Diese Punkte sind nachgelagerte Spezifikationen. + +--- + +## 29. Offene Erweiterungspunkte + +Die Architektur lässt folgende spätere Erweiterungen zu, ohne die Persistenzbasis zu brechen: + +- zusätzliche `definition_ref`-Kataloge +- sportartspezifische Layer-2-Projektionen +- neue KI-Platzhaltertypen +- zusätzliche Qualitäts- und Provenienzfelder +- Rückspeicherung abgeleiteter Werte als eigene Datensätze +- spezielle Event-Detektion in Layer 2a + +--- + +## 30. Endgültige Entscheidung + +### Die Persistenzschicht speichert: + +- Scalars +- vier einfache technische Composite-Typen + +### Layer 1 übernimmt: + +- Lesen +- Validieren +- minimale Normalisierung +- DTO-Bildung + +### Layer 2a übernimmt: + +- fachliche Analyse +- Diagrammaufbereitung +- Kennzahlen +- Profile +- Scores +- Interpretationen + +### Layer 2b übernimmt: + +- KI-Platzhalter +- KI-Fakten +- KI-Zusammenfassungen +- KI-taugliche Verdichtungen + +--- + +## 31. Kurzfassung für einen Coding Agent + +### Ziel + +Erweitere den bestehenden Value Store um Composite-Werte auf `jsonb`-Basis, ohne fachliche Analyse-Logik in die Persistenz zu verlagern. + +### Implementiere + +1. Unterstützung für `datatype = composite` +2. vier `kind`-Typen: + - `group_set` + - `distribution_set` + - `sequence_set` + - `model_set` +3. Layer-1-Validatoren und Parser +4. kanonische DTOs pro `kind` +5. klare Trennung: + - Persistenz = strukturierte Fakten + - Layer 1 = technische Aufbereitung + - Layer 2a = fachliche Projektionen + - Layer 2b = KI-Projektionen + +### Vermeide + +- Scores im Basis-Composite +- Interpretationen im Basis-Composite +- Diagramm- oder KI-Logik in der Persistenz +- zu viele spezialisierte Composite-Speichertypen + +--- + +## 32. Akzeptanzkriterien + +Die Umsetzung gilt als fachlich passend, wenn: + +1. ein Composite-Value mit `jsonb` gespeichert werden kann +2. Layer 1 den Typ sicher validieren und lesen kann +3. die Persistenz keine fachlichen Layer-2-Interpretationen erzwingt +4. HF-Zonen als `distribution_set` abbildbar sind +5. Intervallblöcke als `sequence_set` abbildbar sind +6. Readiness-Bundles als `group_set` abbildbar sind +7. Modellparameter als `model_set` abbildbar sind +8. Layer 2a daraus fachliche Projektionen erzeugen kann +9. Layer 2b daraus KI-Platzhalter erzeugen kann +10. neue Sportarten ohne neue Persistenzarchitektur integrierbar sind + +--- + +## 33. Schlussformel + +Die Leitentscheidung dieses Konzepts lautet: + +**Die Datenbank speichert Struktur. +Layer 1 liefert kanonische technische Objekte. +Layer 2 erzeugt fachliche Bedeutung. +KI konsumiert diese Bedeutung, nicht die rohe Persistenzstruktur.** + +Damit bleibt das System einfach genug für die Implementierung und offen genug für spätere sportartspezifische Erweiterungen. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 014823a..1ebf572 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,6 +123,11 @@ frontend/src/ - **API:** Admin `/api/admin/training-parameters`, `/api/admin/training-category-parameters`, `/api/admin/training-type-parameters`; Nutzer `GET /api/activity/{id}`, `PUT /api/activity/{id}/metrics`; Platzhalter-Pfad `training_sessions_recent_json` liefert pro Session `session_metrics` (wenn befüllt). - **Frontend:** Admin `/admin/activity-attribute-profiles`; Aktivität → Verlauf → Bearbeiten: Profil-Kennwerte; `api.js` ergänzt. +### Updates (16.04.2026 - Aktivität Phase A abgeschlossen, Phase B gestartet) + +- **Phase A:** Skalar-Kanon schriftlich fixiert — `.claude/docs/technical/ACTIVITY_SCALAR_KANON_TABLE.md`; `ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` v1.1; Agent-Guide Checkliste Phase A erledigt. +- **Phase B:** `GET /api/activity` (Liste) reichert jede Zeile mit `session_metrics` über `enrich_sessions_with_metrics` an (gleiche Merge-Logik wie Detail); Consumer-Audit-Tabelle in Produktions-Architektur-Dok §4 Phase B. + ### Updates (11.04.2026 - Ernährung: TDEE, Bilanz, Kalorien-Score) - **`data_layer/nutrition_metrics.py`:** TDEE für Bilanz: primär **Mifflin–St Jeor BMR × PAL 1,55**, wenn Profil (Größe, Geschlecht, DOB) und Gewicht vorhanden; sonst Fallback **kg × 32,5** (`estimate_tdee_kcal_from_latest_weight`). `get_energy_balance_data` / `calculate_energy_balance_7d` nutzen **tägliche kcal-Summen**. **`_score_calorie_adherence`** (Komponente von `calculate_nutrition_score`) wertet die 7-Tage-Bilanz nach **`profiles.goal_mode`** aus (weight_loss vs. strength/recomposition vs. maintenance/health/endurance). diff --git a/backend/data_layer/activity_data_canon.py b/backend/data_layer/activity_data_canon.py index 1582173..11c171d 100644 --- a/backend/data_layer/activity_data_canon.py +++ b/backend/data_layer/activity_data_canon.py @@ -6,7 +6,8 @@ Kanonische Aufteilung activity_log vs. EAV für Aktivitätssessions. - **Alle anderen Attribute:** ``training_parameters`` + Attributprofil (Kategorie/Typ) → EAV; Lesefallback für bekannte Legacy-Spalten siehe unten. -Normative Doku: .claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md +Normative Doku: .claude/docs/technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md, +ACTIVITY_SCALAR_KANON_TABLE.md """ from __future__ import annotations diff --git a/backend/routers/activity.py b/backend/routers/activity.py index 852fc8e..4be8cc1 100644 --- a/backend/routers/activity.py +++ b/backend/routers/activity.py @@ -32,6 +32,7 @@ from data_layer.activity_persistence_orchestrator import ( new_activity_id, ) from data_layer.activity_time_normalize import normalize_activity_start +from data_layer.activity_session_metrics import enrich_sessions_with_metrics router = APIRouter(prefix="/api/activity", tags=["activity"]) logger = logging.getLogger(__name__) @@ -71,6 +72,12 @@ def _activity_rows_after_list_query(cur): return rows +def _return_activity_list_rows(cur, rows: list) -> list: + """Layer-1: gemergte session_metrics wie Detail-Pfad (Batch).""" + enrich_sessions_with_metrics(cur, rows) + return rows + + # Evaluation import with error handling (Phase 1.2) try: from evaluation_helper import evaluate_and_save_activity @@ -140,7 +147,7 @@ def list_activity( """, (pid, d0, d1, limit), ) - return _activity_rows_after_list_query(cur) + return _return_activity_list_rows(cur, _activity_rows_after_list_query(cur)) cur.execute( f""" SELECT * FROM activity_log @@ -152,7 +159,9 @@ def list_activity( """, (pid, d0, d1, limit), ) - return [r2d(r) for r in cur.fetchall()] + return _return_activity_list_rows( + cur, [r2d(r) for r in cur.fetchall()] + ) if days is not None: if collapse_duplicate_sessions: @@ -173,7 +182,7 @@ def list_activity( """, (pid, days, limit, offset), ) - return _activity_rows_after_list_query(cur) + return _return_activity_list_rows(cur, _activity_rows_after_list_query(cur)) cur.execute( f""" SELECT * FROM activity_log @@ -203,7 +212,7 @@ def list_activity( """, (pid, limit, offset), ) - return _activity_rows_after_list_query(cur) + return _return_activity_list_rows(cur, _activity_rows_after_list_query(cur)) cur.execute( f""" SELECT * FROM activity_log @@ -214,7 +223,7 @@ def list_activity( """, (pid, limit, offset), ) - return [r2d(r) for r in cur.fetchall()] + return _return_activity_list_rows(cur, [r2d(r) for r in cur.fetchall()]) @router.post("")