Statistik-Reife & Heute-Beat-Vertrag (SILV-241, live)
StratoClaude → MacClaude, 13.06.2026 (SILV-241, Sub von SILV-110 Statistik-Raum, R2, high). Aus dem Statistik-Streitgespräch (
app/ios-native/reimplementierung/ statistik-redesign/synthese.md §3/§5, Dennis-gelockt). Empirie aus den DB-Counts: Geld 33 Monate reich, kcal wochenjung → die Helligkeit der Aussagen folgt der Datenreife. Alles additiv, Anti-Drift (Server erzeugt Bedeutung/Satz/tone, Client zeigt nur). Code:app/backend/main.py; Contract-Testsapp/tests/contract/test_native_preis.py.
Gelockte Wertungsregeln (gelten überall in der Statistik)
- Regel A — Körper/kcal: NIE moralisieren, NIE rot. Kein
warnauf kcal/Budget.goodnur für Leistung/Kontinuität (Streak, „6/7 geloggt"), nie für Verzicht. - Regel B — Geld/Preis: sachliches Verdikt erlaubt (
warnok), wenn die Referenz klar/ehrlich ist (≥ 3 Vergleichsmonate, laufender Monat/Woche nie Vergleichsbasis). - Regel C — kein Verdikt ohne Stichprobe → Stille statt Zahl aus dünner Luft.
- Cold-start (leere Woche, Montagmorgen) = neutral, nie warn.
Reife-Schwellen (eine Stelle: STATS_MATURITY)
| Achse | reif ab | Datenbasis |
|---|---|---|
| spending (Geld) | ≥ 3 abgeschlossene Monate mit Bons | familienweit (receipts ohne user_id) |
| kcal | ≥ 14 geloggte Tage dieses Nutzers | diary pro user |
| activity | ≥ 2 Wochen mit Aktivität dieses Nutzers | activities pro user |
| top_food | ≥ 3 geloggte Tage & Top ≥ 2× | diary pro user/Woche |
1. GET /api/stats/today-beat?user=<id> → {axis, text, tone}
EIN server-formulierter Wochen-Rückblick-Satz für den Heute-Beat. Der Server wählt
die reifste Achse — Geld führt (33 Monate); kcal/Aktivität nur, wenn für
diesen Nutzer reif. Alle unreif → axis="onboarding" (ehrlicher Vorfreude-Satz).
{ "axis": "spending", "tone": "good",
"text": "Diese Woche 62 € unter dem Schnitt (59 € statt Ø 121 €)." }
axis ∈ {spending, kcal, activity, onboarding}.toneauf kcal NIEwarn(Regel A) — kcal-Satz istgood(≥6 Tage geloggt) oderneutral. Geld darfwarn(Regel B, klare Wochen-Referenz, laufende Woche nie Basis). Aktivität:good/neutral, niewarn.- Jaqueline-Beispiel: 1 geloggter Tag → kcal nie reif → today-beat fällt auf Geld (familienweit) zurück; keine Familien-kcal-Aussage auf dünner Basis.
- Auswahl-Logik (Designwahl, justierbar): Priorität spending → activity → kcal →
onboarding, jeweils nur wenn reif und es diese Woche eine Aussage gibt. Da Geld
immer reif ist, führt es de facto — bewusst so (Streitgespräch „Geld führt").
Soll später unter mehreren reifen Achsen rotiert werden, ist das ein Ein-Stellen-
Eingriff in
stats_today_beat.
2. GET /api/stats/headline → {value, sentence, tone}
Der „Raum-Held": die EINE überraschende Geld-Langzeit-Erkenntnis (Regel B). Aktuell: der teuerste abgeschlossene Monat als Anker (laufender Monat zählt nicht).
{ "value": "859 €", "tone": "neutral",
"sentence": "November 2023 war euer teuerster Monat – 859 €." }
value = prägnante Zahl, sentence = fertiger Satz. tone neutral (Wow-Fakt, keine
Scham). Bei zu wenig Historie: value=null + ehrlicher Platzhalter-Satz.
3. GET /api/stats/overview — months additiv
Je Monatseintrag zusätzlich:
{ "month": "2026-06", "total": 172.65, "bons": 3,
"is_current": true, "delta_to_avg": null }
is_current: laufender Monat (partiell) — nie Vergleichsbasis.delta_to_avg: € über/unteravg_total; beim laufenden Monatnull.- Top-Level
avg_total: Ø nur über abgeschlossene Monate (Regel B-Referenz).
4. GET /api/stats/kcal/{uid} — neue Form {count, days[]}
{ "count": 9,
"days": [ { "date": "2026-06-13", "kcal": 2668, "over_budget": true }, … ] }
count= Datenpunkte (Client-Anzeige-Gate: erst ab ≥ 5 sinnvoll).over_budgetje Tag:kcal > (base + Tages-Aktivität)(ADR-30, dynamisches Tagesbudget → Balken-Farbe, keine konstante Linie). Das ist ein neutraler Balken-Indikator, kein moralischer tone — Regel A bleibt (kcal nie „rot geschämt"); wie der Client das färbt, ist seine Delight-Entscheidung.- ⚠️ Formwechsel (vorher nackte Liste): Client/Web lesen
.daysmit Array-Fallback (Array.isArray(r) ? r : r.days).
Bewusst zurückgestellt
- Monats→Bons-Drilldown-Endpoint (Raum = Durchgang; Top-Produkt nutzt vorhandenes
price-insightvia Artikel-Steckbrief). Erst bauen, wenn die Nutzung es rechtfertigt.