"""Product detail โ scores, nutrients, and whether it suits this person."""
from __future__ import annotations
import streamlit as st
from components import badges, cards
from nutriweb.data import catalog
from nutriweb.reco import filters
from nutriweb.scoring import additives as additives_mod
from nutriweb.util import num, tag_set
from views import state
code = state.require_selection()
if not code:
st.stop()
@st.cache_data(show_spinner=False, ttl=600)
def _load(code: str) -> dict | None:
return catalog.get_product(code)
product = _load(code)
if product is None:
st.error(f"No product found for barcode {code}.")
st.stop()
profile = state.profile()
verdict = filters.evaluate(product, profile)
left, right = st.columns([1, 1.55], gap="large")
with left:
st.markdown(
f'
{badges.thumb(product)}'
f"{badges.badge_row(product)}"
f'{badges.health_meter(product.get("health_score"), product.get("health_confidence"))}'
"
",
unsafe_allow_html=True,
)
st.write("")
if st.button("๐ Find healthier swaps", type="primary", width='stretch'):
state.open_recommendations(code)
if st.button("โ Add to compare", width='stretch'):
outcome = state.add_to_compare(code)
st.toast(
{"added": "Added to compare.",
"already": "Already in the compare list.",
"full": "Compare holds three products; remove one first."}[outcome]
)
with right:
st.markdown(f"## {badges.title_of(product)}")
brand = badges.brand_of(product)
subtitle = " ยท ".join(
x for x in (brand, product.get("quantity") if isinstance(product.get("quantity"), str) else None) if x
)
if subtitle:
st.markdown(f'{badges.esc(subtitle)}
', unsafe_allow_html=True)
st.caption(f"Barcode {product['code']}")
cards.verdict_panel(verdict, profile_is_empty=state.profile_is_empty())
# Be explicit when the grade is ours rather than Open Food Facts'.
if product.get("nutriscore_source") == "nutriweb":
confidence = product.get("health_confidence")
note = (
" Its category is unknown, so the general-food thresholds were assumed โ"
" treat this grade as indicative."
if confidence == "low" else ""
)
st.info(
"Open Food Facts has no Nutri-Score for this product. NutriWeb computed "
f"**{str(product.get('nutriscore_grade', '')).upper()}** from the nutrition "
f"facts using the official 2023 algorithm.{note}"
)
st.markdown("#### Nutrition per 100 g")
st.markdown(cards.nutrient_table(product), unsafe_allow_html=True)
st.caption("Bars show the share of an adult reference intake for a 100 g portion.")
st.divider()
col_a, col_b = st.columns(2, gap="large")
with col_a:
st.markdown("#### Allergens")
allergens = tag_set(product.get("allergens_tags"))
traces = tag_set(product.get("traces_tags"))
if allergens:
st.markdown(badges.chips(sorted(allergens), "chip-danger"), unsafe_allow_html=True)
if traces:
st.caption("May contain traces of:")
st.markdown(badges.chips(sorted(traces), "chip-warn"), unsafe_allow_html=True)
if not allergens and not traces:
st.caption("No allergens declared by Open Food Facts for this product.")
st.markdown("#### Diet")
analysis = tag_set(product.get("ingredients_analysis_tags"))
diet_labels = {
"en:vegan": ("Vegan", "chip-good"),
"en:non-vegan": ("Not vegan", "chip-danger"),
"en:maybe-vegan": ("Vegan status unclear", "chip-warn"),
"en:vegetarian": ("Vegetarian", "chip-good"),
"en:non-vegetarian": ("Not vegetarian", "chip-danger"),
"en:maybe-vegetarian": ("Vegetarian status unclear", "chip-warn"),
"en:palm-oil-free": ("Palm-oil free", "chip-good"),
"en:palm-oil": ("Contains palm oil", "chip-danger"),
}
shown = [
f'{label}'
for tag, (label, tone) in diet_labels.items()
if tag in analysis
]
st.markdown(
'' + "".join(shown) + "
" if shown
else "Not analysed.",
unsafe_allow_html=True,
)
with col_b:
st.markdown("#### Additives")
flagged = additives_mod.concerns(sorted(tag_set(product.get("additives_tags"))))
if flagged:
for item in flagged:
tone = {"high": "chip-danger", "moderate": "chip-warn", "watch": ""}[item["concern"]]
st.markdown(
f''
f'{badges.esc(item["name"])}'
f'
'
f'{badges.esc("; ".join(item["reasons"]))}
',
unsafe_allow_html=True,
)
st.caption("Assessments from EFSA and ANSES, via the Open Food Facts additives taxonomy.")
else:
total = num(product.get("n_flagged_additives"))
st.caption(
"No additives on the EFSA or ANSES watch lists."
if total is not None else "No additive data for this product."
)
st.markdown("#### Ingredients")
text = product.get("ingredients_text")
if text and isinstance(text, str) and text.strip():
st.markdown(f'{badges.esc(text)}
', unsafe_allow_html=True)
else:
st.caption("No ingredient list available.")
# Record the view for the profile history.
if profile.user_id:
from nutriweb.profile import auth
auth.log_view(profile.user_id, product, verdict.summary)