## Ergebnis: 6C ist mit der vorhandenen same-origin Read-only-API umsetzbar, aber nicht nur als Renderer-Tausch Die sichere Minimalmigration ist: **neuen lokalen ECharts-`chart-controller` nur für den Explorer ergänzen, Chart.js-/Snapshot-Explorer als expliziten Fallback belassen, API ausschließlich per bestehender HttpOnly-Browser-Session nutzen.** Für Referenzbänder, freie Zeiträume und eindeutige Wochen-/Tages-Drill-downs bestehen jedoch konkrete Vertragslücken. ### Relevante aktuelle Stellen - **V5-Shell / Explorer-Markup:** `scripts/health/dashboard_v5/render.py` - `render()` Zeilen **74–90**: Explorer mit Rohwert/Basislinie, daily/weekly, Presets, Picker, Trend-Canvas `#explorer-chart`, Event-Lane `#explorer-event-track`, Lag/Scatter/Phase. - Zeilen **21–24 / 63–64**: globale Zeit-Presets nur `7/30/90/180/all`; **kein freier Von-/Bis-Bereich** und **keine Such-/Command-Bar**. - Zeile **96**: Chart.js und das aktuelle V5-Script werden immer geladen; ECharts nur im 6A-Prototyp bei Flag (Zeilen **49–52**). - **Statischer Snapshot und vorhandene Basislinien:** `scripts/health/dashboard_v5/data_provider.py` - `rolling_baseline()` Zeilen **86–94**: past-only rolling median. - `build_bundle()` Zeilen **322–363**, speziell **340–349**: schreibt `baseline` je Punkt in den statischen V5-Bundlevertrag. - Das ist der aktuelle Chart.js-Explorer-Pfad; API-Serien enthalten diese Werte **nicht**. - **Read-only API:** `scripts/health/dashboard_v5/read_api.py` - `parse_range()` Zeilen **159–176**: vollständiges `from`/`to`, max. **3.660** Kalendertage, keine Zukunft. - `_series()` Zeilen **591–634**: Serienvertrag mit `metric`, `label`, `unit`, `aggregation`, `aggregation_rule`, `resolution`, `source`, `coverage`, `timezone`, `points`. - `_weekly()` Zeilen **532–567**: Wochenpunkte mit `week`, `date`, `value`, `observation_count`, `observed_days`, `quality`. - `_events()` Zeilen **637–710**: getrennte Ereignisse, inklusive Intervallen `date`/optional `end_date`. - `_search()` Zeilen **742–772**: gruppierte Suche, inklusive ISO-Tag-Treffer. - `dispatch_api()` Zeilen **799–852**: feste Endpoint-Allowlist. - **Browser-Session / keine Token-Leakage:** `scripts/health/health_dashboard_server.py` - `POST /api/v1/browser-session`: `_handle_browser_session()` Zeilen **667–716**. - API mit Bearer **oder** HttpOnly-Session: `_handle_api()` Zeilen **718–755**. - V5-CSP erhält ausschließlich `connect-src 'self'`: `_handle()` Zeilen **607–635**, `_send_bytes()` Zeilen **808–848**. - Session-Cookie: `HttpOnly`, `SameSite=Strict`, `Path=/api/v1`, begrenztes `Max-Age` (Zeilen **700–707**). - **Katalogdaten für UI-Gating:** `scripts/health/dashboard_v5/metric_catalog_v2.py` - `MetricV2` Zeilen **20–41** enthält `unit`, `overlay_group`, `baseline_rule`, `reference_policy`, Frequenz/Präzision und `drill_down_target`. - Blutdruck-Komponenten haben identische Einheit `mmHg` und gemeinsames Panel (Zeilen **149–153**). --- ## Minimaler 6C-Dateiplan 1. **`scripts/health/dashboard_v5/render.py`** - Explorer-Shell ergänzen, nicht ersetzen: - Command-/Search-Bar mit Ergebniscontainer und stabilem Hook. - Freie Range-Inputs (`from`, `to`) plus Apply/Reset; Presets bleiben erhalten. - ECharts-Host statt nur Canvas, aber Chart.js-Canvas als Fallback beibehalten. - zugängliche Tabelle und einheitlicher Tages-Callback-Hook. - ECharts als lokales Script nur für die freigeschaltete 6C-Explorer-Variante ausliefern; Chart.js weiter lokal laden. - Fallback sichtbar machen, wenn ECharts fehlt/initialisiert nicht/Serie nicht kompatibel ist — **kein stiller Datenverlust**. 2. **Neues Quellmodul, z. B. `scripts/health/dashboard_v5/chart_controller.js`** - Adaptergrenze gemäß Architekturziel: ECharts-Optionen, Instanz-Lifecycle, `dispose()`, Resize, Fehler-Fallback. - Alle API-Aufrufe mit: ```js fetch(url, { credentials: 'same-origin' }) ``` ohne `Authorization`, ohne Bootstrap-Secret, ohne Local-/SessionStorage. - Gemeinsamer Updatepfad für Suche, Auswahl, Preset, Range, Cadence und Chart-Interaktion. - ECharts: - Zeitachse mit expliziten ISO-Tagen, Nullwerte/Lücken nicht interpolieren. - `dataZoom`, Pan, Brush, Crosshair, Legend Toggle/Solo. - maximal zwei Rohwert-Overlays; zweite Y-Achse nur bei explizit definierten kompatiblen Achsengruppen. - Event-Lane als getrennte, zeitlich gleich skalierte Ansicht, nicht als Gesundheitswert-Dataset. - Klick auf Datenpunkt dispatcht `health:day-select` mit exakt `{ date: "YYYY-MM-DD" }`. - Bei 401: neutraler Session-unavailable-Zustand, keine erneute Authentisierung aus JavaScript. 3. **`scripts/health/health_dashboard_server.py`** - Nur Asset-Allowlist ergänzen (analog `ASSET_ROUTES`, Zeilen **61–67**). - CSP/Origin/Session-Logik unverändert lassen. - Kein Browser-Bootstrap aus dem Frontend hinzufügen: Das getrennte Basic-Secret darf ausschließlich außerhalb des JS-Kontexts verwendet werden. 4. **`scripts/health/dashboard_v5/read_api.py`** — gezielte Vertragsergänzungen erforderlich - **Range-Anker:** Top-Level-`today` in Katalog- oder Serienantwort ergänzen, damit 7/30/90/180 in `Europe/Zurich` ohne Browser-Uhr-/Mitternachtsdrift berechnet werden. - **Basislinien:** `_series()` muss serverseitig berechnete, ausschließlich vergangenheitsbasierte Baseline pro Punkt liefern, falls der bestehende „Persönliche Basislinie“-Modus API-basiert bleiben soll. Aktuell bietet nur der Snapshot sie. - **Laborreferenzen:** Serienpunkte für Labore müssen die verifizierte beobachtungsspezifische `reference` mitführen oder ein klar begrenzter Join mit `/api/v1/labs` muss dokumentiert werden. Aktuell verwirft `_metric_points()` für Labore die Referenzdaten (Zeilen **495–502**). - **Wochenanker:** Wochenvertrag explizit ergänzen: `week_start`, `week_end`, `display_anchor_date` und/oder `drilldown_date`. Das jetzige `date=min(distinct)` ist ein zufällig frühester beobachteter Tag, nicht zwingend ISO-Wochenbeginn. - Keine Erweiterung um freie Tabellen-/Spalten-/Sortierparameter. 5. **`scripts/health/dashboard_v5/metric_catalog_v2.py`** - Für zwei Achsen fehlt eine explizite, maschinenlesbare Kompatibilitätsregel. `overlay_group` ist vorhanden, aber nicht als „gleiche Einheit“, „zweite Achse erlaubt“ oder „nicht gemeinsam darstellbar“ spezifiziert. - Minimal: zusätzliches Feld wie `axis_compatibility_group` und `axis_side_policy` oder eine klar getestete Definition von `overlay_group`. - Ohne diese Ergänzung sollte 6C fail-closed bleiben: Rohwertvergleich nur bei **identischer `unit`**, eine Y-Achse; Basislinienmodus darf separat seine bestehende Obergrenze verwenden. 6. **Dokumentation** - `docs/dashboard_v5_architecture_target.md`: 6C-Vertrag für Browser-Session, Range-Anker, Baseline/Reference-Band, Wochen-Drill-down und Fallback präzisieren. - `docs/sprint6b-metric-catalog-read-api.md`: API-Response-Erweiterungen versionieren und die unveränderte Bearer-/Session-Trennung festhalten. - Neues `docs/sprint6c-…md`: klare Scope-Grenze „Explorer-Engine/Read-only; kein V4-, Push-, Deploy- oder Statistikwechsel“. --- ## Vertragslücken / Risiken 1. **Persönliche Basislinie nicht in API-Serien — blocker für API-basierten Basislinienmodus.** Der Snapshot berechnet sie (`data_provider.rolling_baseline()`), `_series()` nicht. Browser-Neuberechnung wäre gegen das Zielbild („keine neue medizinische Statistik im Browser“). 2. **Beobachtungsspezifische Laborreferenz nur über `/labs`, nicht über `/series`.** Damit ist ein korrektes Referenzband pro Serienpunkt derzeit nicht atomar abrufbar. Ein generisches „Normalband“ wäre ausdrücklich unzulässig. 3. **Zwei Achsen nicht ausreichend spezifiziert.** `unit` und `overlay_group` existieren, aber es gibt keine dokumentierte Policy, welche unterschiedlichen Einheiten gemeinsam auf linker/rechter Achse zulässig sind. Gleichheit der Einheit ist die einzige derzeit sichere Regel. 4. **Wochenpunkt → ISO-Tag ist technisch vorhanden, semantisch nicht stabil genug.** `_weekly()` setzt `date` auf den frühesten beobachteten Tag. Für `health:day-select` muss klar sein, ob der Callback auf diesen Beobachtungstag, ISO-Montag oder einen Week-Drawer führt. 5. **Keine freie Range-UI und kein API-`today`-Anker.** Die API validiert Ranges korrekt, aber die V5-Shell enthält nur globale Presets. Ohne serverautoritativen Zürich-„heute“-Wert drohen Preset-Off-by-one-Fälle. 6. **Keine produktive JS-Session-Bootstrap-Route.** Das ist korrekt und darf nicht „behoben“ werden: Der Browser darf weder Basic-Secret noch Bearer kennen. 6C darf nur die bereits bestehende HttpOnly-Session verwenden und bei 401 fail-closed bleiben. 7. **Aktueller Chart.js-Explorer-Code liegt im Asset-Bereich.** Gemäß Auftrag habe ich keine Assets geöffnet. Seine intern benannten Funktionen konnten daher nicht direkt inventarisiert werden; die vorhandenen DOM- und Browser-Verträge sind aber präzise über `render.py` und `tests/browser/dashboard_v5.spec.js` abgedeckt. --- ## Erforderliche/naheliegende Tests für 6C - **Bestehende Testanker erhalten** - `tests/browser/dashboard_v5.spec.js` - Explorer-Registry/Gating: Zeilen **206–246** - Presets, Wochen, Event-Lane und fail-closed Auswahl: **248–299** - echte Nullwerte: **301–319** - 200%-Text/Chart-Geometrie: **321–357** - Cockpit-Lücken und Event-on-gap: **133–193** - `tests/test_dashboard_v5_sprint6b_api.py` - freie Ranges/Bounds/BP: **241–263** - 7/30/90/180/alle und Zürich: **265–303** - API-Redaction/No-token/Origin: **569–775** - `tests/test_dashboard_v5_sprint6b1_integration.py` - Cookie/CSP/API ohne Bearer: **39–94** - metric-specific weekly coverage: **97–143** - `tests/browser/dashboard_v5_api_session.spec.js` - same-origin `fetch(...credentials:'same-origin')`, kein Bearer, CSP-Blockierung extern: **14–54**. - `tests/browser/dashboard_v5_echarts_prototype.spec.js` - bereits gute ECharts-Referenz für Zoom/Brush/ARIA/markArea/markPoint/ISO-Callback: **28–214**. - **Neue 6C-Tests ergänzen** - Suche: `/search`-Gruppen, Auswahl einer Metrik/eines ISO-Tags, keine Pfade/Tokens in DOM/Requests. - Range: 7/30/90/180 und freier inklusiver Zeitraum gegen serverautoritativen Zürich-Anker, Reverse/future/3661 Tage UI-fail-closed. - ECharts-Fallback: fehlendes/geworfenes ECharts ergibt funktionierenden Chart.js-Explorer und klare Statusmeldung. - Auswahl: max. zwei Rohwertreihen; achseninkompatible Auswahl wird abgelehnt; definierte zweite Achse korrekt beschriftet. - Referenzen: persönliche Baseline ausschließlich aus API-Feld; Laborbänder nur exakt beobachtungsspezifisch; keine generischen Normal-/Ampeltexte. - Event-Lane: Start, Ende, Lückentag und Wochenfall gegen exakt denselben gerenderten Zeitbereich. - Point-to-ISO: Chartklick **und** Tabellen-/Tastaturaktion dispatchen denselben `health:day-select`-Payload; tägliche und wöchentliche Semantik getrennt testen. - Token-Hygiene: Netzwerkrequests enthalten keinen Bearer/Basic-Header; HTML/DOM/Storage enthalten keinen Token und keine Session-ID. ### Änderungen / Einschränkungen - **Keine Dateien erstellt oder verändert.** - Ausschließlich Quellcode, Tests und Dokumentation gelesen. - Keine DB, Umgebungsdatei, generierten Reports, Secrets, Assets oder Deployment-Dateien geöffnet.