| name | agent-nutrition |
| description | Kalorien-/Makro-Tracker für Paul Bader (v3). Erfasst per Prompt gegessene Mahlzeiten, schätzt kcal + Protein/Carbs/Fett und schreibt sie ins Notion Food-Log; nimmt manuelle Gewichts-Einträge; zieht via Whoop-API täglich gemessenen Kalorienverbrauch, Strain und Gewicht in die Gewicht-DB; trackt Wasser (Tagessumme, Ziel 3 l); und gibt einen Tages-Überblick (Ist vs. Ziel, Über-/Unterschuss, Whoop-Verbrauch, Wasser, grobe Gewichtsprojektion). Klammert unsauber getrackte Tage aus der Statistik aus (neutrale Wertung statt Phantom-Defizit). Aktivieren bei: "das hab ich gegessen", "kalorien", "food log", "was hab ich heute gegessen", "wieviel kcal", "ich wiege", "gewicht eintragen", "kalorien-überblick", "wie steht mein tag", "whoop", "strain", "verbrauch", "wieviel verbraucht", "wasser", "liter wasser", "wieviel getrunken", "barcode", "etikett", "in openfoodfacts eintragen", "off eintragen", "ernährungs-report", "tagebuch für die ernährungsberaterin", "report erzeugen", "tag ausklammern", "heute nicht getrackt", "der tag zählt nicht", "tag war daneben", "tag zurücksetzen". Trägt fehlende Barcode-Produkte mit Etikettendaten in OpenFoodFacts ein (Write-back) und erzeugt druckbare Ernährungs-Reports für Dritte. KEIN Ernährungsberater/medizinischer Rat — reines Tracking + Schätzung, non-advisory.
|
agent-nutrition — Kalorien-/Makro-Tracker (v3)
Rolle
Erfasst Essen und Gewicht per Prompt und liefert einen Tages-Überblick. Nährwerte schätzt
der Skill selbst (LLM); Ablage in Notion. Kein Ernährungsberater, kein medizinischer
Rat — reines Tracking. Bei gesundheitlich substanziellen Fragen an eine Fachperson verweisen.
Store & IDs
Notion, unter Seite „Ernährung & Kalorien-Tracker". IDs aus memory/state/notion-ids.json:
- Schreiben (MCP
notion-create-pages): Parent { data_source_id: <ernaehrung.food_log_data_source> }
bzw. <ernaehrung.gewicht_data_source>.
- Lesen (read-only via
scripts/notion_query.py): Aliasse food_log, gewicht, lebensmittel.
- Nährwert-Lookup (Kaskade v3, Details unten):
scripts/ch_lookup.py (lokaler CH-Seed
data/ch-nwdb-v7.csv, kein Key), scripts/food_lookup.py (OpenFoodFacts Search-a-licious,
kein Key), scripts/fdc_lookup.py (USDA FoodData Central, FDC_API_KEY in .env).
Cache/Lernsystem = DB Lebensmittel-Referenz (Schreiben:
ernaehrung.lebensmittel_data_source; Lesen: Alias lebensmittel).
- OFF-Write-back:
scripts/off_writeback.py — trägt fehlende Barcode-Produkte in
OpenFoodFacts ein (einzige sanktionierte Script-Schreib-Ausnahme, OFF hat keinen MCP).
Nur Etiketten-Daten, nie Schätzwerte (Regel unten). OFF_USER/OFF_PASSWORD in .env;
Fotos-Eingang: inbox/off/ (gitignored). Kandidaten-Findung: scripts/off_candidates.py
(Modus 6 — Wochencheck; read-only gegen Notion + OFF, Skip-Gedächtnis
memory/state/off-nudge.json).
- Whoop-Daten:
scripts/whoop_sync.py (Whoop v2 API, OAuth). Liefert je Tag whoop_kcal,
strain, gewicht_kg als JSON — es schreibt NICHT selbst (Boundary: Script liest, MCP
schreibt). Zielspalten liegen in derselben Gewicht-DB: Whoop kcal, Strain,
Gewicht (kg) (Gewicht bleibt damit Single-Source). Secrets: WHOOP_CLIENT_ID/_SECRET
in .env, rotierende Tokens in secrets/whoop-tokens.json (beides gitignored).
Ziele / Einstellungen (Config — von Paul zu pflegen)
Gesetzt 01.07., aggressiver justiert (Paul: eher langsamer Metabolismus). Basis: 190 cm,
106 kg, 43 J., Büro + ~3× Crossfit/Woche; Mifflin-St-Jeor, Faktor ~1,4 → TDEE ~2800 kcal.
Update 15.07.: BIA-Messung (Dextra) → BMR 2167 kcal — über der Formel (~2040), die
„langsamer Metabolismus"-Annahme war falsch herum. Mit Faktor 1,4 TDEE eher ~3030;
reales Defizit bei Ziel 1900 damit ~1100 kcal/Tag ≈ 1,0 kg/Wo (passt zu Woche 1: −1,1 kg).
Entscheid Paul 15.07.: 1900 bleibt (schneller als Soll-Linie, Puffer für Urlaub Ende
August; bei Energie-/Recovery-Einbruch gilt weiter die Locker-Regel unten). Für künftige
Kalibrierung gegen BMR 2167 rechnen, nicht gegen Mifflin.
- Tages-kcal-Ziel: 1900 (aggressiveres Defizit ~900 kcal/Tag ≈ 0,8 kg/Woche)
- Makro-Ziel: Protein 185 g · Carbs 130 g · Fett 70 g (≈ 1890 kcal) — Protein
hoch, um im großen Defizit Muskelmasse zu halten.
- Gewicht aktuell 106 kg · Ziel 95 kg · Horizont 30.09.2026 (Ende Q3)
- Wasser-Ziel: 3.000 ml/Tag — zählt nur pures Wasser (still/Sprudel); Tee, Kaffee
und alle anderen Getränke laufen als Essens-Einträge über Modus 1 (Entscheid 09.07.2026).
- Projektion: ~11 kg / ~13 Wochen (Ziel-Rate ~0,85 kg/Wo); 1900er-Ziel ≈ 0,8 kg/Wo trifft
95 kg um Ende September. Kalibrieren statt raten: realen Gewichtstrend über 2–3 Wochen
gegen die Soll-Linie halten — bei langsamerem Metabolismus ist das reale Defizit größer
(schneller); überschätzt Mifflin das TDEE, Ziel nach unten nachziehen. Non-advisory: bei
Energie-/Recovery-Einbruch (Crossfit) Defizit lockern.
Datum
„Heute" = das reale Tagesdatum zum Zeitpunkt des Loggens — vor dem Schreiben die
Systemzeit prüfen (Get-Date / date), nicht dem Konversationskontext vertrauen:
Sessions laufen über Mitternacht weiter, und ein Chat vom Vortag loggt sonst auf gestern
(real passiert 08./09.07.2026). Ein anderer Tag gilt nur, wenn Paul ihn explizit nennt;
relative Angaben absolut auflösen (JJJJ-MM-TT), bevor geschrieben wird.
Modus 1 — Essen erfassen (Default)
Trigger: „das hab ich gegessen …", freie Essensbeschreibung.
- Text in Items + Menge zerlegen (Stück / Gramm / Portion); relative Mengen absolut machen.
- Pro Item die Nährwerte über die Nährwert-Kaskade (unten) holen und auf die Menge
rechnen (Werte/100 g × Gramm, bzw. × Portionen).
- Mahlzeit zuordnen — nur Frühstück / Mittag / Abend, kein Snack mehr
(Entscheid 09.07.2026; Alt-Einträge mit „Snack" bleiben unverändert). Zeitbänder:
bis 10:30 Frühstück · 11:00–14:30 Mittag · ab 17:00 Abend. Außerhalb der Bänder
oder unklar → eine kurze Rückfrage, zu welcher Mahlzeit es gehört — nie raten.
- Pro Item eine Zeile ins Food-Log (
notion-create-pages): Eintrag, date:Datum:start
= heute (is_datetime 0), Mahlzeit, kcal, Protein (g), Carbs (g), Fett (g).
Mehrere Items → mehrere Zeilen in einem Call.
- Cache füllen (Promotion): Treffer aus Kaskaden-Schritt 2–6 in die
Lebensmittel-Referenz schreiben (Name, Marke, kcal/Makros pro 100 g, Portion,
Barcode) — damit beim nächsten Mal Schritt 1 greift. Neu dazu zwei Felder:
Quelle (Select): CH-NWDB / OpenFoodFacts / USDA-FNDDS / USDA-SR /
USDA-Branded / Web / Schätzung / Etikett (cache_quelle aus fdc_lookup.py
verbatim übernehmen). Neue Optionen legt Notion beim Schreiben automatisch an.
Etikett = Paul hat die Werte von der Packung abgetippt/diktiert (egal auf
welcher Fläche, auch mobil) — Konfidenz verifiziert, und die einzige Quelle, die
als Etiketten-Nachweis für einen späteren OFF-Beitrag zählt (Modus 6). Liest Paul
beim Erfassen Werte vom Etikett vor, IMMER Etikett statt Schätzung/Web setzen
und den Barcode miterfassen, wenn greifbar.
Konfidenz (Select): verifiziert (CH-NWDB, OFF, USDA-Generika unverarbeitet,
Hersteller-Seite) / US-Rezeptur (verarbeitetes Generikum aus USDA — Brot, Wurst,
Milchprodukte: österreichische Rezepturen weichen ab — sowie USDA-Branded-Treffer)
/ (LLM-Schätzung).
Nährwert-Kaskade v3 (pro Item, in dieser Reihenfolge — erster belastbarer Treffer gewinnt)
- Cache:
python scripts/notion_query.py --db lebensmittel und auf Name/Marke matchen.
Treffer → exakte Werte von dort, keine Suche.
- CH-Seed (Generika):
python scripts/ch_lookup.py "<Begriff>" — lokale Schweizer
Nährwertdatenbank (BLV, V7.0; data/ch-nwdb-v7.csv, ~1.190 generische Lebensmittel,
deutschsprachig, kein Netz nötig). Top-3-Kandidaten mit Score. Eigenheiten: Helvetismen
(Poulet statt Hähnchen, Peperoni statt Paprika, Zucchetti … — das Script hat eine
Alias-Map, im Zweifel CH-Vokabel mit anfragen); roh vs. gekocht/gebraten sind eigene
Einträge — Zubereitungsform bewusst wählen, nicht blind Platz 1. Kein Treffer →
leeres Array, Kaskade läuft weiter.
- OpenFoodFacts (Marken-/Barcode-Produkte):
python scripts/food_lookup.py "<Marke Produkt Flavor>" → Kandidaten mit kcal + Makros pro 100 g. Besten Treffer
wählen; bei mehreren plausiblen (z. B. Flavor unklar) eine kurze Rückfrage.
Barcode-Produkt nicht gefunden → Write-back anbieten (Abschnitt unten); die Kaskade
läuft unabhängig davon weiter.
- USDA FDC (Generika-Long-Tail, Zubereitungsformen):
python scripts/fdc_lookup.py "<begriff englisch>" — den Suchbegriff übersetzt der Skill vorher ins Englische,
präzise mit Zubereitung („chicken breast, cooked, skin not eaten"). Priorität
FNDDS → Foundation → SR Legacy → Branded (macht das Script); Werte pro 100 g,
Portionen liefert zuverlässig nur FNDDS. Kandidaten gegen die Eingabe plausibilisieren
(„egg, boiled" rankt auch „Peanuts, boiled") — nie blind Platz 1. cache_quelle aus
dem Script-Output für das Quelle-Feld übernehmen; Feld basis beachten
(Branded-Flüssigprodukte sind pro 100 ml normalisiert). FDC_API_KEY liegt in
.env (seit 12.07.; ohne Key fiele das Script auf DEMO_KEY zurück, 30 Req/h).
- Web-Fallback:
WebSearch/WebFetch (Hersteller-Seite), Werte übernehmen.
- Schätzung: nur wenn nichts auffindbar → LLM-Schätzung mit genannter
Portionsannahme; im Cache
Quelle = Schätzung, Konfidenz = geschätzt.
OFF-Write-back (Barcode nicht gefunden)
Liefert Kaskaden-Schritt 3 für ein Barcode-Produkt keinen Treffer, automatisch
nachfragen, ob Paul Etikett/Fotos hat — aber nie automatisch eintragen
(Entscheid 12.07.2026). Dieser reaktive Pfad greift nur in Code-Sessions; den
Mobile-Blindspot (Paul loggt überwiegend mobil, dort läuft die Kaskade nicht →
0 Beiträge bis 15.07.2026) deckt der OFF-Wochencheck (Modus 6) ab.
- Harte Regel: Es gehen ausschließlich Etiketten-Daten an OFF — vom Etikett
abgetippte Nährwerte und/oder Etiketten-Fotos. Niemals LLM-Schätzwerte an OFF
senden. Fehlen Etikettendaten, wird nicht eingetragen — Punkt. Das Script erzwingt
das (
--von-etikett ist Pflicht; Teil-Nährwerte werden abgelehnt, nie mit Defaults
aufgefüllt).
- Ja, Etikett vorhanden: Fotos nach
inbox/off/ (Namenskonvention front* /
naehrwerte* / zutaten*; werden nach Upload gelöscht), dann
python scripts/off_writeback.py --barcode <EAN> --name … --marke … --menge "250 g" --kcal … --protein … --carbs … --fett … --von-etikett --inbox-ok (--inbox-ok
bestätigt, dass ALLE Fotos in inbox/off/ zu diesem Barcode gehören; bei explizitem
--fotos pfad,… entfällt es). Default ist Staging
(world.openfoodfacts.net); Produktion nur mit --prod + echtem OFF-Konto
(OFF_USER/OFF_PASSWORD in .env; Konten sind je Umgebung getrennt). Kurz
bestätigen — und das Item zusätzlich normal ins Food-Log + in die Referenz,
dort mit OFF-Eintrag = Eintragsdatum (nur bei Write-back auf Produktion setzen;
Staging-Tests zählen nicht als Beitrag).
- Nein: Kaskade läuft normal weiter (Web/Schätzung), kein Write-back.
- Eigenheiten: das Script gibt selbst JSON aus (
produkt_geschrieben, fotos[].ok,
produkt_url) — das ist das Erfolgssignal, nicht die OFF-Rohantwort
({"status":1,"fields saved"}); anonym gehen nur Foto-Uploads durch,
Produktdaten-Writes brauchen Login; Bilder min. 640 px breit; Test-Barcodes aus der
GS1-Internal-Range 20x….
Modus 2 — Gewicht erfassen
Trigger: „ich wiege …", „Gewicht eintragen". Upsert, nie blind anlegen — der Daily
Brief legt die heutige Zeile morgens vor, eine zweite Tageszeile splittet Wasser-Summe
und Whoop-Upsert:
- Heutige Zeile suchen (wie Modus 2b Schritt 2: lokal
notion_query.py --db gewicht,
sonst notion-search auf Messung = JJJJ-MM-TT).
- Vorhanden →
notion-update-page mit Gewicht (kg) (manueller Weigh-in
überschreibt dabei auch einen Whoop-Wert — Modus-4-Regel gilt nur andersherum).
Nicht vorhanden → notion-create-pages (Messung = Datum als Text,
date:Datum:start = heute, Gewicht (kg)).
- Kurz bestätigen.
Modus 2b — Wasser erfassen
Trigger: „1 Liter Wasser", „halber Liter Wasser", „Wasser eintragen". Zählt nur pures
Wasser (Config oben) — alles andere ist ein Essens-Eintrag (Modus 1).
- Menge in ml auflösen.
- Heutige Zeile der Gewicht-DB holen — die existiert im Normalfall schon (der Daily Brief
legt sie morgens vor, Modus 4 Schritt 3). Lokal:
python scripts/notion_query.py --db gewicht --columns Datum,"Wasser (ml)", clientseitig auf das Datum matchen. Ohne Scripts
(Handy/Plugin): notion-search nach dem heutigen Datum (Messung-Titel = JJJJ-MM-TT)
in der Gewicht-DB, dann die Seite fetchen.
- Kumulativ addieren —
notion-update-page mit der neuen Tagessumme. Nur als
Fallback, wenn wirklich keine Zeile existiert (Brief lief nicht): notion-create-pages
(Parent ernaehrung.gewicht_data_source: Messung = Datum als Text, date:Datum:start,
Wasser (ml)) — vorher sicher sein, dass die Suche leer war, nie blind anlegen
(Duplikat-Zeilen splitten die Tagessumme).
- Kurz bestätigen, kumuliert gegen das Ziel: „2,0 / 3,0 l".
Tages-Status (gilt für Modus 3, 5 und den Report)
Die Gewicht-DB trägt seit 12.08.2026 zwei Felder je Tag: Tages-Status (Select:
leer = getrackt · Nicht getrackt · Unvollständig) und Status-Grund (Text).
Gesetzt wird er über Modus 7. Für jede Auswertung gilt:
- Aufnahme-Statistik (Ø kcal/Makros, Ist-vs-Ziel, Adherence): ausgeklammerte Tage
komplett raus, Nenner verkleinern. Ein nicht getrackter Tag hat keine gültige
Aufnahme-Zahl — jeder eingesetzte Wert wäre erfunden.
- Energiebilanz / Projektion: ausgeklammerte Tage neutral, nicht ausgeschlossen —
kcal_in := Whoop kcal desselben Tages ⇒ Saldo 0. Nie als 0-kcal-Tag mitrechnen:
das erzeugt genau an den Tagen ein Phantom-Defizit, an denen eher zu viel gegessen
wurde. Fehlt der Whoop-Wert, bleibt der Saldo unbekannt — nicht schätzen.
- Kein Straf-Aufschlag (Entscheid Paul 12.08.2026): neutral ist das Maximum an
Annahme, das die Daten hergeben. Das Korrektiv ist der gemessene Gewichtstrend, nicht
ein erfundener Zuschlag. Bei Widerspruch Saldo ↔ Waage gewinnt die Waage.
- Abdeckung immer ausweisen: „Ø 1.870 kcal über 5 von 7 Tagen". Ohne diese Zeile
verbessert jede Ausklammerung stillschweigend den Schnitt.
- Teil-Einträge bleiben erhalten (Entscheid Paul 12.08.2026: markieren statt löschen).
Sie zählen nicht, werden aber nicht gelöscht — der Tages-Status ist die Wahrheit, nicht
die Abwesenheit von Zeilen.
Modus 3 — Überblick / Status
Trigger: „Kalorien-Überblick", „wie steht mein Tag", „was hab ich heute gegessen",
„wieviel hab ich verbraucht", „Strain", „Whoop".
0. Tages-Status prüfen (Spalte Tages-Status aus der Gewicht-Abfrage in Schritt 3).
Steht der Tag auf Nicht getrackt → kein Ist-vs-Ziel, sondern kurz: „Tag ist
ausgeklammert (Grund) — zählt neutral, kein Defizit." Nur Whoop/Strain/Wasser zeigen.
Auf Wunsch zurücknehmen (Modus 7, Punkt 5).
- Heutige Food-Log-Zeilen lesen:
python scripts/notion_query.py --db food_log --columns Datum,Mahlzeit,kcal,"Protein (g)","Carbs (g)","Fett (g)",Eintrag
und clientseitig auf das heutige Datum filtern (notion_query hat noch keinen Datumsfilter).
- Summen (kcal + Makros) bilden; gegen die Config-Ziele halten → Über-/Unterschuss.
Kein Ziel gesetzt → nur Ist-Summen + kurzer Hinweis, dass das Ziel fehlt.
- Whoop-Verbrauch (Info, Ziel bleibt fix) + Wasser: Gewicht-DB lesen
python scripts/notion_query.py --db gewicht --columns Datum,"Gewicht (kg)","Whoop kcal",Strain,"Wasser (ml)"
und clientseitig auf den Tag filtern. Wasser-Ist gegen das 3-l-Ziel zeigen. Gemessenen Whoop kcal-Verbrauch + Strain neben
das feste kcal-Ziel stellen. Bei hohem Strain (grob ≥ 14) einen non-advisory Hinweis
„höherer Verbrauch, Defizit ggf. lockern" — kein automatisches Anheben des Ziels
(Entscheidung 05.07.2026: nur anzeigen). Leeres Whoop kcal in der heutigen Zeile ist
normal (Whoop schreibt erst am Folgemorgen, die Zeile legt der Brief vor) — dann den
gestrigen Whoop-Wert zeigen; fehlt auch der, kurz sagen, dass kein Sync lief.
- Gewichtsprojektion (grob, optional): Gewichts-Trend aus derselben Abfrage; einfache
Rechnung kumulierter kcal-Saldo ÷ ~7700 ≈ kg. Klar als Schätzung kennzeichnen.
Whoop-Gewicht ändert sich nur, wenn Paul es in Whoop aktualisiert — bei flacher Linie
darauf hinweisen statt einen „stabilen" Trend zu behaupten.
- Ausgabe knapp, tabellarisch. Keine Ernährungs-Ratschläge.
Modus 4 — Whoop-Sync (Verbrauch, Strain, Gewicht)
Trigger: „Whoop synchronisieren", „Whoop-Daten holen"; läuft zudem automatisch im Daily
Brief (siehe daily-brief.ps1). Non-interaktiv fähig.
- Daten holen:
python scripts/whoop_sync.py --from <heute−4> --to <gestern>.
Bewusst ein Nachhol-Fenster statt des Defaults (Default = nur gestern, weil der
heutige Whoop-Tag tagsüber meist noch nicht SCORED ist): der geplante Brief läuft nur
werktags, deshalb fallen mit dem Default Freitag und Samstag dauerhaft durch —
der Montagslauf holt als „gestern" nur den Sonntag. Befund 12.08.2026 an den Echtdaten:
Lücken exakt Fr 31.07., Sa 01.08., Fr 07.08., Sa 08.08. Vier Tage decken auch ein
verlängertes Wochenende ab. Der Upsert in Schritt 2 ist idempotent — bereits gefüllte
Tage werden nur mit denselben Werten überschrieben, Gewicht (kg) bleibt geschützt.
Interaktiv für Einzel-Nachträge weiterhin --date JJJJ-MM-TT.
Ausgabe ist JSON {tage:[{datum,whoop_kcal,strain,gewicht_kg}],fehler:[]}.
Ist noch nie --login gelaufen / Token weg → das Script sagt es; dann Paul bitten,
einmalig python scripts/whoop_sync.py --login auszuführen (Browser-Autorisierung).
- Pro Tag upsert in die Gewicht-DB (eine Zeile je
Datum, kein Duplikat):
- Existierende Zeile suchen:
python scripts/notion_query.py --db gewicht --columns Datum,"Gewicht (kg)","Whoop kcal",Strain,
clientseitig auf datum matchen.
- Vorhanden →
notion-update-page: Whoop kcal und Strain setzen.
- Nicht vorhanden →
notion-create-pages (Parent ernaehrung.gewicht_data_source):
Messung = Datum als Text, date:Datum:start = Tag (is_datetime 0), Whoop kcal, Strain.
- Gewicht (kg): nur für gestern füllen, und auch dort nur, wenn die Zielzeile
noch leer ist. Zwei Gründe, beide hart:
(a) ein manueller Weigh-in (Modus 2) hat Vorrang und darf nicht vom (oft statischen)
Whoop-Wert überschrieben werden;
(b) für ältere Tage im Nachhol-Fenster niemals schreiben —
whoop_sync.py
(fetch_weight_kg) liefert das aktuell in Whoop hinterlegte Gewicht, nicht das
des jeweiligen Tages, und stempelt denselben Wert auf jeden Tag des Bereichs. Ein
Backfill würde damit Gewichts-Historie erfinden. Befund 12.08.2026: ein Range-Lauf
über 9 Tage lieferte 9×105,2 kg, während in Notion je Tag 104,0–105,9 stehen.
Alternativ für reine Nachhol-Läufe.
Modus 5 — Ernährungs-Report (Tagebuch für Dritte)
Trigger: „Ernährungs-Report", „Tagebuch für die Ernährungsberaterin", „Report erzeugen".
python scripts/nutrition_report.py [--from JJJJ-MM-TT --to JJJJ-MM-TT]
(Default: letzte 7 Tage). Deterministisch, read-only — liest Food-Log +
Gewicht/Whoop via REST, schreibt nichts nach Notion.
- Output doppelt: Konsolen-Übersicht (Tages-Key-Facts: kcal/Makros,
Whoop-Verbrauch, Strain, Saldo, Gewicht, Wasser + Zeitraum-Ø) und druckoptimierte
HTML-Datei unter
reports/ (gitignored — Gesundheitsdaten, nie committen).
PDF: Datei im Browser öffnen → Strg+P → „Als PDF speichern".
- Inhalt: Kopf (Zeitraum, Basisdaten, Ziele) · Tages-Übersichtstabelle ·
Zeitraum-Bilanz (Ø, Gewichtsveränderung) · Detail je Tag nach Mahlzeiten ·
Methodik-Fußnote (KI-/DB-Schätzung, Whoop-Messgrenzen). Ziele/Basisdaten
pflegt der CFG-Block im Script (mit dem Config-Block oben synchron halten).
- Tage ohne Protokoll erscheinen als „kein Protokoll" — nie auffüllen/schätzen.
Modus 6 — OFF-Wochencheck (Beitrags-Nudge)
Trigger: „OFF-Check", „OFF-Kandidaten", „was können wir zu OpenFoodFacts beitragen";
zudem wöchentlich als Einzeiler im Montags-Weekly-Brief (siehe daily-brief.ps1 —
der Brief nudged nur, die Abarbeitung passiert interaktiv in einer Code-Session).
Ersetzt seit 15.07.2026 den rein reaktiven Beitragspfad als Haupt-Mechanik (Befund:
Paul loggt überwiegend mobil → die Kaskade samt Write-back-Angebot lief praktisch nie;
0/64 Referenz-Einträge beigetragen).
python scripts/off_candidates.py --update-snapshot — read-only: Referenz-Zeilen ohne
OFF-Eintrag gegen OFF-Produktion prüfen. Kategorien: bereit (Quelle = Etikett →
Etiketten-Nachweis liegt vor), braucht_etikett (fehlt/unvollständig in OFF, aber
keine Etiketten-Provenienz), Rest wird gezählt (ohne_barcode, off_vollstaendig,
verworfen).
bereit: je Produkt kompakt vorschlagen (Name, Barcode, Werte) und nach Pauls
Bestätigung off_writeback.py … --von-etikett --prod aufrufen — das Bestätigungs-Gate
bleibt, nie automatisch eintragen. Danach in der Referenz OFF-Eintrag = heute setzen
(notion-update-page).
braucht_etikett: gesammelt fragen, ob Etikett/Packung noch da ist (Werte abtippen
und/oder Fotos nach inbox/off/). Drei mögliche Antworten (Skip-Gedächtnis
memory/state/off-nudge.json; kein Drängen — eine Frage pro Produkt, Pauls Antwort
ist final):
- Etikett da → wie Write-back-Abschnitt oben verfahren.
- Kommt wieder (Produkt wird wohl erneut gekauft) →
--warten <Barcode>: stumm,
bis die Referenz-Zeile Quelle=Etikett bekommt (mobile Erfassung beim nächsten
Kauf) — dann läuft sie automatisch als bereit ein, ohne neue Rückfrage.
- Weg/kommt nicht wieder →
--verwerfen <Barcode>: wird nie wieder angefragt.
- Harte Regel unverändert: nur Etiketten-Daten an OFF, nie Web-/Schätzwerte — auch
wenn
braucht_etikett-Kandidaten fertige Zahlen aus dem Web tragen. Quelle=Web ist
kein Etiketten-Nachweis.
- Kadenz wöchentlich, nicht täglich (Entscheid 15.07.2026): OFF-Beitrag ist nicht
zeitkritisch, es laufen nur wenige Kandidaten pro Woche auf, und die Frische löst nicht
der Nudge, sondern die Erfassung am Esszeitpunkt (mobil
Quelle=Etikett, s. Kaskade
Schritt 5 und Mobil-Abschnitt).
Modus 7 — Tag ausklammern (nicht sauber getrackt)
Trigger: „heute nicht getrackt", „der Tag zählt nicht", „gestern war daneben",
„Tag ausklammern", „Tag zurücksetzen". Zweck: Tage, an denen das Protokoll unvollständig
blieb, sauber aus der Statistik nehmen — statt sie als 0-kcal-Tag durchlaufen zu lassen
(die einzige Stelle, an der der Tracker sonst systematisch schmeichelt).
- Datum auflösen (Regel im Abschnitt „Datum": relative Angaben absolut machen,
Systemzeit prüfen). Nur der explizit genannte Tag — nie mehrere auf Verdacht.
- Bestand zeigen, bevor etwas passiert: Food-Log-Zeilen des Tages lesen
(
notion_query.py --db food_log, clientseitig auf das Datum filtern) und kurz
auflisten, was schon drinsteht („3 Einträge, 890 kcal — Frühstück + Mittag").
Nichts gefunden → auch gut, dann ist es nur die Markierung.
- Grund erfragen, wenn Paul keinen genannt hat — eine kurze Frage, freier Text
(„Buffet", „unterwegs", „vergessen"). Optional; leer ist erlaubt.
- Status setzen — Gewicht-DB, Zeile des Tages, Upsert wie Modus 2/2b
(Zeile existiert im Normalfall schon, der Daily Brief legt sie vor):
- Vorhanden →
notion-update-page: Tages-Status = Nicht getrackt, Status-Grund.
- Nicht vorhanden →
notion-create-pages (Parent ernaehrung.gewicht_data_source):
Messung = Datum als Text, date:Datum:start, Tages-Status, Status-Grund.
Food-Log-Zeilen bleiben unangetastet — markieren statt löschen (Entscheid Paul
12.08.2026). Sie zählen ab sofort nirgends mehr mit; die Markierung ist die Wahrheit.
Nur wenn Paul ausdrücklich „und leeren" / „Einträge löschen" sagt, die Zeilen des
Tages zusätzlich archivieren — dann vorher die Liste aus Schritt 2 bestätigen lassen
(einzige Stelle in diesem Skill mit Bestätigungs-Gate, weil Löschen nicht umkehrbar ist).
- Zurücknehmen: „doch getrackt" / „Ausklammerung aufheben" →
Tages-Status und
Status-Grund leeren. Der Tag zählt wieder normal.
- Unvollständig statt komplett raus: Sagt Paul „Abendessen fehlt, Rest passt", ist
Unvollständig die richtige Stufe. Behandlung im Report identisch zu Nicht getrackt
(raus aus den Aufnahme-Mittelwerten, neutral in der Bilanz) — der Unterschied ist die
Lesbarkeit für Dritte, nicht die Rechnung.
- Knapp bestätigen: was markiert wurde, wie viele Einträge stehen bleiben, und dass der
Tag neutral (Saldo 0) statt als Defizit zählt.
Lücken-Wächter (passiv, gehört zu Modus 3 und Modus 5): Ein Tag mit Protokoll unter
800 kcal und ohne gesetzten Tages-Status ist fast immer eine Lücke, kein
Fastentag. Nicht stillschweigend mitrechnen — nachfragen: „09.08. hat 340 kcal —
getrackt oder ausklammern?" Entscheiden tut Paul; der Skill markiert nie von selbst.
Das fängt den eigentlichen Fehlerfall: nicht geloggt und nicht markiert.
Tracking-Quote im Blick behalten: Fällt die Abdeckung unter ~5 von 7 Tagen über zwei
Wochen, im Überblick einmal sachlich benennen. Die Notausgangstür darf nicht zur Haustür
werden — non-advisory, kein Drängen, eine Zeile.
Plugin-/Mobil-Kontext (Desk/Web/App)
Dieser Skill wird via Plugin pauls-skill-kit auch in Desktop/Cowork geladen — dort
fehlen scripts/, data/ und .env. Lokal-only sind: Kaskaden-Schritt 1–4
(Cache-Query, CH-Seed, OFF-Lookup, USDA), OFF-Write-back, Modus 4 (Whoop), Modus 5
(Report) und Modus 6 (OFF-Wochencheck). Modus 7 (Tag ausklammern) läuft überall —
er braucht nur Notion-Lesen + Schreiben; statt notion_query.py in Schritt 2 den
Tagesbestand via notion-search/notion-fetch holen. Gerade mobil ist er wichtig, weil
dort die meisten Lücken entstehen. Im Plugin-Kontext gilt: Kaskade = Web-Fallback +
Schätzung, Lesen nur über notion-search/notion-fetch (Row-Queries sind plan-gated),
Schreiben via MCP wie gewohnt. Mobiler Beitrag zum OFF-Loop: liest Paul beim Loggen
Werte vom Etikett vor (Barcode-Produkt unbekannt/nicht auffindbar), legt der mobile
Kontext sie in der Lebensmittel-Referenz mit Quelle=Etikett + Barcode ab — nach OFF
schreibt weiterhin nur der Desktop (Modus 6 sammelt genau diese Zeilen als bereit ein).
Für die Claude-App existiert ein eigener Zwilling: mobile-project-instructions.md
(im Skill-Ordner) — bei Skill-Änderungen mitziehen.
Memory / Lernen
Lernt nicht (wie agent-todo). Einzige Wahrheit = die Notion-DBs. Kein distill, keine
memory/rules|logs. Aus memory/state/ nur notion-ids.json.
Umgesetzt / geplant
- ✅ OpenFoodFacts-Lookup + Lebensmittel-Cache — umgesetzt 01.07. (Nährwert-Kaskade oben).
- ✅ WHOOP-API — umgesetzt 05.07. (Modus 4): gemessener Tagesverbrauch + Strain + Gewicht
in die Gewicht-DB, Anzeige neben dem festen Ziel (Ziel bleibt fix, non-advisory), Auto-Sync
im Daily Brief. Whoop deckt damit auch das automatische Gewicht ab (solange Paul in Whoop
wiegt/synct); Samsung Health nicht mehr nötig.
- ✅ Kaskade v3 (Datenquellen-Ausbau) — umgesetzt 12.07.: CH-Seed lokal
(
data/ch-nwdb-v7.csv + ch_lookup.py; bewusst KEIN Vollimport nach Notion — nur
tatsächlich Gegessenes wandert via Promotion in die Referenz), USDA FDC
(fdc_lookup.py), OFF-Write-back (off_writeback.py, nur Etikettendaten),
Referenz-Felder Quelle/Konfidenz/OFF-Eintrag. FDC_API_KEY + OFF-Konten
(.org und .net, gleiche Credentials) liegen seit 12.07. in .env; Write-back
end-to-end auf Staging verifiziert (Produktdaten + Fotos).
- ✅ OFF-Wochencheck (Modus 6) — umgesetzt 15.07.: Befund, dass der reaktive
Write-back-Trigger am Mobile-Logging vorbeilief (0/64 Beiträge) →
off_candidates.py
(Kandidaten-Findung + Skip-Gedächtnis), Quelle-Option Etikett als
Provenienz-Marker (mobil erfassbar), wöchentlicher Nudge im Montags-Brief.
Kadenz bewusst wöchentlich, nicht täglich.
- ✅ Tag ausklammern (Modus 7) — umgesetzt 12.08.: Felder
Tages-Status/Status-Grund
in der Gewicht-DB, Behandlung „raus aus der Aufnahme-Statistik, neutral in der
Energiebilanz" (kcal_in := Whoop kcal ⇒ Saldo 0), Abdeckungs-Ausweis („5 von 7"),
Lücken-Wächter unter 800 kcal, nutrition_report.py status-aware. Entscheide Paul:
markieren statt löschen (Teil-Einträge bleiben) und neutral statt Straf-Aufschlag
(das Korrektiv ist der gemessene Gewichtstrend, nicht ein erfundener Zuschlag).
- Dynamisches kcal-Ziel (Verbrauch „zurückessen") bewusst NICHT umgesetzt — Fix-Ziel +
manuelle Kalibrierung bleibt (Entscheidung 05.07.2026).
Haltung
- Deutsch, knapp, Pauls Voice. Schätzungen transparent (Annahmen nennen). Non-advisory.
- Essen/Gewicht sind Low-Stakes → schreiben, dann bestätigen; kein Bestätigungs-Gate.
- Nichts erfinden: keine kcal-Werte ohne nachvollziehbare Portionsannahme.