"""Score badges, chips and meters — the small visual vocabulary of the app. Everything returns an HTML string rather than calling st.* directly, so the pieces compose inside cards and columns. A recurring rule here: whenever a Nutri-Score grade is shown, its *source* is shown with it. Roughly 45% of the grades in the catalog were computed by NutriWeb rather than published by Open Food Facts, and presenting the two identically would overstate what we know. """ from __future__ import annotations import html from nutriweb.util import num, tag_set GRADE_LABEL = { "a": "A", "b": "B", "c": "C", "d": "D", "e": "E", } NOVA_LABEL = { 1: "Unprocessed", 2: "Culinary ingredient", 3: "Processed", 4: "Ultra-processed", } def esc(value) -> str: return html.escape(str(value)) if value is not None else "" def nutriscore_badge(grade: str | None, source: str | None = None) -> str: """Nutri-Score pill. `source` is 'off' or 'nutriweb'.""" if not grade or str(grade).lower() not in GRADE_LABEL: return 'Nutri-Score n/a' g = str(grade).lower() chip = "" if source == "nutriweb": chip = 'est.' elif source == "off": chip = 'OFF' return ( f'Nutri-Score ' f"{GRADE_LABEL[g]}{chip}" ) def nova_badge(nova) -> str: value = num(nova) if value is None or int(value) not in NOVA_LABEL: return "" n = int(value) tone = {1: "chip-good", 2: "chip-good", 3: "chip-warn", 4: "chip-danger"}[n] return f'{NOVA_LABEL[n]}' def eco_badge(grade) -> str: if not grade or str(grade).lower() not in GRADE_LABEL: return "" g = str(grade).lower() return f'Eco {GRADE_LABEL[g]}' def badge_row(product: dict) -> str: parts = [ nutriscore_badge(product.get("nutriscore_grade"), product.get("nutriscore_source")), eco_badge(product.get("environmental_score_grade")), nova_badge(product.get("nova_group")), ] return '