zainab1234awan's picture
Update app.py
2c0238a verified
Raw
History Blame Contribute Delete
36.7 kB
"""
EcoVision β€” Smart Sustainability Analyzer
Gradio 6. Background thread trains model at startup.
All click handlers wrapped in try/except.
"""
import os, io, tempfile, warnings, threading
from datetime import datetime
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import gradio as gr
warnings.filterwarnings("ignore")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
from eco_score import calculate_eco_score, get_score_description
from ml_model import predict_impact, train_model
from llm_analysis import generate_eco_explanation, compare_products_llm
from vision_model import classify_image
# ── Load CSV (instant) ────────────────────────────────────────────────────────
DF = pd.read_csv(os.path.join(BASE_DIR, "dataset", "products.csv"))
ALL_PRODUCTS = sorted(DF["product_name"].tolist())
ALL_CATS = sorted(DF["category"].unique().tolist())
PACKAGING_OPTS = [
"Plastic Bottle","Glass Bottle","Cardboard Box","Compostable",
"Recycled Plastic","Single-use Plastic","Paper Wrapper","Other",
]
# ── Train model in background thread so UI is never blocked ───────────────────
_model_ready = threading.Event()
_model_error = None
def _train_bg():
global _model_error
try:
model_path = os.path.join(BASE_DIR, "models", "eco_classifier.pkl")
os.makedirs(os.path.join(BASE_DIR, "models"), exist_ok=True)
if not os.path.exists(model_path):
train_model()
_model_ready.set()
print("βœ… Model ready")
except Exception as e:
_model_error = str(e)
_model_ready.set() # unblock anyway so clicks don't hang forever
print(f"❌ Model training failed: {e}")
threading.Thread(target=_train_bg, daemon=True).start()
def wait_for_model():
"""Wait up to 120s for model, return error string or None."""
_model_ready.wait(timeout=120)
return _model_error
# ── Charts ────────────────────────────────────────────────────────────────────
def _bar_color(v):
if v > 0.5: return "#C62828"
if v > 0.25: return "#E65100"
return "#2E7D32"
BASE_LAYOUT = dict(
paper_bgcolor="#FFFFFF",
plot_bgcolor="#F4FBF4",
font=dict(color="#1A1A1A", family="sans-serif"),
margin=dict(t=50, b=30, l=55, r=20),
)
def gauge_chart(score, grade):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(grade,"#2E7D32")
fig = go.Figure(go.Indicator(
mode="gauge+number", value=score,
title=dict(text=f"Eco Score β€’ Grade {grade}",
font=dict(size=14, color="#1B5E20")),
number=dict(font=dict(size=46, color=gc), suffix=" / 10"),
gauge=dict(
axis=dict(range=[1,10], tickfont=dict(color="#607D63")),
bar=dict(color=gc, thickness=0.28),
bgcolor="#F4FBF4", borderwidth=1, bordercolor="#C8E6C9",
steps=[dict(range=[1,3], color="#FFEBEE"),
dict(range=[3,5], color="#FFF3E0"),
dict(range=[5,7], color="#F1F8E9"),
dict(range=[7,10],color="#E8F5E9")],
threshold=dict(line=dict(color=gc,width=4),thickness=0.8,value=score),
),
))
fig.update_layout(**BASE_LAYOUT, height=260)
return fig
def metric_bar(label, value, icon):
color = _bar_color(value)
fig = go.Figure(go.Bar(
x=[label], y=[value], marker_color=[color],
text=[f"{value:.0%}"], textposition="outside",
textfont=dict(color="#1A1A1A", size=14), width=[0.4],
))
fig.update_layout(
**BASE_LAYOUT,
title=dict(text=f"{icon} {label}", font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%",
tickfont=dict(color="#607D63")),
xaxis=dict(showgrid=False),
height=220,
)
return fig
def prob_bar(probs):
fig = go.Figure(go.Bar(
x=list(probs.keys()), y=list(probs.values()),
marker_color=["#2E7D32","#43A047","#66BB6A"][:len(probs)],
text=[f"{v:.0%}" for v in probs.values()],
textposition="outside", textfont=dict(color="#1A1A1A",size=12),
))
fig.update_layout(
**BASE_LAYOUT,
title=dict(text="ML Confidence", font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
height=220,
)
return fig
def comparison_bar(n1, n2, eco1, eco2):
labels = ["Deforestation","Pollution","Biodiversity"]
v1 = [eco1["metrics"]["deforestation_risk"],
eco1["metrics"]["pollution_level"],
eco1["metrics"]["biodiversity_impact"]]
v2 = [eco2["metrics"]["deforestation_risk"],
eco2["metrics"]["pollution_level"],
eco2["metrics"]["biodiversity_impact"]]
fig = go.Figure(data=[
go.Bar(name=n1[:18], x=labels, y=v1,
text=[f"{v:.0%}" for v in v1], textposition="outside",
marker_color="#2E7D32"),
go.Bar(name=n2[:18], x=labels, y=v2,
text=[f"{v:.0%}" for v in v2], textposition="outside",
marker_color="#C62828"),
])
fig.update_layout(
**BASE_LAYOUT, barmode="group", height=280,
title=dict(text="Side-by-Side Metrics",font=dict(color="#1B5E20",size=13)),
yaxis=dict(range=[0,1.4], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
legend=dict(bgcolor="#F4FBF4", bordercolor="#C8E6C9", borderwidth=1),
margin=dict(t=55,b=35,l=55,r=20),
)
return fig
# ── HTML helpers ──────────────────────────────────────────────────────────────
def err_html(msg):
return (f"<div style='background:#FFEBEE;border:1px solid #EF9A9A;"
f"border-radius:10px;padding:14px;font-family:sans-serif;"
f"color:#C62828;font-size:14px;'>❌ {msg}</div>")
def ok_html(msg):
return (f"<div style='background:#E8F5E9;border:1px solid #A5D6A7;"
f"border-radius:10px;padding:12px;font-family:sans-serif;"
f"color:#2E7D32;font-size:14px;'>βœ… {msg}</div>")
def score_card(name, eco, ml):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(eco["grade"],"#2E7D32")
impact = ml["predicted_impact"]
ibg = {"Low Impact":"#E8F5E9","Medium Impact":"#FFF3E0",
"High Impact":"#FFEBEE"}.get(impact,"#F5F5F5")
ifg = {"Low Impact":"#2E7D32","Medium Impact":"#E65100",
"High Impact":"#C62828"}.get(impact,"#333")
badges = []
if eco["metrics"]["deforestation_risk"] < 0.2: badges.append("🌲 Forest Protector")
if eco["metrics"]["pollution_level"] < 0.3: badges.append("πŸ’§ Clean Waters")
if eco["metrics"]["biodiversity_impact"] < 0.2: badges.append("πŸ¦‹ Biodiversity Guard")
if eco["eco_score"] >= 7: badges.append("⭐ Eco Star")
if eco["eco_score"] >= 9: badges.append("🌟 Green Leader")
badge_html = "".join(
f"<span style='display:inline-block;margin:3px;padding:4px 12px;background:#E8F5E9;"
f"color:#2E7D32;border:1px solid #A5D6A7;border-radius:20px;font-size:12px;'>{b}</span>"
for b in badges
) or "<span style='color:#607D63;font-size:13px;'>No badges yet</span>"
def pill(lbl, val):
bg = "#FFEBEE" if val>0.5 else "#FFF8E1" if val>0.3 else "#E8F5E9"
fg = "#C62828" if val>0.5 else "#E65100" if val>0.3 else "#2E7D32"
return (f"<div style='flex:1;min-width:80px;background:{bg};border-radius:10px;"
f"padding:10px;text-align:center;'>"
f"<div style='font-size:20px;font-weight:700;color:{fg};'>{val:.0%}</div>"
f"<div style='font-size:10px;color:#607D63;font-weight:600;"
f"text-transform:uppercase;letter-spacing:.5px;margin-top:2px;'>{lbl}</div></div>")
return f"""
<div style='background:#fff;border:1px solid #C8E6C9;border-radius:14px;
padding:20px;font-family:sans-serif;margin-top:12px;'>
<h2 style='margin:0 0 10px;color:#1B5E20;font-size:18px;'>{name}</h2>
<div style='display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;'>
<span style='background:{ibg};color:{ifg};border:2px solid {ifg};
border-radius:20px;padding:4px 14px;font-size:13px;font-weight:700;'>
{impact}</span>
<span style='color:#607D63;font-size:13px;'>
ML confidence: <strong style='color:#1A1A1A;'>{ml["confidence"]:.0%}</strong></span>
</div>
<div style='display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:16px;'>
<div style='text-align:center;'>
<div style='font-size:60px;font-weight:800;color:{gc};line-height:1;'>{eco["eco_score"]}</div>
<div style='color:#607D63;font-size:11px;'>out of 10</div>
</div>
<div style='border-left:3px solid #C8E6C9;padding-left:14px;flex:1;min-width:150px;'>
<div style='font-size:26px;font-weight:800;color:{gc};'>Grade {eco["grade"]}</div>
<div style='font-size:14px;color:#3E4E3F;'>{eco["grade_label"]}</div>
<div style='font-size:13px;color:#607D63;margin-top:4px;line-height:1.5;'>
{get_score_description(eco["eco_score"])}</div>
</div>
</div>
<div style='display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;'>
{pill("Deforestation", eco["metrics"]["deforestation_risk"])}
{pill("Pollution", eco["metrics"]["pollution_level"])}
{pill("Biodiversity", eco["metrics"]["biodiversity_impact"])}
</div>
<div style='border-top:1px solid #E8F5E9;padding-top:12px;'>
<div style='color:#607D63;font-size:11px;font-weight:700;text-transform:uppercase;
letter-spacing:1px;margin-bottom:8px;'>Badges</div>
{badge_html}
</div>
</div>"""
def alts_card(category, current=""):
rows = DF[(DF["category"]==category) &
(DF["eco_label"]=="Low Impact") &
(DF["product_name"]!=current)].head(3)
if rows.empty:
return ""
items = ""
for _, r in rows.iterrows():
ae = calculate_eco_score(r["deforestation_risk"],
r["pollution_level"], r["biodiversity_impact"])
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828"}.get(ae["grade"],"#2E7D32")
items += (
f"<div style='background:#F1F8F1;border-left:4px solid #2E7D32;"
f"border-radius:0 10px 10px 0;padding:12px 16px;margin:6px 0;"
f"display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;'>"
f"<div><div style='font-weight:700;color:#1B5E20;font-size:14px;'>{r['product_name']}</div>"
f"<div style='color:#607D63;font-size:12px;'>πŸ“¦ {r['packaging_type']}</div></div>"
f"<div style='font-size:20px;font-weight:800;color:{gc};'>{ae['eco_score']}"
f"<span style='font-size:12px;color:#607D63;'>/10</span></div></div>"
)
return (f"<div style='font-family:sans-serif;margin-top:16px;'>"
f"<h3 style='color:#1B5E20;font-size:15px;margin:0 0 8px;'>"
f"🌱 Eco-Friendly Alternatives</h3>{items}</div>")
def llm_card(text, model_name=""):
out = ""
for line in text.split("\n"):
s = line.strip()
if not s: continue
if s.startswith("## "):
out += (f"<h3 style='color:#1B5E20;font-size:15px;font-weight:700;"
f"margin:14px 0 5px;border-bottom:2px solid #C8E6C9;"
f"padding-bottom:3px;'>{s[3:]}</h3>")
elif len(s)>2 and s[0].isdigit() and s[1]==".":
c = s[3:].replace("**","<strong>",1).replace("**","</strong>",1)
out += (f"<div style='background:#F1F8F1;border-left:4px solid #43A047;"
f"border-radius:0 8px 8px 0;padding:10px 14px;margin:5px 0;"
f"color:#1A1A1A;font-size:13px;'>#{s[0]} {c}</div>")
elif s.startswith("- "):
out += f"<li style='color:#1A1A1A;font-size:13px;margin:3px 0;'>{s[2:]}</li>"
else:
s2 = s.replace("**","<strong>",1).replace("**","</strong>",1)
out += (f"<p style='color:#1A1A1A;font-size:13px;line-height:1.7;"
f"margin:5px 0;'>{s2}</p>")
badge = (f"<span style='background:#F1F8F1;border:1px solid #C8E6C9;border-radius:20px;"
f"padding:2px 10px;font-size:11px;color:#607D63;'>{model_name}</span>"
) if model_name else ""
return (f"<div style='background:#fff;border:1px solid #C8E6C9;border-radius:14px;"
f"padding:20px;font-family:sans-serif;margin-top:16px;'>"
f"<h3 style='color:#1B5E20;font-size:16px;margin:0 0 12px;'>"
f"🧠 AI Environmental Analysis {badge}</h3><div>{out}</div></div>")
def compare_card(p, eco, ml, winner):
gc = {"A":"#2E7D32","B":"#43A047","C":"#E65100",
"D":"#C62828","F":"#6A0000"}.get(eco["grade"],"#2E7D32")
border = "#2E7D32" if winner else "#E0E0E0"
pills = "".join(
f"<div style='background:#F5F5F5;border-radius:8px;padding:7px;text-align:center;'>"
f"<div style='font-size:13px;font-weight:700;color:{_bar_color(v)};'>{v:.0%}</div>"
f"<div style='font-size:10px;color:#607D63;'>{l}</div></div>"
for l,v in [("🌲 Deforest.",eco["metrics"]["deforestation_risk"]),
("πŸ’¨ Pollution",eco["metrics"]["pollution_level"]),
("πŸ¦‹ Biodiv.",eco["metrics"]["biodiversity_impact"])]
)
return (f"<div style='background:#fff;border:2px solid {border};border-radius:14px;"
f"padding:16px;font-family:sans-serif;'>"
f"<div style='font-size:15px;font-weight:700;color:#1B5E20;margin-bottom:4px;'>"
f"{'πŸ† ' if winner else ''}{p['product_name']}</div>"
f"<div style='color:#607D63;font-size:12px;margin-bottom:12px;'>"
f"{p['category']} Β· {p['packaging_type']}</div>"
f"<div style='display:flex;gap:12px;margin-bottom:12px;'>"
f"<div style='text-align:center;'>"
f"<div style='font-size:48px;font-weight:800;color:{gc};line-height:1;'>"
f"{eco['eco_score']}</div>"
f"<div style='color:#607D63;font-size:10px;'>SCORE</div></div>"
f"<div style='text-align:center;'>"
f"<div style='font-size:40px;font-weight:800;color:{gc};line-height:1;'>"
f"{eco['grade']}</div>"
f"<div style='color:#607D63;font-size:10px;'>GRADE</div></div></div>"
f"<div style='display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;"
f"margin-bottom:10px;'>{pills}</div>"
f"<div style='font-size:12px;color:#3E4E3F;background:#F9FBF9;"
f"border-radius:8px;padding:8px 12px;'>"
f"ML: <strong>{ml['predicted_impact']}</strong> Β· "
f"Conf: <strong>{ml['confidence']:.0%}</strong></div></div>")
# ── PDF ───────────────────────────────────────────────────────────────────────
def save_pdf(name, category, eco, ml, explanation, packaging=""):
try:
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.colors import HexColor
from reportlab.platypus import (SimpleDocTemplate, Paragraph,
Spacer, Table, TableStyle)
from reportlab.lib.units import inch
buf = io.BytesIO()
doc = SimpleDocTemplate(buf, pagesize=letter, topMargin=inch,
bottomMargin=inch, leftMargin=inch, rightMargin=inch)
TT = ParagraphStyle("TT",fontSize=20,fontName="Helvetica-Bold",
textColor=HexColor("#1B5E20"),spaceAfter=6)
HH = ParagraphStyle("HH",fontSize=13,fontName="Helvetica-Bold",
textColor=HexColor("#2E7D32"),spaceAfter=4,spaceBefore=8)
BB = ParagraphStyle("BB",fontSize=10,fontName="Helvetica",
textColor=HexColor("#1A1A1A"),spaceAfter=3,leading=14)
content = [
Paragraph("EcoVision Sustainability Report", TT),
Paragraph(f"Generated: {datetime.now().strftime('%B %d, %Y %H:%M')}", BB),
Spacer(1,0.15*inch), Paragraph("Product Overview", HH),
]
rows = [["Product",name],["Category",category],
["Eco Score",f"{eco['eco_score']}/10 Grade {eco['grade']}"],
["Impact",ml.get("predicted_impact","N/A")],["Packaging",packaging]]
tbl = Table(rows, colWidths=[1.8*inch, 4.7*inch])
tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,-1),HexColor("#E8F5E9")),
("TEXTCOLOR",(0,0),(0,-1),HexColor("#1B5E20")),
("TEXTCOLOR",(1,0),(1,-1),HexColor("#1A1A1A")),
("FONTNAME",(0,0),(-1,-1),"Helvetica"),
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),10),
("GRID",(0,0),(-1,-1),0.5,HexColor("#C8E6C9")),
("ROWBACKGROUNDS",(0,0),(-1,-1),[HexColor("#FFFFFF"),HexColor("#F7F9F4")]),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
]))
content += [tbl, Spacer(1,0.12*inch), Paragraph("Analysis", HH)]
for line in explanation.replace("**","").replace("## ","").split("\n")[:30]:
if line.strip():
content.append(Paragraph(line.strip(), BB))
doc.build(content)
data = buf.getvalue()
except Exception:
data = (f"EcoVision Report\nProduct: {name}\n"
f"Eco Score: {eco['eco_score']}/10\nGrade: {eco['grade']}\n"
f"Impact: {ml.get('predicted_impact')}\n\n{explanation}").encode("utf-8")
ext = "pdf" if data[:4]==b"%PDF" else "txt"
path = os.path.join(tempfile.gettempdir(),
f"eco_{name[:20].replace(' ','_')}.{ext}")
with open(path,"wb") as f: f.write(data)
return path
# ── Core pipeline ─────────────────────────────────────────────────────────────
def run_eco(name, category, deforest, pollution, biodiversity,
packaging, ingredients, groq_key):
if groq_key:
os.environ["GROQ_API_KEY"] = groq_key.strip()
eco = calculate_eco_score(deforest, pollution, biodiversity)
ml = predict_impact(category, deforest, pollution, biodiversity)
llm = generate_eco_explanation(
name, category, deforest, pollution, biodiversity,
eco["eco_score"], ml["predicted_impact"], packaging, ingredients,
)
return eco, ml, llm
EMPTY9 = ("", None, None, None, None, None, "", "", None)
# ── Tab handlers ──────────────────────────────────────────────────────────────
def search_fn(query, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY9[1:]
if not query.strip():
return (err_html("Please enter a product name."),) + EMPTY9[1:]
q = query.lower()
m = DF[DF["product_name"].str.lower().str.contains(q, na=False)]
if m.empty:
m = DF[DF["category"].str.lower().str.contains(q, na=False)]
if m.empty:
return (err_html(f"'{query}' not found. Try: Patagonia Β· Lays Β· "
f"Head &amp; Shoulders Β· Shein Β· Ethique"),) + EMPTY9[1:]
row = m.iloc[0]
eco, ml, llm = run_eco(
row["product_name"], row["category"],
float(row["deforestation_risk"]), float(row["pollution_level"]),
float(row["biodiversity_impact"]), row["packaging_type"],
row["ingredients"], groq_key,
)
pdf = save_pdf(row["product_name"], row["category"], eco, ml,
llm["explanation"], row["packaging_type"])
return (
score_card(row["product_name"], eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk", eco["metrics"]["deforestation_risk"], "🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "πŸ’¨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"πŸ¦‹"),
prob_bar(ml["all_probabilities"]),
llm_card(llm["explanation"], llm.get("model","")),
alts_card(row["category"], row["product_name"]),
pdf,
)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY9[1:]
def manual_fn(name, category, packaging, ingredients,
deforest, pollution, biodiversity, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY9[1:]
if not name.strip():
return (err_html("Please enter a product name."),) + EMPTY9[1:]
eco, ml, llm = run_eco(name, category, deforest, pollution, biodiversity,
packaging, ingredients, groq_key)
pdf = save_pdf(name, category, eco, ml, llm["explanation"], packaging)
return (
score_card(name, eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk", eco["metrics"]["deforestation_risk"], "🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "πŸ’¨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"πŸ¦‹"),
prob_bar(ml["all_probabilities"]),
llm_card(llm["explanation"], llm.get("model","")),
alts_card(category, ""),
pdf,
)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY9[1:]
EMPTY_IMG = ("", None, "", None, None, None, None, "", None)
def image_fn(image_path, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY_IMG[1:]
if not image_path:
return EMPTY_IMG
vision = classify_image(image_path)
detected = vision["predicted_category"]
cat_rows = DF[DF["category"]==detected]
if cat_rows.empty:
return EMPTY_IMG
avg = cat_rows[["deforestation_risk","pollution_level","biodiversity_impact"]].mean()
rep = cat_rows.iloc[0]
eco, ml, llm = run_eco(
f"Detected: {detected}", detected,
float(avg["deforestation_risk"]), float(avg["pollution_level"]),
float(avg["biodiversity_impact"]), rep["packaging_type"], "", groq_key,
)
scores = vision["all_scores"]
fig_v = go.Figure(go.Bar(
x=list(scores.keys()), y=list(scores.values()),
marker_color=["#2E7D32" if k==detected else "#A5D6A7" for k in scores],
text=[f"{v:.0%}" for v in scores.values()],
textposition="outside", textfont=dict(color="#1A1A1A", size=12),
))
fig_v.update_layout(
**BASE_LAYOUT, height=220,
title=dict(text=f"Vision β€” '{detected}' detected",
font=dict(color="#1B5E20",size=12)),
yaxis=dict(range=[0,1.3], gridcolor="#D4E8D4", tickformat=".0%"),
xaxis=dict(showgrid=False),
)
vhtml = (f"<div style='background:#fff;border:1px solid #C8E6C9;"
f"border-radius:12px;padding:16px;font-family:sans-serif;'>"
f"<div style='color:#607D63;font-size:11px;font-weight:700;"
f"text-transform:uppercase;letter-spacing:1px;'>Detected</div>"
f"<div style='font-size:26px;font-weight:800;color:#1B5E20;'>"
f"πŸ“· {detected}</div>"
f"<div style='color:#607D63;font-size:13px;margin-top:4px;'>"
f"Confidence: <strong style='color:#2E7D32;'>"
f"{vision['confidence']:.0%}</strong> Β· {vision['method']}</div></div>")
pdf = save_pdf(f"Detected {detected}", detected, eco, ml,
llm["explanation"], rep["packaging_type"])
return (vhtml, fig_v, score_card(f"Detected {detected}", eco, ml),
gauge_chart(eco["eco_score"], eco["grade"]),
metric_bar("Deforestation Risk",eco["metrics"]["deforestation_risk"],"🌲"),
metric_bar("Pollution Level", eco["metrics"]["pollution_level"], "πŸ’¨"),
metric_bar("Biodiversity Impact",eco["metrics"]["biodiversity_impact"],"πŸ¦‹"),
llm_card(llm["explanation"], llm.get("model","")), pdf)
except Exception as e:
return (err_html(f"Error: {str(e)}"),) + EMPTY_IMG[1:]
EMPTY_CMP = ("","",None,None,None,"")
def compare_fn(n1, n2, groq_key):
try:
model_err = wait_for_model()
if model_err:
return (err_html(f"Model training failed: {model_err}"),) + EMPTY_CMP[1:]
if groq_key:
os.environ["GROQ_API_KEY"] = groq_key.strip()
p1 = DF[DF["product_name"]==n1].iloc[0]
p2 = DF[DF["product_name"]==n2].iloc[0]
eco1 = calculate_eco_score(float(p1["deforestation_risk"]),
float(p1["pollution_level"]),
float(p1["biodiversity_impact"]))
eco2 = calculate_eco_score(float(p2["deforestation_risk"]),
float(p2["pollution_level"]),
float(p2["biodiversity_impact"]))
ml1 = predict_impact(p1["category"],float(p1["deforestation_risk"]),
float(p1["pollution_level"]),float(p1["biodiversity_impact"]))
ml2 = predict_impact(p2["category"],float(p2["deforestation_risk"]),
float(p2["pollution_level"]),float(p2["biodiversity_impact"]))
d1 = dict(name=n1, category=p1["category"],
eco_score=eco1["eco_score"], **eco1["metrics"])
d2 = dict(name=n2, category=p2["category"],
eco_score=eco2["eco_score"], **eco2["metrics"])
comp = compare_products_llm(d1, d2)
w1 = eco1["eco_score"] >= eco2["eco_score"]
return (compare_card(p1,eco1,ml1,w1), compare_card(p2,eco2,ml2,not w1),
gauge_chart(eco1["eco_score"],eco1["grade"]),
gauge_chart(eco2["eco_score"],eco2["grade"]),
comparison_bar(n1,n2,eco1,eco2),
llm_card(comp["comparison"],comp.get("model","")))
except Exception as e:
return (err_html(f"Error: {str(e)}"), "",None,None,None,"")
# ── Build UI ──────────────────────────────────────────────────────────────────
with gr.Blocks(title="EcoVision β€” Smart Sustainability Analyzer") as demo:
gr.HTML("""
<div style='text-align:center;padding:28px 16px 18px;
background:linear-gradient(135deg,#E8F5E9,#F1F8E9);
border-bottom:2px solid #C8E6C9;'>
<div style='font-size:44px;font-weight:800;color:#1B5E20;'>🌿 EcoVision</div>
<p style='color:#3E4E3F;font-size:14px;margin:8px 0 12px;'>
Smart Sustainability Analyzer &nbsp;Β·&nbsp; ML Β· Computer Vision Β· LLM
</p>
<div style='display:flex;justify-content:center;gap:8px;flex-wrap:wrap;'>
<span style='background:#fff;border:1px solid #C8E6C9;border-radius:20px;
padding:3px 12px;font-size:12px;font-weight:600;color:#2E7D32;'>
βœ… Rule-based Scoring</span>
<span style='background:#fff;border:1px solid #C8E6C9;border-radius:20px;
padding:3px 12px;font-size:12px;font-weight:600;color:#2E7D32;'>
πŸ€– RandomForest ML</span>
<span style='background:#fff;border:1px solid #C8E6C9;border-radius:20px;
padding:3px 12px;font-size:12px;font-weight:600;color:#2E7D32;'>
πŸ“· MobileNetV2 CV</span>
<span style='background:#fff;border:1px solid #C8E6C9;border-radius:20px;
padding:3px 12px;font-size:12px;font-weight:600;color:#2E7D32;'>
🧠 Groq LLM</span>
</div>
</div>""")
with gr.Accordion("πŸ”‘ Groq API Key (optional)", open=False):
gr.HTML("<p style='color:#3E4E3F;font-size:13px;margin:4px 0 8px;'>"
"Free at <a href='https://console.groq.com' target='_blank' "
"style='color:#2E7D32;font-weight:600;'>console.groq.com</a> β€” "
"works without it using template explanations.</p>")
groq_key = gr.Textbox(label="Groq API Key", type="password", placeholder="gsk_…")
with gr.Tabs():
# ── Tab 1: Search ─────────────────────────────────────────────────────
with gr.Tab("πŸ” Product Search"):
gr.HTML("<p style='color:#3E4E3F;font-size:14px;margin:10px 0;'>"
"Search any of our <strong style='color:#1B5E20;'>53+ products</strong>"
" β€” shampoos, snacks, clothing, cosmetics, plastics.</p>")
with gr.Row():
s_in = gr.Textbox(
label="Product name or category",
placeholder="e.g. Head & Shoulders | Patagonia | Lays | Shein",
scale=4,
)
s_btn = gr.Button("πŸ”¬ Analyze", variant="primary", scale=1)
with gr.Accordion("πŸ“‹ Browse all products", open=False):
gr.Dataframe(
value=DF[["product_name","category","eco_label","packaging_type"]].rename(
columns={"product_name":"Product","category":"Category",
"eco_label":"Impact","packaging_type":"Packaging"}),
interactive=False,
)
s_card = gr.HTML()
with gr.Row():
s_gauge = gr.Plot()
s_prob = gr.Plot()
with gr.Row():
s_b1 = gr.Plot()
s_b2 = gr.Plot()
s_b3 = gr.Plot()
s_llm = gr.HTML()
s_alts = gr.HTML()
s_pdf = gr.File(label="πŸ“„ Download Report")
s_btn.click(
fn=search_fn,
inputs=[s_in, groq_key],
outputs=[s_card, s_gauge, s_b1, s_b2, s_b3, s_prob,
s_llm, s_alts, s_pdf],
)
# ── Tab 2: Manual ─────────────────────────────────────────────────────
with gr.Tab("✏️ Manual Input"):
gr.HTML("<p style='color:#3E4E3F;font-size:14px;margin:10px 0;'>"
"Analyse any custom product by entering details manually.</p>")
with gr.Row():
with gr.Column():
m_name = gr.Textbox(label="Product Name",
placeholder="e.g. My Eco Shampoo")
m_cat = gr.Dropdown(ALL_CATS, label="Category", value="Shampoo")
m_pkg = gr.Dropdown(PACKAGING_OPTS, label="Packaging",
value="Plastic Bottle")
m_ingr = gr.Textbox(label="Ingredients / Materials",
placeholder="e.g. Organic Cotton, Natural Dyes",
lines=3)
with gr.Column():
m_defo = gr.Slider(0.0,1.0,value=0.30,step=0.05,label="🌲 Deforestation Risk")
m_poll = gr.Slider(0.0,1.0,value=0.50,step=0.05,label="πŸ’¨ Pollution Level")
m_bio = gr.Slider(0.0,1.0,value=0.40,step=0.05,label="πŸ¦‹ Biodiversity Impact")
m_btn = gr.Button("πŸ”¬ Analyse Product", variant="primary")
m_card = gr.HTML()
with gr.Row():
m_gauge = gr.Plot()
m_prob = gr.Plot()
with gr.Row():
m_b1 = gr.Plot()
m_b2 = gr.Plot()
m_b3 = gr.Plot()
m_llm = gr.HTML()
m_alts = gr.HTML()
m_pdf = gr.File(label="πŸ“„ Download Report")
m_btn.click(
fn=manual_fn,
inputs=[m_name, m_cat, m_pkg, m_ingr,
m_defo, m_poll, m_bio, groq_key],
outputs=[m_card, m_gauge, m_b1, m_b2, m_b3, m_prob,
m_llm, m_alts, m_pdf],
)
# ── Tab 3: Image ──────────────────────────────────────────────────────
with gr.Tab("πŸ“· Image Scan"):
gr.HTML("<p style='color:#3E4E3F;font-size:14px;margin:10px 0;'>"
"Upload a product photo β€” MobileNetV2 detects the category.</p>")
with gr.Row():
with gr.Column():
i_img = gr.Image(type="filepath", label="Upload Product Image",
height=250)
i_btn = gr.Button("πŸ”­ Scan & Analyse", variant="primary")
with gr.Column():
i_detect = gr.HTML()
i_vchart = gr.Plot()
i_card = gr.HTML()
with gr.Row():
i_gauge = gr.Plot()
with gr.Row():
i_b1 = gr.Plot()
i_b2 = gr.Plot()
i_b3 = gr.Plot()
i_llm = gr.HTML()
i_pdf = gr.File(label="πŸ“„ Download Report")
i_btn.click(
fn=image_fn,
inputs=[i_img, groq_key],
outputs=[i_detect, i_vchart, i_card, i_gauge,
i_b1, i_b2, i_b3, i_llm, i_pdf],
)
# ── Tab 4: Compare ────────────────────────────────────────────────────
with gr.Tab("βš–οΈ Compare Products"):
gr.HTML("<p style='color:#3E4E3F;font-size:14px;margin:10px 0;'>"
"Compare any two products side-by-side.</p>")
with gr.Row():
c_p1 = gr.Dropdown(ALL_PRODUCTS, label="πŸ…°οΈ Product A",
value=ALL_PRODUCTS[0], scale=5)
gr.HTML("<div style='padding:22px 6px 0;font-size:18px;"
"font-weight:800;color:#2E7D32;'>VS</div>")
c_p2 = gr.Dropdown(ALL_PRODUCTS, label="πŸ…±οΈ Product B",
value=ALL_PRODUCTS[5], scale=5)
c_btn = gr.Button("βš–οΈ Compare", variant="primary", scale=2)
with gr.Row():
c_card1 = gr.HTML()
c_card2 = gr.HTML()
with gr.Row():
c_g1 = gr.Plot()
c_g2 = gr.Plot()
c_bar = gr.Plot()
c_llm = gr.HTML()
c_btn.click(
fn=compare_fn,
inputs=[c_p1, c_p2, groq_key],
outputs=[c_card1, c_card2, c_g1, c_g2, c_bar, c_llm],
)
gr.HTML("<div style='text-align:center;color:#607D63;font-size:12px;"
"padding:14px;border-top:1px solid #C8E6C9;margin-top:16px;"
"background:#F1F8F1;'>"
"🌿 <strong>EcoVision</strong> · Gradio · scikit-learn · "
"PyTorch MobileNetV2 Β· Groq LLM Β· Plotly</div>")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)