"""Product cards and the verdict panel.""" from __future__ import annotations import streamlit as st from components import badges from nutriweb.reco.engine import Recommendation from nutriweb.reco.filters import Verdict def product_card(product: dict, *, key: str, on_open, cta: str = "View details") -> None: """A search-result card with an action button beneath it.""" st.markdown( f"""
{badges.thumb(product)}

{badges.title_of(product)}

{badges.brand_of(product) or " "}
{badges.badge_row(product)} {badges.health_meter(product.get("health_score"))}
""", unsafe_allow_html=True, ) if st.button(cta, key=key, width='stretch'): on_open(product["code"]) def recommendation_card(rec: Recommendation, *, key: str, on_open) -> None: """A recommendation card. Always states why this specific swap was chosen.""" product = rec.product warning = "" if rec.warnings: warning = ( '
⚠︎ ' + badges.esc("; ".join(rec.warnings)) + "
" ) st.markdown( f"""
{badges.thumb(product)}

{badges.title_of(product)}

{badges.brand_of(product) or " "}
+{rec.health_gain:.0f} health {badges.badge_row(product)}
{badges.esc(rec.why)}
{warning}
""", unsafe_allow_html=True, ) if st.button("View details", key=key, width='stretch'): on_open(product["code"]) def verdict_panel(verdict: Verdict, profile_is_empty: bool = False) -> None: """The "is this for you" panel. Driven by the same `filters.evaluate` the engine uses, so what the user is told here always matches what the recommender actually did. """ if profile_is_empty: st.markdown( '
No profile set
' "
Add your allergens and dietary preferences to see whether this " "product suits you.
", unsafe_allow_html=True, ) return tone = "v-block" if verdict.blockers else ("v-warn" if verdict.warnings else "v-ok") icon = "✕" if verdict.blockers else ("!" if verdict.warnings else "✓") items = "".join( f"
  • {badges.esc(m)}
  • " for m in (*verdict.blockers, *verdict.warnings) ) body = f"" if items else "
    Nothing in your profile conflicts with this product.
    " st.markdown( f'
    {icon} {badges.esc(verdict.summary)}
    {body}
    ', unsafe_allow_html=True, ) def nutrient_table(product: dict, reference: dict | None = None) -> str: """Per-100g nutrient table with bars scaled to a reference intake. Bars are relative to a rough per-100g share of an adult reference intake, so they answer "is this a lot?" rather than just restating the number. """ rows = [ ("Energy", "energy_kcal_100g", "kcal", 200.0), ("Fat", "fat_100g", "g", 17.5), (" of which saturates", "saturated_fat_100g", "g", 5.0), ("Carbohydrates", "carbohydrates_100g", "g", 45.0), (" of which sugars", "sugars_100g", "g", 22.5), ("Fibre", "fiber_100g", "g", 7.5), ("Protein", "proteins_100g", "g", 25.0), ("Salt", "salt_derived", "g", 1.5), ] from nutriweb.util import num out = [''] for label, col, unit, high in rows: value = num(product.get(col)) if value is None: out.append( f'' f'' ) continue pct = max(2.0, min(100.0, value / high * 100.0)) over = " over" if value > high else "" out.append( f'' f'' f'' ) out.append("
    {badges.esc(label)}
    {badges.esc(label)}{value:.1f} {unit}
    ") return "".join(out)