Digest-Metrik-tone-Vertrag (farbige „Satz der Woche"-Karte, live)
StratoClaude → MacClaude, 13.06.2026 (SILV-239 + SILV-241-Ehrlichkeits-Update, Sub von SILV-110 Statistik-Raum, R2). Die „Satz der Woche"-Karte braucht Ampelfarbe je Kennzahl, ohne dass der Client selbst wertet (= Drift,
delight.md §4).GET /api/stats/digestliefert darum additiv einmetrics[]-Array. Die tone-Vergabe folgt den gelockten Wertungsregeln A/B/C aus dem Statistik-Streitgespräch (statistik-redesign/synthese.md §3) — siehe auch Statistik-Reife-Vertrag. Code-belegt:app/backend/main.py(_digest_metrics+ Endpoint); Contract-Testsapp/tests/contract/test_native_preis.py.
Das Feld (additiv — summary bleibt Fallback)
GET /api/stats/digest?user=<id>[&week=<Montag-ISO>] liefert zusätzlich:
{
"summary": "1 von 7 Tagen geloggt · Ø 670 kcal/Tag · …", // unverändert, Fallback
"metrics": [
{ "key": "days_logged", "label": "1 von 7 Tagen geloggt", "value": "1/7", "tone": "neutral" },
{ "key": "activity", "label": "2 Aktivitäten (+590 kcal)", "value": "2×", "tone": "good" },
{ "key": "avg_kcal", "label": "Ø 670 kcal pro Tag", "value": "Ø 670 kcal", "tone": "neutral" }
// 'top_food' erscheint NUR ab ≥3 geloggten Tagen & Top ≥2× (sonst weggelassen)
],
"top_food_id": 2 // Drilldown-Anker fürs Top-Produkt (nice-to-have, nullable)
}
labelundvaluesind fertige de-Strings — der Server textet, der Client rendert nur (kein App-Texten).value= die prägnante Zahl,label= der Satz.tone∈{good, neutral, warn}(semantisch). Der Client mappt nur:good → kelp,warn → coral,neutral → ink. Keine Client-Logik, welcher tone wohin — die Bedeutung kommt komplett vom Server.- Reihenfolge = Render-Reihenfolge (days_logged, activity, avg_kcal, [top_food]).
metricsist nie leer, solange der Endpoint 200 liefert.
tone-Vergabe (Server-Hoheit, gelockte Wertungsregeln)
digest ist eine kcal/Körper-Fläche → es gilt Regel A: kcal wird
NIEMALS warn/rot; good nur für Leistung/Kontinuität (Streak), nie für
Verzicht/„im Budget". Essen ist intim — Scham killt die wochenjunge Tracking-
Gewohnheit. Konkret:
| Metrik | good (kelp) | neutral (ink) | warn |
|---|---|---|---|
| days_logged (Tage mit Eintrag) | ≥ 6 (Kontinuitäts-Leistung) | < 6 | nie |
| activity (Anzahl Aktivitäten) | ≥ 1 vorhanden (= Leistung) | 0 (= „keine Aktivitäten") | nie |
| avg_kcal (Ø kcal/Tag) | — | immer | nie |
| top_food (häufigstes Produkt) | — | immer (wenn gezeigt) | nie |
| leere Woche (0 Tage geloggt) | — | „Noch nichts geloggt diese Woche" | nie |
Bewusst raus seit SILV-241: die in_budget-Quote-Metrik fliegt ganz weg
(auch grün ist ein Körper-Urteil). avg_kcal/top_food bleiben tonlos-neutral
(ohne persönliches Ziel ist ein Ø weder gut noch schlecht). „Keine Aktivität" =
neutraler Fakt, kein roter Stups.
„am häufigsten" nur mit Stichprobe (Regel C)
top_food erscheint nur, wenn days_logged ≥ 3 und das Top-Produkt
mindestens 2× vorkommt. Sonst wird es weggelassen (Stille ist ehrlicher
als eine Zahl aus dünner Luft). top_food_id (Drilldown-Anker) wird unabhängig
davon mitgeliefert, solange überhaupt ein Top-Produkt existiert.
Cold-start / leere Woche
days_logged == 0 → metrics = genau ein NEUTRALES Häppchen
{"key":"days_logged","value":"0/7","label":"Noch nichts geloggt diese Woche","tone":"neutral"}
(nie rot — keine rote Begrüßung, Regel A), top_food_id = null. summary bleibt
„Noch nichts geloggt diese Woche."
Client-Hinweis (SILV-110)
Karte oben: pro metrics[]-Eintrag ein Chip — value prominent, label als
Kontext, eingefärbt nach tone. summary nur als Fallback, wenn metrics mal
fehlt (alte Backends). top_food_id (falls gesetzt) verankert den Tap aufs
Top-Produkt → Artikel-Detail. Den Heute-Beat-Satz (eine Achse, server-gewählt)
liefert separat GET /api/stats/today-beat — siehe
Statistik-Reife-Vertrag.
Bewusst NICHT
- Kein Drilldown-Anker für Monat/Tag im digest (Top-Produkt trägt
top_food_id;/api/stats/overviewliefert die übrigen IDs — später bei Bedarf). metrics/top_food_idsind additiv; diesummarybleibt unangetastet als Fallback (der Client schwenkt aufmetrics[]).