"""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)