mitai-jinkendo/.claude/docs/technical/BLS_FOOD_REFERENCE.md
Lars 95453d548a
All checks were successful
Deploy Development / deploy (push) Successful in 1m13s
Build Test / pytest-backend (push) Successful in 6s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 22s
fix: Listen in der Tagesuebersicht und Katalog-Makros uebernehmen
Tagebuchzeilen mit Liste gelten als zugeordnet. Berechnete Zutaten-Makros lassen sich am Tag in nutrition_log schreiben.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 13:31:30 +02:00

53 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BLS Food Reference – technische Spec
**Stand:** 2026-09-14 · Migration **062**–**069**
## Tabellen
- `food_attributes` — dynamischer Stoff-/Merkmalskatalog (`attr_key`, `data_type`, `origin`)
- `food_catalog` — Lebensmittel (`bls_code` UNIQUE bei official_bls; manuell ohne BLS-Code)
- `food_attribute_values` — typisiertes EAV
- `food_name_mappings` — FDDB-Name → `food_id` (User / global)
- `nutrition_items` — Tagebuchzeilen inkl. `logged_at`
- `nutrition_daily_nutrients` — Tages-Rollup numerischer Attribute
- `nutrition_day_marks` — `fasting` | `incomplete`
- `food_recipes` / `food_recipe_ingredients` — Mitai-Listen/Kombinationen (Rohmischung, kein Kochschwund); `nutrition_items.recipe_id`. Kein Tabellen-Rename. Gekochte Gerichte später Tandoor + `cooked_yield_g`, nicht hier.
## Layer 1
- `data_layer/food_mapping.py` — Normalisierung, Lookup, Learn, Apply, Delete
- `data_layer/nutrition_items.py` — Ingest, drei Makro-Summen, Policy, `resolve_*_attributes`
- `data_layer/food_recipes.py` — Listen-Upsert, Link auf Tagebuchzeilen, Listen-Makros (Skala: gegessen_g / Summe Zutaten, sonst 1/Portionen). Diese Skala gilt nur für Roh-Kombinationen, nicht für gekochte Gerichte.
- `bls/recipe_parser.py` — `produkte`-Feld: Split nur vor nächstem `\d+ (g|kg|ml|l)` (Kommas im Namen bleiben)
## Import
BLS: Admin-Upload Components + Daten-XLSX (`backend/bls/parser.py`). Upsert über `bls_code` / `attr_key`, nie Delete+Insert offizieller Zeilen.
FDDB: Items persistieren; `nutrition_log` nur bei leerem Tag oder laut Policy / Bestätigung.
## Router
- `/api/bls/*` — Suche, Attribute-Suche, eigene Foods (`attributes` + `serving_g`), eigene Mappings; `POST /bls/attributes` legt Extension-Stoff an (Name + Einheit), `GET /bls/attributes?keys=EPA,DHA`
- `GET /bls/foods/mine` — eigene `manual_user`-Einträge; `GET /bls/foods/shared` — zentrale `manual_admin`; `GET /bls/foods/{id}` — Editor-Payload inkl. `visibility` und `editable` (Owner persönlich; Admin auch zentral); `PUT /bls/foods/manual/{id}` — nur Owner. Zentrale Updates: `PUT /admin/bls/foods/manual/{id}` (Admin). Suche `?scope=global` blendet persönliche Foods aus (Admin-Mappings).
- Sichtbarkeit: `data_layer/food_visibility.py`. Lookup-Mapping: User-Zeile nur wenn Food sichtbar; globale Mapping-Zeile nur `owner_profile_id IS NULL`. `upsert_food_mapping` erzwingt dasselbe. Vorschläge sortieren persönlich → zentral → BLS.
- `GET /admin/bls/foods?kind=manual` — `manual_user`/`manual_admin`. `POST /admin/bls/foods/manual` — zentrale Foods. `PUT /admin/bls/foods/manual/{id}` — zentral **und** persönlich (`attributes` + `serving_g`). `GET /admin/bls/foods/{id}?editor=true` — Editor-Payload. Migration **069**: Owner-Check (persönlich hat Owner, BLS/Admin nicht).
- Migration **065** — Extension-Keys EPA/DHA/DPA/ALA/OMEGA3/OMEGA6 falls BLS sie nicht unter diesem Key hat
- `/api/admin/bls/*` — Import, Katalog, Attribute. `GET /admin/bls/attributes` liefert `group_key`/`group_label` (BLS-Gruppen, sortiert). `PUT /admin/bls/attributes/{id}` ändert Name/Einheit/Gruppe (nicht `attr_key`). `POST /admin/bls/attributes/fill-labels` füllt leere Namen und fehlende Gruppen aus BLS-4.0-Glossar (`bls/attribute_labels.py`). Lebensmittel-Import schreibt fehlende Namen aus Spaltenköpfen. Makro-Rollup: `catalog_macros_for_item` liest nur ENERCC/PROT625/FAT/CHO → `nutrition_log`; `rebuild_daily_nutrients` summiert alle `num_per_100g` nach `nutrition_daily_nutrients`.
- `/api/admin/food-mappings` — Admin-CRUD
- `/api/nutrition/*` — Items, Unmapped, Bulk-Map, Marken, Konflikt-Resolve
- `GET/POST /api/nutrition/recipes`, `PUT/DELETE /api/nutrition/recipes/{id}`, `POST …/import-fddb-lists`, `POST …/{id}/apply`
- `PUT /api/admin/food-mappings/{id}` — Ziel-Lebensmittel einer bestehenden Zuordnung wechseln
- Unmapped = Tagebuchzeilen ohne `food_id`/`recipe_id` **plus** Listenzutaten ohne Mapping
- Frontend: Inline-Vorschläge auf Zuordnen (`food_suggest.py`: Collapse-Key ohne Leerzeichen, Index 5 Min. Cache, Fett-%-Zahlen 9,5/10), `FoodSearchModal` nur noch Zusatzsuche (Abort + Debounce). Eigene Foods: Tab **Lebensmittel** (`MyFoodsPanel` + `FoodEditDialog`). Listen-UI: `FoodRecipeDialog` (Suche gelernt + Katalog; Katalogpick upsertet Mapping). Offene Zutaten: `kind=recipe_ingredient` (API-Wert unverändert, UI: Listenzutat).
- Tandoor (Modul `tandoor`): Migration **067** `profile_tandoor_settings`; `GET/PUT /api/tandoor/settings` (Token nur `token_set` / `token_hint`); `POST /api/tandoor/test` (10/min, kein Redirect-Follow). Client: `data_layer/tandoor_client.py`. Import und `cooked_yield_g` später.
- Migration **066** — `food_recipe_ingredients.quantity_amount` + `source_unit`; `quantity_g` = Umrechnung (`resolve_quantity` in `food_mapping.py`). `GET /bls/units` = Einheitenkatalog (EL/TL/Prise/Stück, default_g).
- `GET /nutrition/unmapped?since_days=28` — nur Namen mit `last_date` im Fenster; `count_only` liefert `{count, total, since_days}`; `meta=true` liefert `{items, count, total, since_days}`. Listenzutaten-Datum: letzte `recipe_id`-Nutzung oder `food_recipes.updated_at`/`created_at` — kein Join über den Listennamen auf alle Tagebuchzeilen. Migration **068**: Partial-Index unmapped-open + `(profile_id, recipe_id, date)`.
- `GET /nutrition/recipes?brief=true` — Listen ohne Zutaten, nur `ingredient_count`. `GET /nutrition/recipes/{id}` lädt eine Liste. FDDB-Listenimport antwortet nach Upsert; `rebuild_daily_nutrients` im Hintergrund (`rebuild_pending`).
- Listen-Verknüpfung (`apply` / `link_recipes_to_items`) matcht über `names_match_list` (Mengen-Prefix in gespeichertem `source_name_normalized` zählt). Unmapped-Zeilen bekommen `matching_recipe_id` + `matching_recipe_name`. Frontend: Tab Ernährung → Listen.
- `GET /nutrition/ingredient-names` — Distinct-Zutatennamen aller Listen inkl. Mapping-Status. Listendialog mappt manuell (`upsertMyFoodMapping`) an der Zeile.
- Mapping-Schreiben und Nährwert-Rebuild sind getrennte Transaktionen; Rebuild läuft nach der API-Antwort im Hintergrund (UI bleibt bedienbar)
- `food_name_mappings.grams_per_unit` / `source_unit` (Migration **064**)
- `GET /nutrition/items` liefert `recipe_name_de` + `recipe_complete`. `GET /nutrition/days/{date}/macros` vergleicht bestehende vs. Katalog-Tagesmakros; `POST …/apply-catalog-macros` schreibt die berechnete Summe nach `nutrition_log`. Listen-Save verknüpft passende Tagebuchzeilen (`items_linked`).
- `GET/POST /api/nutrition/food-knowledge` — portable JSON (`mitai-food-knowledge` v1): manuelle Foods, Mappings (über `bls_code` / Name, keine UUIDs), Listen. Import löst Katalog auf dem Zielsystem auf (BLS muss dort importiert sein).