mitai-jinkendo/backend/bls/import_service.py
Lars 919c77fcf8
Some checks failed
Deploy Development / deploy (push) Failing after 52s
Build Test / pytest-backend (push) Successful in 3s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Failing after 9s
feat: BLS-Stammdaten, FDDB-Mapping und Item-Tagebuch (#106)
Katalog, lernendes Mapping ohne KI, optionale Items und Import-Policy.
Playwright-Smoke und Issue-Audit um Ernährung/Zuordnen/API ergänzt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 14:35:57 +02:00

127 lines
5.5 KiB
Python

"""Apply parsed BLS 4.0 data: upsert attributes and foods, never delete official rows."""
from __future__ import annotations
from typing import Any
def upsert_attributes(cur, attributes: list[dict[str, Any]]) -> dict[str, int]:
inserted = updated = 0
for a in attributes:
cur.execute(
"""
INSERT INTO food_attributes
(attr_key, name_de, name_en, unit, category, data_type, origin, sort_order, updated_at)
VALUES (%s, %s, %s, %s, %s, %s, 'official_bls', %s, NOW())
ON CONFLICT (attr_key) DO UPDATE SET
name_de = EXCLUDED.name_de,
name_en = COALESCE(EXCLUDED.name_en, food_attributes.name_en),
unit = COALESCE(EXCLUDED.unit, food_attributes.unit),
category = COALESCE(EXCLUDED.category, food_attributes.category),
sort_order = EXCLUDED.sort_order,
updated_at = NOW()
WHERE food_attributes.origin = 'official_bls'
RETURNING (xmax = 0) AS inserted
""",
(
a["attr_key"], a["name_de"], a.get("name_en"), a.get("unit"),
a.get("category"), a.get("data_type") or "num_per_100g",
a.get("sort_order") or 0,
),
)
row = cur.fetchone()
if row and row.get("inserted"):
inserted += 1
else:
updated += 1
return {"inserted": inserted, "updated": updated, "total": len(attributes)}
def upsert_foods(cur, foods: list[dict[str, Any]], bls_version: str = "4.0") -> dict[str, int]:
cur.execute("SELECT attr_key, id FROM food_attributes")
attr_ids = {r["attr_key"]: r["id"] for r in cur.fetchall()}
inserted = updated = values_written = 0
for f in foods:
code = f["bls_code"]
cur.execute("SELECT id FROM food_catalog WHERE bls_code = %s", (code,))
existing = cur.fetchone()
if existing:
food_id = existing["id"]
cur.execute(
"""
UPDATE food_catalog
SET name_de=%s, name_en=%s, food_group=%s, bls_version=%s,
catalog_kind='official_bls', source='bls_4.0', is_active=true, updated_at=NOW()
WHERE id=%s AND catalog_kind='official_bls'
""",
(f["name_de"], f.get("name_en"), f.get("food_group"), bls_version, food_id),
)
updated += 1
else:
cur.execute(
"""
INSERT INTO food_catalog
(bls_code, name_de, name_en, food_group, catalog_kind, bls_version, source)
VALUES (%s, %s, %s, %s, 'official_bls', %s, 'bls_4.0')
RETURNING id
""",
(code, f["name_de"], f.get("name_en"), f.get("food_group"), bls_version),
)
food_id = cur.fetchone()["id"]
inserted += 1
for val in f.get("values") or []:
aid = attr_ids.get(val["attr_key"])
if not aid:
continue
if val.get("is_trace"):
cur.execute(
"""
INSERT INTO food_attribute_values
(food_id, attribute_id, value_num, is_trace, origin_code, reference_text, updated_at)
VALUES (%s, %s, NULL, true, %s, %s, NOW())
ON CONFLICT (food_id, attribute_id) DO UPDATE SET
value_num = NULL, is_trace = true,
origin_code = EXCLUDED.origin_code,
reference_text = EXCLUDED.reference_text,
updated_at = NOW()
""",
(food_id, aid, val.get("origin_code"), val.get("reference_text")),
)
elif val.get("value_num") is None:
cur.execute(
"""
INSERT INTO food_attribute_values
(food_id, attribute_id, value_num, is_trace, origin_code, reference_text, updated_at)
VALUES (%s, %s, NULL, false, %s, %s, NOW())
ON CONFLICT (food_id, attribute_id) DO UPDATE SET
value_num = NULL, is_trace = false,
origin_code = EXCLUDED.origin_code,
reference_text = EXCLUDED.reference_text,
updated_at = NOW()
""",
(food_id, aid, val.get("origin_code"), val.get("reference_text")),
)
else:
cur.execute(
"""
INSERT INTO food_attribute_values
(food_id, attribute_id, value_num, is_trace, origin_code, reference_text, updated_at)
VALUES (%s, %s, %s, false, %s, %s, NOW())
ON CONFLICT (food_id, attribute_id) DO UPDATE SET
value_num = EXCLUDED.value_num, is_trace = false,
origin_code = EXCLUDED.origin_code,
reference_text = EXCLUDED.reference_text,
updated_at = NOW()
""",
(
food_id, aid, val["value_num"],
val.get("origin_code"), val.get("reference_text"),
),
)
values_written += 1
return {
"foods_inserted": inserted,
"foods_updated": updated,
"values_written": values_written,
"foods_total": len(foods),
}