"""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'| {badges.esc(label)} | '
f'— | |
'
)
continue
pct = max(2.0, min(100.0, value / high * 100.0))
over = " over" if value > high else ""
out.append(
f'| {badges.esc(label)} | '
f'{value:.1f} {unit} | '
f'
|
'
)
out.append("
")
return "".join(out)