## Ergebnis: **FAIL** - **Tests:** `27 passed` (gesamte Suite), davon `11 passed` für Analytics v2. - **Qualität:** `ruff` und `git diff --check` ohne Befund. - **Dateien verändert:** keine. ### Important Findings 1. **Deduplizierung erfolgt vor kanonischer Zeit-/Einheiten-Normalisierung** `_row_key()` verwendet rohe Zeitstempel und Einheiten. Dieselbe Beobachtung wird daher doppelt gezählt, wenn Exporte sie äquivalent, aber z. B. einmal als `kcal` und einmal als `kJ` oder mit unterschiedlichen UTC-Offsets darstellen. Synthetisch reproduziert: eine Energiebeobachtung ergab nach Normalisierung **200 statt 100**, `deduplicated_records == 0`. 2. **Coarse-Erkennung ist nicht zuverlässig** `_coarse_factors()` erkennt gruppierte Exporte erst ab drei Datumswerten. Ein unvollständiger Wochen-/Monatsexport mit ein oder zwei Werten wird als direkt behandelt und nicht auf Tagesäquivalente umgerechnet. Umgekehrt können regelmäßig spärliche direkte Messungen als coarse fehlklassifiziert werden. Damit ist Direct-vs-Coarse-Auflösung nicht belastbar. 3. **Quellenauswahl widerspricht der dokumentierten Source-Class-Semantik** `_select_source()` gruppiert nach vollständigem normalisiertem Quellnamen (`source_key`), nicht nach `source_class`. Mehrere Watch-Bezeichnungen derselben Klasse konkurrieren daher gegeneinander; gültige Datensätze einer Alias-Quelle können vollständig verworfen werden. 4. **Einheiten-Normalisierung ist unvollständig** Nur Energie `kJ → kcal` wird konvertiert. Bei Distanz, Gewicht, Wasser oder Dauer werden kompatible Einheiten nicht normalisiert, sondern über `_select_unit()` bis auf die häufigste Einheit verworfen. Das kann Tageswerte systematisch unterzählen; `unit_for()` basiert zusätzlich auf der global häufigsten Roheinheit und kann von der tatsächlich ausgewählten Tagesreihe abweichen. 5. **30-Tage-Coverage nutzt nicht konsequent Zürich-Lokaltage** Das Dashboard berechnet das Fenster mit `datetime.now().date()` in der Host-Zeitzone, während Analytics v2 `Europe/Zurich` verwendet. Rund um Mitternacht bzw. auf Hosts mit anderer Zeitzone kann das angebliche Fenster der „letzten 30 abgeschlossenen Tage“ um einen Tag verschoben sein. 6. **Coverage-/Summary-Pfad skaliert schlecht** `_load_rows()` lädt mit `SELECT *` die vollständige Historie einschließlich nicht benötigter großer Spalten wie `raw_json`; ein Coverage-Fenster begrenzt die SQL-Abfrage nicht. Dashboard und `summary()` wiederholen diese Vollhistorien-Scans pro Metrik, wodurch Laufzeit und Speicherbedarf mit wachsender Importhistorie unnötig stark steigen.