Zuletzt aktualisiert:

Dynamisches kcal-Budget — Vertrag (live)

StratoClaude → MacClaude, 21.06.2026 (SILV-336, Sub von SILV-298 „Ring-/Budget-Redesign Weg B"). Antwort auf die Backend-Bestellung über den Agent-Bus. Der Server erzeugt die Budget-Bedeutung, der Client rendert nur (Anti-Drift). Code-belegt: app/backend/main.py (Budget-Kern + Endpoints), app/backend/db.py (Tabellen budget_settings, day_budget); Contract-Tests app/tests/contract/test_budget.py.

Modell (gelockt, SILV-298)

Formeln (Client-Live-Vorschau MUSS identisch rechnen)

defizit_tag = rate_kg_week × 7700 / 7
boden       = boden_pct × erhaltung
ziel        = max(erhaltung − defizit_tag, boden)      # gesund gekappt
ziel_eff    = ziel + (activity_kcal > schwelle ? bonus_pct × activity_kcal : 0)
kcal_left   = ziel_eff − eaten

Gesund-Kappung (ehrlicher Default, StratoClaude-Entscheidung): das Ziel wird nie unter den Boden gedrückt — der Boden ist der ehrliche Mindestwert (boden_pct × Erhaltung, konfigurierbar). So gibt es nur einen Schwellenwert statt einer zweiten Magic-Zahl. Greift die Kappung, ist capped=true und cap_hint trägt einen Hinweistext. Wer einen härteren absoluten Boden will, hebt boden_pct.

Per-Tag einfrieren — kein Zurückrechnen (hart, Dennis 21.06.)

Die Erhaltung (TDEE) ist ein rollender Messwert. Ohne Einfrieren würde ein Tag von vor drei Wochen mit dem heutigen TDEE-Fenster gerendert — das ist explizit verboten. Darum:

Was man isst (eaten) ist immer live — nur die Bedeutung (Erhaltung/Ziel/ Boden/Bonus) friert ein. kcal_left = ziel_eff(eingefroren) − eaten(live).

DTO (additiv in day_summary, /api/diary/range-Einträgen, Snapshot)

Feld Typ Bedeutung
erhaltung_kcal int Erhaltung des Tages
erhaltung_source measured|fallback|override Herkunft der Erhaltung
erhaltung_calc_kcal int|null errechneter TDEE als Hinweis (auch bei Override); null bei cold-start
ziel_kcal int Ziel (gekappt)
boden_kcal int Boden
training_bonus_kcal int angewandter Trainingstag-Bonus (0, wenn unter Schwelle)
ziel_eff_kcal int effektives Ziel = ziel + bonus
capped bool wurde aufs Boden-Minimum angehoben?
cap_hint string|null Hinweistext, wenn capped
activity_kcal int Tages-Aktivitäts-kcal (nur Info, eGYM-Verdrängung ADR-32)
zielrichtung abnehmen|halten|aufbauen SILV-407, rein abgeleitet aus dem Vorzeichen von rate_kg_per_week (+ abnehmen, 0 halten, aufbauen) — keine eigene Spalte
deadband_kcal float SILV-407, wirksame Totzone für die Client-Einfärbung der Aufnahmezahl (Aufnahme vs. verbrauch_kcal, s. aktivitaet-konsolidierung-vertrag.md); Default 150, Bereich 0..1000

Legacy-Felder bleiben (Web-Frontend lauffähig), aber neu gemappt: kcal_base = erhaltung_kcal, kcal_budget = ziel_eff_kcal, kcal_left = ziel_eff_kcal − kcal_eaten, kcal_activity = activity_kcal.

Settings-API (versioniert, valid_from = heute)

GET /api/budget/settings?user_id=&date= → effektive Einstellungen für den Tag (Default heute) + erhaltung_calc_kcal (TDEE-Hinweis) + valid_from (null, wenn nur Defaults greifen):

{ "rate_kg_per_week": 0.0, "erhaltung_override_kcal": null, "boden_pct": 0.5,
  "training_threshold_kcal": 500.0, "training_bonus_pct": 0.5,
  "valid_from": "2026-06-21", "erhaltung_calc_kcal": null }

PUT /api/budget/settings (Body: user_id, rate_kg_per_week, erhaltung_override_kcal, boden_pct, training_threshold_kcal, training_bonus_pct, deadband_kcal) → speichert eine neue Version ab heute; alte Tage bleiben unberührt. Mehrfaches PUT am selben Tag überschreibt die heutige Version. Gibt die effektiven Werte zurück. Effektiv für Tag D = jüngste Version mit valid_from ≤ D; ohne Version greifen die Defaults. Achtung: eine heute gesetzte Version wirkt NIE auf einen Tag vor valid_from — auch nicht rückwirkend per Freeze (der Freeze nutzt die zum Zieltag effektive Version, s. o.).

deadband_kcal ist serverseitig validiert (0 ≤ x ≤ 1000, HTTP 422 sonst). zielrichtung ist NICHT Teil von Settings (rein abgeleitet, s. DTO oben) und erscheint daher nicht in der GET/PUT-Antwort.

Defaults

rate_kg_per_week=0 · erhaltung_override_kcal=null · boden_pct=0.5 · training_threshold_kcal=500 · training_bonus_pct=0.5 · deadband_kcal=150.