Kryptische Schluessel bleiben Identitaet; Namen/Einheit sind aenderbar. Nur ENERCC/PROT625/FAT/CHO schreiben die Makro-Haupttabelle. Co-authored-by: Cursor <cursoragent@cursor.com>
259 lines
11 KiB
Python
259 lines
11 KiB
Python
"""Official BLS 4.0 component labels (MRI documentation) and placeholder repair.
|
||
|
||
``attr_key`` stays the identity. These names only fill empty or key-equal ``name_de``.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
# Keys that feed nutrition_log kcal / protein_g / fat_g / carbs_g
|
||
MACRO_KEYS = ("ENERCC", "PROT625", "FAT", "CHO")
|
||
|
||
# Code → (name_de, unit, category) from BLS 4.0 Referenzhandbuch (Jan 2026)
|
||
_OFFICIAL: dict[str, tuple[str, str, str]] = {
|
||
"ENERCJ": ("Energie (Kilojoule)", "kJ", "energy"),
|
||
"ENERCC": ("Energie (Kilokalorien)", "kcal", "energy"),
|
||
"WATER": ("Wasser", "g", "macro"),
|
||
"PROT625": ("Protein (N×6,25)", "g", "macro"),
|
||
"FAT": ("Fett", "g", "macro"),
|
||
"CHO": ("Kohlenhydrate, verfügbar", "g", "macro"),
|
||
"FIBT": ("Ballaststoffe, gesamt", "g", "fiber"),
|
||
"ALC": ("Alkohol (Ethanol)", "g", "macro"),
|
||
"OA": ("Organische Säuren, gesamt", "g", "organic_acids"),
|
||
"ASH": ("Rohasche", "g", "macro"),
|
||
"POLYL": ("Zuckeralkohole, gesamt", "g", "polyols"),
|
||
"GLUS": ("Glucose", "g", "carbs"),
|
||
"FRUS": ("Fructose", "g", "carbs"),
|
||
"GALS": ("Galactose", "g", "carbs"),
|
||
"MNSAC": ("Monosaccharide, gesamt", "g", "carbs"),
|
||
"SUCS": ("Saccharose", "g", "carbs"),
|
||
"MALS": ("Maltose", "g", "carbs"),
|
||
"LACS": ("Lactose", "g", "carbs"),
|
||
"DISAC": ("Disaccharide, gesamt", "g", "carbs"),
|
||
"SUGAR": ("Zucker, gesamt", "g", "carbs"),
|
||
"OLSAC": ("Oligosaccharide, verfügbar", "g", "carbs"),
|
||
"STARCH": ("Stärke", "g", "carbs"),
|
||
"FIBLMW": ("Ballaststoffe, niedermolekular", "g", "fiber"),
|
||
"FIBHMW": ("Ballaststoffe, hochmolekular", "g", "fiber"),
|
||
"FIBINS": ("Ballaststoffe, wasserunlöslich", "g", "fiber"),
|
||
"FIBSOL": ("Ballaststoffe, wasserlöslich", "g", "fiber"),
|
||
"FIBHMWS": ("Ballaststoffe, hochmolekular, wasserlöslich", "g", "fiber"),
|
||
"FIBHMWI": ("Ballaststoffe, hochmolekular, wasserunlöslich", "g", "fiber"),
|
||
"FASAT": ("Fettsäuren, gesättigt, gesamt", "g", "fatty_acids"),
|
||
"F4:0": ("Buttersäure (C4:0)", "g", "fatty_acids"),
|
||
"F6:0": ("Capronsäure (C6:0)", "g", "fatty_acids"),
|
||
"F8:0": ("Caprylsäure (C8:0)", "g", "fatty_acids"),
|
||
"F10:0": ("Caprinsäure (C10:0)", "g", "fatty_acids"),
|
||
"F12:0": ("Laurinsäure (C12:0)", "g", "fatty_acids"),
|
||
"F14:0": ("Myristinsäure (C14:0)", "g", "fatty_acids"),
|
||
"F15:0": ("Pentadecylsäure (C15:0)", "g", "fatty_acids"),
|
||
"F16:0": ("Palmitinsäure (C16:0)", "g", "fatty_acids"),
|
||
"F17:0": ("Margarinsäure (C17:0)", "g", "fatty_acids"),
|
||
"F18:0": ("Stearinsäure (C18:0)", "g", "fatty_acids"),
|
||
"F20:0": ("Arachinsäure (C20:0)", "g", "fatty_acids"),
|
||
"F22:0": ("Behensäure (C22:0)", "g", "fatty_acids"),
|
||
"F24:0": ("Lignocerinsäure (C24:0)", "g", "fatty_acids"),
|
||
"FAMS": ("Fettsäuren, einfach ungesättigt, gesamt", "g", "fatty_acids"),
|
||
"F14:1CN5": ("Myristoleinsäure (C14:1 n-5 cis)", "g", "fatty_acids"),
|
||
"F16:1CN7": ("Palmitoleinsäure (C16:1 n-7 cis)", "g", "fatty_acids"),
|
||
"F18:1CN7": ("Vaccensäure (C18:1 n-7 cis)", "g", "fatty_acids"),
|
||
"F18:1CN9": ("Ölsäure (C18:1 n-9 cis)", "g", "fatty_acids"),
|
||
"F20:1CN9": ("Gondosäure (C20:1 n-9 cis)", "g", "fatty_acids"),
|
||
"F22:1CN9": ("Erucasäure (C22:1 n-9 cis)", "g", "fatty_acids"),
|
||
"FAPU": ("Fettsäuren, mehrfach ungesättigt, gesamt", "g", "fatty_acids"),
|
||
"FAPUN3": ("Omega-3-Fettsäuren, gesamt", "g", "fatty_acids"),
|
||
"FAPUN6": ("Omega-6-Fettsäuren, gesamt", "g", "fatty_acids"),
|
||
"F18:2CN6": ("Linolsäure (C18:2 n-6 cis)", "g", "fatty_acids"),
|
||
"F18:2C9T11": ("Konjugierte Linolsäure (C18:2 cis-9, trans-11)", "g", "fatty_acids"),
|
||
"F18:3CN3": ("Alpha-Linolensäure (C18:3 n-3)", "g", "fatty_acids"),
|
||
"F18:3CN6": ("Gamma-Linolensäure (C18:3 n-6)", "g", "fatty_acids"),
|
||
"F18:4CN3": ("Stearidonsäure (C18:4 n-3)", "g", "fatty_acids"),
|
||
"F20:2CN6": ("Eicosadiensäure (C20:2 n-6)", "g", "fatty_acids"),
|
||
"F20:3CN6": ("Dihomo-Gamma-Linolensäure (C20:3 n-6)", "g", "fatty_acids"),
|
||
"F20:4CN6": ("Arachidonsäure (C20:4 n-6)", "g", "fatty_acids"),
|
||
"F20:5CN3": ("Eicosapentaensäure EPA (C20:5 n-3)", "g", "fatty_acids"),
|
||
"F22:5CN3": ("Docosapentaensäure DPA (C22:5 n-3)", "g", "fatty_acids"),
|
||
"F22:6CN3": ("Docosahexaensäure DHA (C22:6 n-3)", "g", "fatty_acids"),
|
||
"FAX": ("Fettsäuren, sonstige", "g", "fatty_acids"),
|
||
"AAE9": ("Aminosäuren, unentbehrlich, gesamt", "g", "amino_acids"),
|
||
"ILE": ("Isoleucin", "g", "amino_acids"),
|
||
"LEU": ("Leucin", "g", "amino_acids"),
|
||
"LYS": ("Lysin", "g", "amino_acids"),
|
||
"MET": ("Methionin", "g", "amino_acids"),
|
||
"PHE": ("Phenylalanin", "g", "amino_acids"),
|
||
"THR": ("Threonin", "g", "amino_acids"),
|
||
"TRP": ("Tryptophan", "g", "amino_acids"),
|
||
"VAL": ("Valin", "g", "amino_acids"),
|
||
"HIS": ("Histidin", "g", "amino_acids"),
|
||
"ALA": ("Alanin", "g", "amino_acids"),
|
||
"ARG": ("Arginin", "g", "amino_acids"),
|
||
"ASP": ("Asparaginsäure inkl. Asparagin", "g", "amino_acids"),
|
||
"CYS": ("Cystein", "g", "amino_acids"),
|
||
"GLU": ("Glutaminsäure inkl. Glutamin", "g", "amino_acids"),
|
||
"GLY": ("Glycin", "g", "amino_acids"),
|
||
"PRO": ("Prolin", "g", "amino_acids"),
|
||
"SER": ("Serin", "g", "amino_acids"),
|
||
"TYR": ("Tyrosin", "g", "amino_acids"),
|
||
"VITA": ("Vitamin A, Retinol-Äquivalent (RE)", "µg", "vitamins"),
|
||
"VITAA": ("Vitamin A, Retinol-Aktivitätsäquivalent (RAE)", "µg", "vitamins"),
|
||
"RETOL": ("Retinol", "µg", "vitamins"),
|
||
"CARTB": ("Beta-Carotin", "µg", "vitamins"),
|
||
"CAROTPAXB": ("Carotinoide außer Beta-Carotin", "µg", "vitamins"),
|
||
"VITD": ("Vitamin D", "µg", "vitamins"),
|
||
"ERGCAL": ("Vitamin D2 (Ergocalciferol)", "µg", "vitamins"),
|
||
"CHOCAL": ("Vitamin D3 (Cholecalciferol)", "µg", "vitamins"),
|
||
"VITE": ("Vitamin E (Alpha-Tocopherol)", "mg", "vitamins"),
|
||
"TOCPHA": ("Alpha-Tocopherol", "mg", "vitamins"),
|
||
"TOCPHB": ("Beta-Tocopherol", "mg", "vitamins"),
|
||
"TOCPHG": ("Gamma-Tocopherol", "mg", "vitamins"),
|
||
"TOCPHD": ("Delta-Tocopherol", "mg", "vitamins"),
|
||
"TOCTRA": ("Alpha-Tocotrienol", "mg", "vitamins"),
|
||
"VITK": ("Vitamin K", "µg", "vitamins"),
|
||
"VITK1": ("Vitamin K1 (Phyllochinon)", "µg", "vitamins"),
|
||
"VITK2": ("Vitamin K2 (Menachinone)", "µg", "vitamins"),
|
||
"THIA": ("Vitamin B1 (Thiamin)", "mg", "vitamins"),
|
||
"RIBF": ("Vitamin B2 (Riboflavin)", "mg", "vitamins"),
|
||
"NIA": ("Niacin", "mg", "vitamins"),
|
||
"NIAEQ": ("Niacin-Äquivalent", "mg", "vitamins"),
|
||
"PANTAC": ("Pantothensäure", "mg", "vitamins"),
|
||
"VITB6": ("Vitamin B6", "µg", "vitamins"),
|
||
"BIOT": ("Biotin", "µg", "vitamins"),
|
||
"FOL": ("Folat-Äquivalent", "µg", "vitamins"),
|
||
"FOLFD": ("Folat", "µg", "vitamins"),
|
||
"FOLAC": ("Folsäure, synthetisch", "µg", "vitamins"),
|
||
"VITB12": ("Vitamin B12 (Cobalamine)", "µg", "vitamins"),
|
||
"VITC": ("Vitamin C", "mg", "vitamins"),
|
||
"NACL": ("Salz (Natriumchlorid)", "g", "minerals"),
|
||
"NA": ("Natrium", "mg", "minerals"),
|
||
"CLD": ("Chlorid", "mg", "minerals"),
|
||
"K": ("Kalium", "mg", "minerals"),
|
||
"CA": ("Calcium", "mg", "minerals"),
|
||
"MG": ("Magnesium", "mg", "minerals"),
|
||
"P": ("Phosphor", "mg", "minerals"),
|
||
"S": ("Schwefel", "mg", "minerals"),
|
||
"FE": ("Eisen", "mg", "minerals"),
|
||
"ZN": ("Zink", "mg", "minerals"),
|
||
"ID": ("Iodid", "µg", "minerals"),
|
||
"CU": ("Kupfer", "µg", "minerals"),
|
||
"MN": ("Mangan", "µg", "minerals"),
|
||
"FD": ("Fluorid", "µg", "minerals"),
|
||
"CR": ("Chrom", "µg", "minerals"),
|
||
"MO": ("Molybdän", "µg", "minerals"),
|
||
"ACEAC": ("Essigsäure", "g", "organic_acids"),
|
||
"CITAC": ("Zitronensäure", "g", "organic_acids"),
|
||
"LACAC": ("Milchsäure", "g", "organic_acids"),
|
||
"MALAC": ("Äpfelsäure", "g", "organic_acids"),
|
||
"TARAC": ("Weinsäure", "g", "organic_acids"),
|
||
"MANTL": ("Mannit", "g", "polyols"),
|
||
"SORTL": ("Sorbit", "g", "polyols"),
|
||
"XYLTL": ("Xylit", "g", "polyols"),
|
||
"CHORL": ("Cholesterin", "mg", "other"),
|
||
"NT": ("Stickstoff, gesamt", "g", "other"),
|
||
"EPA": ("Eicosapentaensäure (EPA)", "g", "fatty_acids"),
|
||
"DHA": ("Docosahexaensäure (DHA)", "g", "fatty_acids"),
|
||
"DPA": ("Docosapentaensäure (DPA)", "g", "fatty_acids"),
|
||
"ALA": ("Alpha-Linolensäure (ALA)", "g", "fatty_acids"),
|
||
"OMEGA3": ("Omega-3-Fettsäuren gesamt", "g", "fatty_acids"),
|
||
"OMEGA6": ("Omega-6-Fettsäuren gesamt", "g", "fatty_acids"),
|
||
}
|
||
|
||
|
||
def lookup_key(raw: str | None) -> str:
|
||
return str(raw or "").strip().upper()
|
||
|
||
|
||
def official_label(attr_key: str | None) -> dict[str, str] | None:
|
||
key = lookup_key(attr_key)
|
||
hit = _OFFICIAL.get(key)
|
||
if not hit:
|
||
hit = _OFFICIAL.get(key.replace("_", ":"))
|
||
if not hit:
|
||
return None
|
||
name_de, unit, category = hit
|
||
return {"name_de": name_de, "unit": unit, "category": category}
|
||
|
||
|
||
def label_is_placeholder(name_de: str | None, attr_key: str | None) -> bool:
|
||
name = (name_de or "").strip()
|
||
key = lookup_key(attr_key)
|
||
return (not name) or name.upper() == key
|
||
|
||
|
||
def apply_official_name(attr_key: str | None, name_de: str | None) -> str:
|
||
if not label_is_placeholder(name_de, attr_key):
|
||
return (name_de or "").strip()
|
||
lab = official_label(attr_key)
|
||
if lab:
|
||
return lab["name_de"]
|
||
return (name_de or "").strip() or lookup_key(attr_key)
|
||
|
||
|
||
def enrich_attributes_in_memory(attributes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||
for a in attributes:
|
||
key = a.get("attr_key")
|
||
lab = official_label(key)
|
||
if label_is_placeholder(a.get("name_de"), key) and lab:
|
||
a["name_de"] = lab["name_de"]
|
||
a["unit"] = a.get("unit") or lab["unit"]
|
||
a["category"] = a.get("category") or lab["category"]
|
||
return attributes
|
||
|
||
|
||
def fill_placeholder_labels(cur) -> dict[str, int]:
|
||
cur.execute(
|
||
"""
|
||
SELECT id, attr_key, name_de, unit, category
|
||
FROM food_attributes
|
||
WHERE is_active = true
|
||
"""
|
||
)
|
||
updated = 0
|
||
for row in cur.fetchall():
|
||
rec = dict(row)
|
||
lab = official_label(rec.get("attr_key"))
|
||
if not lab or not label_is_placeholder(rec.get("name_de"), rec.get("attr_key")):
|
||
continue
|
||
cur.execute(
|
||
"""
|
||
UPDATE food_attributes
|
||
SET name_de = %s,
|
||
unit = COALESCE(NULLIF(BTRIM(COALESCE(unit, '')), ''), %s),
|
||
category = COALESCE(NULLIF(BTRIM(COALESCE(category, '')), ''), %s),
|
||
updated_at = NOW()
|
||
WHERE id = %s
|
||
""",
|
||
(lab["name_de"], lab["unit"], lab["category"], rec["id"]),
|
||
)
|
||
updated += 1
|
||
return {"updated": updated}
|
||
|
||
|
||
def enrich_labels_from_food_headers(cur, headers: list[dict[str, Any]] | None) -> int:
|
||
updated = 0
|
||
for h in headers or []:
|
||
key = h.get("attr_key")
|
||
name = (h.get("name_de") or "").strip()
|
||
unit = (h.get("unit") or "").strip() or None
|
||
if not key or not name or label_is_placeholder(name, key):
|
||
lab = official_label(key)
|
||
if not lab:
|
||
continue
|
||
name = lab["name_de"]
|
||
unit = unit or lab["unit"]
|
||
cur.execute(
|
||
"""
|
||
UPDATE food_attributes
|
||
SET name_de = %s,
|
||
unit = COALESCE(%s, unit),
|
||
updated_at = NOW()
|
||
WHERE attr_key = %s
|
||
AND (
|
||
name_de IS NULL OR BTRIM(name_de) = '' OR name_de = attr_key
|
||
)
|
||
""",
|
||
(name, unit, key),
|
||
)
|
||
if cur.rowcount:
|
||
updated += cur.rowcount
|
||
return updated
|