## Sprint-6B-Source-Audit — Ergebnis **Auditbasis:** Commit `469c86cc672b4a13560cfe683d69b5ec26195bc1`, Branch `main`. Ausschließlich Git-Quellen, Schema, synthetische Fixtures und Tests geprüft. Keine Produktionsdaten, DBs, Reports oder Secrets geöffnet. Working Tree blieb unverändert. ### 1. Apple Health: tatsächlich im Source belegte Identifier Der Importer besitzt **keine Identifier-Allowlist**: Er übernimmt dynamisch `type`/`metric`/`name`/`identifier` und würde daher auch unbekannte Namen speichern. Statisch vollständig inventarisierbar sind nur die **28 ausdrücklich genannten Identifier**. | Identifier | Aggregation | kanonische Einheit / Verhalten | belegter Nutzungspfad | |---|---|---|---| | `step_count` | sum | Quelleneinheit; V5 deklariert „Schritte“ | Analytics `apple_health_analytics.py:35-54`; V3 `health_dashboard_v3.py:38-50`; V5 `data_provider.py:23-28` | | `walking_running_distance` | sum | `m`/`mi`/`km` → `km` | Analytics `:35-54,179-185`; Test `test_apple_health_analytics_v2.py:301-310`; V3 `health_dashboard_v3.py:38-50` | | `active_energy` | sum | `kJ`/`kcal` → `kcal` | Analytics `:35-54,60-63,174-178`; Test `:165-189,383-390` | | `basal_energy_burned`, `dietary_energy` | sum | `kJ`/`kcal` → `kcal` | Analytics `:35-63,174-178` | | `apple_exercise_time`, `apple_stand_time` | sum | s/h/min → `min` | Analytics `:35-69,205-211`; Test für exercise `:313-324` | | `sleep_analysis` | sum | s/min/h → `hr` | Analytics `:35-69,198-204`; V5 `data_provider.py:23-28`; Fixture `dashboard_v5_fixture.py:145-161` | | `dietary_water` | sum | L/mL → `mL` | Analytics `:35-69,193-197`; Test `:313-324` | | `flights_climbed` | sum | Quelleneinheit unverändert | Analytics `:35-54,171-212` | | `carbohydrates`, `protein`, `total_fat`, `dietary_sugar`, `fiber`, `saturated_fat`, `monounsaturated_fat`, `polyunsaturated_fat` | sum | Quelleneinheit unverändert | Analytics `:35-54,171-212` | | `weight_body_mass` | last | g/lb/kg → `kg` | Analytics `:55-69,186-192`; Test `:393-400,426-441`; V3 `health_dashboard_v3.py:38-50` | | `body_mass_index` | last | Quelleneinheit unverändert | Analytics `:55-59,122-139` | | `blood_pressure` | last | Quelleneinheit unverändert; **kein Panel** | Analytics `:55-59,122-139` | | `resting_heart_rate` | avg | Quelleneinheit; V5 deklariert `bpm` | V3 `health_dashboard_v3.py:38-50`; V5 `metric_registry.py:26`; Provider `data_provider.py:23-28` | | `heart_rate_variability` | avg | Quelleneinheit; V5 deklariert `ms` | V3 `health_dashboard_v3.py:38-50`; V5 `metric_registry.py:27`; Provider `data_provider.py:23-28` | | `heart_rate` | avg | Quelleneinheit unverändert | V3 `health_dashboard_v3.py:38-50`; Test `test_apple_health_analytics_v2.py:327-342,357-372` | | `blood_oxygen_saturation` | avg | Quelleneinheit unverändert | V3 `health_dashboard_v3.py:38-50`; Korrelation `multimodal_correlations.py:54-63` | | `respiratory_rate` | avg | Quelleneinheit unverändert | V3 `health_dashboard_v3.py:38-50`; Korrelation `multimodal_correlations.py:54-63` | | `physical_effort` | avg | Quelleneinheit; synthetischer Vertrag nutzt `MET` | V3 `health_dashboard_v3.py:38-50`; Korrelation `multimodal_correlations.py:54-63`; Test `test_apple_health_analytics_v2.py:209-226` | | `oxygen_saturation` | generischer avg-Fallback | Quelleneinheit | Nur Null-/Missingness-Fixture `dashboard_v5_fixture.py:139-143`; nicht in V3/V5/Korrelation registriert | **Gemeinsamer Parserpfad** - JSON/CSV-Eingang: `apple_health_import.py:217-225` - Health-Auto-Export-Shape `data.metrics[].data[]`: `:167-185` - Weitere Wrapper und dynamische Metrik-Arrays: `:186-214` - Identifier-Felder und Wertauswahl: `:121-157` - Speicherung ohne Metrik-Allowlist: `:273-301` - Analytics-Regel: `SUM_METRICS`/`LAST_METRICS`, sonst automatisch `avg`: `apple_health_analytics.py:122-139` - Einheitennormalisierung: `:171-212` - Tagesaggregation, Zürich-Tag, Source-/Unit-Selektion: `:410-482` - Synthetische Aggregationstests: `test_apple_health_analytics_v2.py:79-441` ### 2. Blutdruck und Vitalzeichen | Oberfläche | tatsächlicher Vertrag | Datei:Zeile | Befund | |---|---|---|---| | Apple-Health-Import | Wertsuche endet mit `systolic`, `diastolic` | `apple_health_import.py:121-127` | `first()` liefert nur **einen** Wert. Bei einem Record mit beiden Feldern gewinnt `systolic`; `diastolic` bleibt nur im `raw_json`. | | Apple Analytics | nur zusammengesetzter Identifier `blood_pressure`, Aggregation `last` | `apple_health_analytics.py:55-59,122-139` | Keine getrennten systolischen/diastolischen Reihen, keine mmHg-Kanonisierung. | | Canonical Schema | generische Zeile `(datum, parameter, wert, einheit, notizen)` | `database/schema.sql:505` | Keine Parameter-Allowlist, keine Panel-/Komponenten-ID, keine Aggregationsregel. | | Legacy-Fallback-Schema | abweichend `(typ, wert REAL, referenzbereich, gerat, …)` | `health_manager.py:108-119` | Schema-Drift gegenüber `database/schema.sql`. | | V5 Registry/Provider | kein Vitalzeichen und kein Blutdruck | `metric_registry.py:23-30`; `data_provider.py:23-28,322-363` | Nicht suchbar, nicht als Serie verfügbar. | | Tests/Fixture | kein positiver Blutdruck-Panelvertrag | relevante Apple-Tests `test_apple_health_analytics_v2.py:79-441` | Keine systolisch/diastolisch-, Pairing-, Einheit- oder Paneltests. | **Fazit Blutdruck:** Das Sprint-6B-Ziel „Panel mit getrennten systolischen/diastolischen Reihen“ ist **nicht implementiert**. Der vorhandene Importpfad kann ein kombiniertes Blutdruckobjekt sogar verlustbehaftet normalisieren. ### 3. Kanonische Laborparameter Die einzige strikte öffentliche Labor-Allowlist enthält **8 kanonische Parameter**: | kanonischer Parameter | erlaubte Raw-Slugs | Einheit | Datei:Zeile | |---|---|---|---| | CRP | `c_reaktives_protein_crp`, `crp` | mg/L | `dashboard_v5/lab_registry.py:9-12` | | D-Dimer | `d_dimer` | µg/L | `:12` | | Fibrinogen | `fibrinogen` | g/L | `:13` | | Faktor VIII | `faktor_viii` | % | `:14` | | Thrombozyten | `thrombozyten` | Tsd/µL | `:15` | | Leukozyten | `leukozyten` | Tsd/µL | `:16` | | Ferritin | `ferritin` | ng/mL | `:17` | Die Korrelations-Allowlist entspricht denselben acht Parametern: `multimodal_correlations.py:44-53`. **Freigabebedingungen und Felder** | Bereich | vorhandene Felder/Regeln | Datei:Zeile | |---|---|---| | Laborbeobachtung | `dokument_id`, `parameter_name`, `wert`, `einheit`, `reference_min`, `reference_max`, `abnahme_datum`, `befund_datum`, `wert_original`, `quelle`, `validierungsstatus`, `source_type`, `reference_range_source`, `verified_against_original`, `canonical_document_id`, `provenance_note` | `database/schema.sql:193-205` | | Staging/Review | `referenzbereich`, min/max, Dokumentseite, Extraktionsmethode, Confidence, Dokument-ID, Status | `database/schema.sql:207-232` | | Dokumentmetadaten | Hash, Quelle, Review/Processing Quality, Dokumentdatum, Institution, Drive-/lokale Felder, extrahierter Text | `database/schema.sql:68-79` | | V5-Auswahl | validiert, Original geprüft, `scanned_original`, Einheit vorhanden; kanonisches Parameter/Einheit-Paar; eindeutige Zeile pro Tag; exakt numerischer Wert | `data_provider.py:197-237` | | V5-Ausgabe | Parameter, Wert, Einheit, Datum, `reference_min/max`, Qualität, Source-Typ | `data_provider.py:230-235`; Contract `contracts.py:204-223` | | Synthetische Abdeckung | nur CRP als positiver Fixture-Fall | `dashboard_v5_fixture.py:199-211`; `test_health_dashboard_v5.py:154-187` | **Wichtige Abgrenzung:** `generate_doctor_report.py:64-68` und `health_dashboard_v3.py:298-303` enthalten breitere Anzeige-/Reportlisten. Diese sind **keine kanonische Sprint-6B-Allowlist** und dürfen nicht als freigegebene Registry-Metriken gezählt werden. ### 4. Bestehende Metric Registry | Registry-ID | Label | Einheit | Aggregation | Source-Nachweis | |---|---|---:|---|---| | `symptom.total` | Symptome total | 0–21 | sum | `metric_registry.py:24`; Provider `data_provider.py:97-118` | | `apple.sleep` | Schlaf | h | sum | `metric_registry.py:25`; `APPLE_SPECS` `data_provider.py:23-28` | | `apple.resting_heart_rate` | Ruhepuls | bpm | avg | `metric_registry.py:26`; Provider `:23-28` | | `apple.hrv` | HRV | ms | avg | `metric_registry.py:27`; Provider `:23-28` | | `apple.steps` | Schritte | Schritte | sum | `metric_registry.py:28`; Provider `:23-28` | | `nutrition.histamine` | Histamin-Score | Score | sum | `metric_registry.py:29`; Provider `data_provider.py:240-264,330-335` | Registry-Modell und aktuelle Felder: `metric_registry.py:7-20`. Der Bundle-Contract verlangt exakt diese Registry: `contracts.py:71-109,143-176`. ### 5. Konkrete Lücken gegen Sprint 6B 1. **Metrikkatalog V2 fehlt:** Aktuelle Registry hat sechs Einträge und nicht die geforderten Felder für Aliasse, explizite Quelle/Provenienz, Werttyp, Messfrequenz, Reference-Policy, Suchbegriffe, Drill-down, Korrelationsfreigabe/Lags und Datenschutzklasse (`metric_registry.py:7-30` vs. Sprint-Spezifikation `docs/source/...sprint6.md:749-759`). 2. **Keine programmatisch begrenzte Apple-Allowlist:** Import und Analytics akzeptieren beliebige Identifier; unbekannte Namen erhalten implizit `avg` und eine beliebige Quelleneinheit (`apple_health_import.py:121-157`; `apple_health_analytics.py:122-139`). Das ist kein sicherer Sprint-6B-Katalogvertrag. 3. **V5 deckt nur vier Apple-Reihen ab:** Schlaf, Ruhepuls, HRV, Schritte (`data_provider.py:23-28`). Distanz, Aktivenergie, SpO₂, Atemfrequenz, Physical Effort, Gewicht/BMI und Blutdruck fehlen. 4. **Blutdruckpanel fehlt vollständig:** keine Komponenten-IDs, kein Pairing, keine mmHg-Normalisierung, kein Registry-Panel, kein Vertragstest. 5. **Vitalzeichen-Tabelle unerschlossen:** generisches Schema ohne Parameter-Allowlist; keine Providerfunktion, Registry oder Tests. Zusätzlich Schema-Drift zwischen kanonischem Schema und Legacy-Erzeugung. 6. **Laborwerte sind keine Explorer-Metriken:** Acht erlaubte Parameter erscheinen nur als begrenzte Kartenliste; keine Metric-IDs, Serienaggregation, Overlay-Gruppe oder Suchaliasse. 7. **Labor-Dokumentkontext geht in V5-Ausgabe verloren:** Schema besitzt `dokument_id`/`canonical_document_id`, Provider selektiert und liefert diese Felder jedoch nicht (`schema.sql:193-205` vs. `data_provider.py:205-235`). Sprint-6B fordert das verknüpfte Dokument für verifizierte Beobachtungen. 8. **Keine observation-spezifische Reference-Policy im Katalog:** min/max werden pro Beobachtung geliefert, aber Registry-Metadaten beschreiben die Policy nicht. 9. **Read-only Sprint-6B-APIs fehlen:** Server stellt nur HTML, Assets, Dokument-/Reportdateien und Symptom-POST bereit (`health_dashboard_server.py:45-54,420-485`). Keine `/api/v1/metric-catalog`, `/series`, `/day`, `/events`, `/labs` oder `/search`. 10. **Suche/Aliasse fehlen:** insbesondere Blutdruck/BD/RR, systolisch/diastolisch, Puls, CRP/D-Dimer/Faktor VIII sowie gruppierte Suchresultate. 11. **Keine Serienverträge für Limits/Zeitraum/Auflösung/Abdeckung:** Der vorhandene statische Bundle-Contract begrenzt zwar Bundlegröße und Punktzahl (`contracts.py:20-29`), ersetzt aber keine parameterisierte, allowlist-geprüfte Serien-API. 12. **Testabdeckung zu schmal:** gute Analytics-v2-Tests für Deduplizierung, Zürich-Zeit, Einheiten und Aggregation; jedoch keine Sprint-6B-API-, Katalog-V2-, Suche-, Blutdruckpanel-, Vitalzeichen- oder Labor-Dokumentlink-Contracttests. **Dateien erstellt/geändert:** keine. **Probleme:** keine Produktionsprüfung vorgenommen; deshalb bewusst keine Aussage über tatsächlich vorhandene Datenbestände.