mitai-jinkendo/backend/bls/recipe_parser.py
Lars 86f15e6957
All checks were successful
Deploy Development / deploy (push) Successful in 1m9s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 22s
feat: FDDB-Listen auflösen und Katalog nach Name suchen
Tagebuchzeilen eigener Rezepte werden über den Listen-Import in Zutaten zerlegt. Zuordnen erfolgt im Namens-Popup statt per BLS-Code.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 15:31:20 +02:00

80 lines
2.6 KiB
Python

"""Parse FDDB lists_*.csv (name;…;produkte) into recipe + ingredients."""
from __future__ import annotations
import csv
import io
import re
from typing import Any
from data_layer.food_mapping import normalize_food_name, parse_quantity_g
ING_RE = re.compile(
r"(?P<qty>\d+(?:[.,]\d+)?)\s*(?P<unit>g|kg|ml|l)\b\s*(?P<name>.+?)"
r"(?=,\s*\d+(?:[.,]\d+)?\s*(?:g|kg|ml|l)\b|$)",
re.IGNORECASE | re.DOTALL,
)
def parse_fddb_produkte(text: str) -> list[dict[str, Any]]:
raw = (text or "").strip().strip('"')
if not raw:
return []
out: list[dict[str, Any]] = []
for i, m in enumerate(ING_RE.finditer(raw)):
qty = float(m.group("qty").replace(",", "."))
unit = m.group("unit").lower()
name = re.sub(r"\s+", " ", m.group("name")).strip(" ,;")
if not name:
continue
grams = qty
if unit == "kg":
grams = qty * 1000.0
elif unit == "l":
grams = qty * 1000.0
elif unit == "ml":
grams = qty
out.append({
"source_name_raw": name,
"source_name_normalized": normalize_food_name(name),
"quantity_raw": f"{m.group('qty').replace(',', '.')} {unit}",
"quantity_g": round(grams, 3),
"sort_order": i,
})
return out
def parse_fddb_lists_csv(text: str) -> list[dict[str, Any]]:
if text.startswith("\ufeff"):
text = text[1:]
reader = csv.DictReader(io.StringIO(text), delimiter=";")
recipes = []
for row in reader:
name = (row.get("name") or "").strip().strip('"')
if not name:
continue
try:
portions = float(str(row.get("anzahl_portionen") or "1").replace(",", "."))
except ValueError:
portions = 1.0
if portions <= 0:
portions = 1.0
ingredients = parse_fddb_produkte(row.get("produkte") or "")
if not ingredients:
leftover = (row.get("produkte") or "").strip().strip('"')
if leftover:
ingredients = [{
"source_name_raw": leftover,
"source_name_normalized": normalize_food_name(leftover),
"quantity_raw": None,
"quantity_g": parse_quantity_g(leftover),
"sort_order": 0,
}]
recipes.append({
"name_raw": name,
"name_normalized": normalize_food_name(name),
"portions": portions,
"description": (row.get("beschreibung") or "").strip() or None,
"ingredients": ingredients,
})
return recipes