import streamlit as st import pandas as pd import plotly.graph_objects as go from nutriscore import NutriScoreCalculator # Page config st.set_page_config( page_title="Nutri-Score Calculator", page_icon="🥗", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS st.markdown(""" """, unsafe_allow_html=True) # Header st.markdown('
Nutri-Score Calculator
', unsafe_allow_html=True) st.markdown('
Calculate the nutritional quality of food products
', unsafe_allow_html=True) # Sidebar with information with st.sidebar: st.header("ℹ️ About Nutri-Score") st.markdown(""" The **Nutri-Score** is a nutrition label that converts the nutritional value of products into a simple code consisting of **5 letters** (A to E), each with its own color. ### How it works: - **A (Dark Green)**: Best nutritional quality - **B (Light Green)**: Good quality - **C (Yellow)**: Average quality - **D (Orange)**: Poor quality - **E (Red)**: Worst quality ### Calculation: - **Negative Points (N)**: Energy, sugars, saturated fat, salt - **Positive Points (P)**: Proteins, fiber, fruits/vegetables - **Score = N - P** Lower scores = Better nutrition """) st.divider() st.header("Example Products") st.markdown(""" ### **1. Low-Fat Plain Yogurt — Nutri-Score A** **Nutrients per 100g** - Energy: **250 kJ** - Saturated Fat: **0.2 g** - Sugars: **3.5 g** - Salt: **0.10 g** - Protein: **5.5 g** - Fiber: **0 g** - Fruits/Vegetables/Nuts: **0%** --- ### **2. Milk Chocolate — Nutri-Score E** **Nutrients per 100g** - Energy: **2200 kJ** - Saturated Fat: **18 g** - Sugars: **50 g** - Salt: **0.35 g** - Protein: **7 g** - Fiber: **3 g** - Fruits/Vegetables/Nuts: **0%** --- ### **3. Frozen French Fries (Baked) — Nutri-Score A** **Nutrients per 100g** - Energy: **670 kJ** - Saturated Fat: **1.2 g** - Sugars: **0.3 g** - Salt: **0.10 g** - Protein: **2.5 g** - Fiber: **2.0 g** - Fruits/Vegetables/Nuts: **95% (Potato)** """) # Main content in two columns col1, col2 = st.columns([1, 1], gap="large") with col1: st.markdown("### Input Nutritional Values (per 100g/100ml)") with st.container(): st.markdown('
', unsafe_allow_html=True) st.markdown("**🔴 Nutrients to Limit**") energy_kj = st.number_input( "Energy (kJ) *", min_value=0.0, max_value=5000.0, value=1500.0, step=10.0, help="Energy content in kilojoules" ) saturated_fat = st.number_input( "Saturated Fat (g) *", min_value=0.0, max_value=100.0, value=5.0, step=0.1, help="Saturated fatty acids content" ) sugars = st.number_input( "Sugars (g) *", min_value=0.0, max_value=100.0, value=10.0, step=0.1, help="Simple sugars content" ) salt = st.number_input( "Salt (g) *", min_value=0.0, max_value=10.0, value=1.0, step=0.01, help="Salt (sodium chloride) content" ) st.markdown('
', unsafe_allow_html=True) with st.container(): st.markdown('
', unsafe_allow_html=True) st.markdown("**🟢 Nutrients to Favor**") proteins = st.number_input( "Proteins (g) *", min_value=0.0, max_value=100.0, value=8.0, step=0.1, help="Protein content" ) fiber = st.number_input( "Fiber (g) *", min_value=0.0, max_value=100.0, value=3.0, step=0.1, help="Dietary fiber content" ) fruit_veg = st.number_input( "Fruits/Vegetables/Nuts (%)", min_value=0.0, max_value=100.0, value=20.0, step=1.0, help="Percentage of fruits, vegetables, legumes and nuts" ) st.markdown('
', unsafe_allow_html=True) # Calculate button calculate_btn = st.button("Calculate Nutri-Score", type="primary", use_container_width=True) with col2: st.markdown("### Results") if calculate_btn or st.session_state.get('auto_calculate', False): # Calculate Nutri-Score result = NutriScoreCalculator.calculate_nutriscore( energy_kj=energy_kj, saturated_fat_g=saturated_fat, sugars_g=sugars, salt_g=salt, proteins_g=proteins, fiber_g=fiber, fruit_veg_pct=fruit_veg ) # Display Nutri-Score grade with color grade = result['class'] grade_class = f"grade-{grade}" st.markdown(f"""
Nutri-Score: {grade}
""", unsafe_allow_html=True) # Display visual Nutri-Score bar st.markdown("#### Nutri-Score Scale") # Create Plotly figure for visual scale grades = ['A', 'B', 'C', 'D', 'E'] colors = { 'A': '#038141', 'B': '#85BB2F', 'C': '#FECB02', 'D': '#EE8100', 'E': '#E63E11' } fig = go.Figure() for i, g in enumerate(grades): # Larger bar for selected grade height = 1.5 if g == grade else 1.0 y_pos = 0.5 if g == grade else 0.25 fig.add_trace(go.Bar( x=[1], y=[height], name=g, marker_color=colors[g], text=g, textposition='inside', textfont=dict(size=24 if g == grade else 18, color='white', family='Arial Black'), hovertemplate=f'{g}', showlegend=False )) fig.update_layout( barmode='group', height=150, margin=dict(l=0, r=0, t=0, b=0), xaxis=dict(showticklabels=False, showgrid=False), yaxis=dict(showticklabels=False, showgrid=False), plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)' ) st.plotly_chart(fig, use_container_width=True) # Display breakdown in metrics st.markdown("#### Score Breakdown") metric_col1, metric_col2, metric_col3 = st.columns(3) with metric_col1: st.metric( label="Negative Points (N)", value=result['negative_points'], delta=None, help="Points from nutrients to limit" ) with metric_col2: st.metric( label="Positive Points (P)", value=result['positive_points'], delta=None, help="Points from nutrients to favor" ) with metric_col3: st.metric( label="Final Score", value=result['score'], delta=None, help="Score = N - P (lower is better)" ) # Interpretation st.markdown("#### Interpretation") interpretations = { 'A': "**Excellent nutritional quality!** This product is among the best choices for your health.", 'B': "**Good nutritional quality.** A solid choice for a balanced diet.", 'C': "**Average nutritional quality.** Consume in moderation as part of a varied diet.", 'D': "**Poor nutritional quality.** Limit consumption of this product.", 'E': "**Very poor nutritional quality.** Avoid regular consumption." } st.info(interpretations[grade]) # Display calculation details with st.expander(" View Calculation Details"): st.markdown("##### Negative Component (N)") st.markdown(f""" - Energy: {energy_kj} kJ → Points: {NutriScoreCalculator._get_points_from_thresholds(energy_kj, NutriScoreCalculator.ENERGY_THRESHOLDS)} - Saturated Fat: {saturated_fat} g → Points: {NutriScoreCalculator._get_points_from_thresholds(saturated_fat, NutriScoreCalculator.SATURATED_FAT_THRESHOLDS)} - Sugars: {sugars} g → Points: {NutriScoreCalculator._get_points_from_thresholds(sugars, NutriScoreCalculator.SUGARS_THRESHOLDS)} - Salt: {salt} g → Points: {NutriScoreCalculator._get_points_from_thresholds(salt, NutriScoreCalculator.SALT_THRESHOLDS)} - **Total N: {result['negative_points']}** """) st.markdown("##### Positive Component (P)") protein_note = " *(not counted due to N≥11 and fruit/veg≤80%)*" if (result['negative_points'] >= 11 and fruit_veg <= 80) else "" st.markdown(f""" - Proteins: {proteins} g{protein_note} - Fiber: {fiber} g - Fruits/Vegetables: {fruit_veg}% - **Total P: {result['positive_points']}** """) st.markdown(f"##### Final Calculation") st.markdown(f"**Score = {result['negative_points']} - {result['positive_points']} = {result['score']}**") st.markdown(f"**Grade: {grade}** (Score range: {NutriScoreCalculator.CLASS_THRESHOLDS[grade][0]} to {NutriScoreCalculator.CLASS_THRESHOLDS[grade][1]})") else: st.info(" Enter nutritional values on the left and click 'Calculate' to see the Nutri-Score!") # Footer st.divider() st.markdown("""

Nutri-Score Calculator | Decision Modeling Project 2025-2026

Based on the official French Nutri-Score algorithm (ANSES methodology)

⚠️ This calculator is for educational purposes. Always refer to official product labels.

""", unsafe_allow_html=True)