Add Task 6 Business Case tab
Browse files
app.py
CHANGED
|
@@ -373,12 +373,13 @@ def _met(y,yh):
|
|
| 373 |
st.markdown("<h2 style='margin:0 0 12px;color:#1e293b'>π EcoCart AI System</h2>",
|
| 374 |
unsafe_allow_html=True)
|
| 375 |
|
| 376 |
-
T1,T2,T3,T4,T5=st.tabs([
|
| 377 |
"π€ Task 1 β AI Agents",
|
| 378 |
"βοΈ Task 2 β Bias Check",
|
| 379 |
"πΊοΈ Task 3 β Route Finder",
|
| 380 |
"π Task 4 β Speed Test",
|
| 381 |
"π Task 5 β Sales Forecast",
|
|
|
|
| 382 |
])
|
| 383 |
|
| 384 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -858,3 +859,182 @@ with T5:
|
|
| 858 |
"LR Prediction":lp.round(1),"RF Prediction":rp.round(1),
|
| 859 |
"LR Error":(y-lp).round(1),"RF Error":(y-rp).round(1)}),
|
| 860 |
use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
st.markdown("<h2 style='margin:0 0 12px;color:#1e293b'>π EcoCart AI System</h2>",
|
| 374 |
unsafe_allow_html=True)
|
| 375 |
|
| 376 |
+
T1,T2,T3,T4,T5,T6=st.tabs([
|
| 377 |
"π€ Task 1 β AI Agents",
|
| 378 |
"βοΈ Task 2 β Bias Check",
|
| 379 |
"πΊοΈ Task 3 β Route Finder",
|
| 380 |
"π Task 4 β Speed Test",
|
| 381 |
"π Task 5 β Sales Forecast",
|
| 382 |
+
"πΌ Task 6 β Business Case",
|
| 383 |
])
|
| 384 |
|
| 385 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 859 |
"LR Prediction":lp.round(1),"RF Prediction":rp.round(1),
|
| 860 |
"LR Error":(y-lp).round(1),"RF Error":(y-rp).round(1)}),
|
| 861 |
use_container_width=True)
|
| 862 |
+
|
| 863 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 864 |
+
# TASK 6 β BUSINESS CASE (optional for AI students β covers 20% Business Viability)
|
| 865 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 866 |
+
with T6:
|
| 867 |
+
st.markdown("### Business Case β ROI & Sustainability Impact")
|
| 868 |
+
st.caption("Adjust the assumptions below to model EcoCart's financial and environmental gains from the AI system.")
|
| 869 |
+
|
| 870 |
+
# ββ assumption sliders ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 871 |
+
st.markdown("#### Your business assumptions")
|
| 872 |
+
c1,c2,c3=st.columns(3)
|
| 873 |
+
with c1:
|
| 874 |
+
fleet = st.slider("Fleet size (vehicles)", 5, 100, 30, 5)
|
| 875 |
+
deliveries = st.slider("Deliveries per vehicle/day", 10, 80, 40, 5)
|
| 876 |
+
avg_km = st.slider("Avg km per delivery", 2, 30, 12, 1)
|
| 877 |
+
with c2:
|
| 878 |
+
fuel_cost = st.slider("Fuel cost per km (β¬)", 0.10, 0.60, 0.32, 0.01, format="β¬%.2f")
|
| 879 |
+
driver_wage = st.slider("Driver hourly wage (β¬)", 10, 35, 18, 1, format="β¬%d")
|
| 880 |
+
working_days= st.slider("Working days per year", 200, 365, 300, 10)
|
| 881 |
+
with c3:
|
| 882 |
+
route_saving_pct = st.slider("Route saving from A* (%)", 5, 35, 18, 1, format="%d%%",
|
| 883 |
+
help="How much shorter routes become with A* vs manual planning")
|
| 884 |
+
forecast_waste_pct= st.slider("Waste cut from forecasting (%)", 5, 40, 22, 1, format="%d%%",
|
| 885 |
+
help="Reduction in overstock/understock from ML demand prediction")
|
| 886 |
+
segment_revenue = st.slider("Extra revenue from fair targeting (β¬k/yr)", 10, 200, 65, 5)
|
| 887 |
+
|
| 888 |
+
st.divider()
|
| 889 |
+
|
| 890 |
+
# ββ calculations ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 891 |
+
total_deliveries_yr = fleet * deliveries * working_days
|
| 892 |
+
total_km_yr = total_deliveries_yr * avg_km
|
| 893 |
+
|
| 894 |
+
# route savings
|
| 895 |
+
km_saved = total_km_yr * route_saving_pct / 100
|
| 896 |
+
fuel_saved = km_saved * fuel_cost
|
| 897 |
+
time_saved_hrs = km_saved / 40 # assume 40 km/h avg
|
| 898 |
+
driver_time_saved = time_saved_hrs * driver_wage
|
| 899 |
+
route_total_saving = fuel_saved + driver_time_saved
|
| 900 |
+
|
| 901 |
+
# CO2 savings (diesel: ~0.27 kg CO2/km urban, ~0.21 rural β avg 0.24)
|
| 902 |
+
co2_saved_kg = km_saved * 0.24
|
| 903 |
+
co2_saved_tonnes = co2_saved_kg / 1000
|
| 904 |
+
|
| 905 |
+
# forecast savings (assume avg inventory cost β¬8 per unit, 500 SKUs)
|
| 906 |
+
inventory_cost_base = 500 * 8 * working_days * 0.05 # 5% daily holding cost approximation
|
| 907 |
+
forecast_saving = inventory_cost_base * forecast_waste_pct / 100
|
| 908 |
+
|
| 909 |
+
# segmentation revenue uplift
|
| 910 |
+
segment_saving = segment_revenue * 1000
|
| 911 |
+
|
| 912 |
+
# total benefit
|
| 913 |
+
total_benefit = route_total_saving + forecast_saving + segment_saving
|
| 914 |
+
|
| 915 |
+
# implementation cost (one-off dev + annual cloud)
|
| 916 |
+
dev_cost = 45000 # one-off
|
| 917 |
+
annual_ops = 8000 # cloud + maintenance per year
|
| 918 |
+
total_cost_yr1 = dev_cost + annual_ops
|
| 919 |
+
total_cost_yr3 = dev_cost + annual_ops * 3
|
| 920 |
+
|
| 921 |
+
roi_yr1 = round((total_benefit - total_cost_yr1) / total_cost_yr1 * 100, 1)
|
| 922 |
+
roi_yr3 = round((total_benefit * 3 - total_cost_yr3) / total_cost_yr3 * 100, 1)
|
| 923 |
+
payback = round(total_cost_yr1 / total_benefit * 12, 1) # months
|
| 924 |
+
|
| 925 |
+
# ββ headline metrics ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 926 |
+
st.markdown("#### Results")
|
| 927 |
+
m=st.columns(4)
|
| 928 |
+
m[0].metric("Annual cost saving", f"β¬{total_benefit/1000:.1f}k")
|
| 929 |
+
m[1].metric("Year-1 ROI", f"{roi_yr1}%",
|
| 930 |
+
delta="positive" if roi_yr1>0 else "negative")
|
| 931 |
+
m[2].metric("Payback period", f"{payback} months")
|
| 932 |
+
m[3].metric("COβ saved per year", f"{co2_saved_tonnes:.1f} tonnes")
|
| 933 |
+
|
| 934 |
+
# ββ savings breakdown bar chart βββββββββββββββββββββββββββββββββββββββββββ
|
| 935 |
+
fig_roi=go.Figure()
|
| 936 |
+
categories=["Route\nOptimisation","Demand\nForecasting","Fairer\nSegmentation"]
|
| 937 |
+
values=[round(route_total_saving/1000,1),
|
| 938 |
+
round(forecast_saving/1000,1),
|
| 939 |
+
round(segment_saving/1000,1)]
|
| 940 |
+
colors=[BLUE,GREEN,AMBER]
|
| 941 |
+
fig_roi.add_trace(go.Bar(
|
| 942 |
+
x=categories, y=values,
|
| 943 |
+
marker_color=colors,
|
| 944 |
+
text=[f"β¬{v}k" for v in values],
|
| 945 |
+
textposition="outside", textfont_color=FG,
|
| 946 |
+
hovertemplate="%{x}<br>Saving: β¬%{y}k/year<extra></extra>",
|
| 947 |
+
width=0.5,
|
| 948 |
+
))
|
| 949 |
+
fig_roi.update_layout(**_ch(300,"Annual savings breakdown by AI module (β¬ thousands)"))
|
| 950 |
+
fig_roi.update_xaxes(**_xax())
|
| 951 |
+
fig_roi.update_yaxes(**_yax(title="β¬ thousands"))
|
| 952 |
+
st.plotly_chart(fig_roi,use_container_width=True)
|
| 953 |
+
|
| 954 |
+
# ββ 3-year cumulative ROI line ββββββββββββββββββββββββββββββββββββββββββββ
|
| 955 |
+
years=[0,1,2,3]
|
| 956 |
+
cumulative_benefit=[0, total_benefit, total_benefit*2, total_benefit*3]
|
| 957 |
+
cumulative_cost =[0, total_cost_yr1, total_cost_yr1+annual_ops, total_cost_yr1+annual_ops*2]
|
| 958 |
+
cumulative_net =[b-c for b,c in zip(cumulative_benefit,cumulative_cost)]
|
| 959 |
+
|
| 960 |
+
fig_cum=go.Figure()
|
| 961 |
+
fig_cum.add_trace(go.Scatter(x=years,y=[v/1000 for v in cumulative_benefit],
|
| 962 |
+
name="Cumulative benefit",line=dict(color=GREEN,width=2.5),
|
| 963 |
+
hovertemplate="Year %{x}<br>Benefit: β¬%{y:.1f}k<extra></extra>"))
|
| 964 |
+
fig_cum.add_trace(go.Scatter(x=years,y=[v/1000 for v in cumulative_cost],
|
| 965 |
+
name="Cumulative cost",line=dict(color=RED,width=2.5,dash="dash"),
|
| 966 |
+
hovertemplate="Year %{x}<br>Cost: β¬%{y:.1f}k<extra></extra>"))
|
| 967 |
+
fig_cum.add_trace(go.Scatter(x=years,y=[v/1000 for v in cumulative_net],
|
| 968 |
+
name="Net gain",line=dict(color=BLUE,width=2.5,dash="dot"),
|
| 969 |
+
fill="tozeroy",fillcolor=f"{BLUE}18",
|
| 970 |
+
hovertemplate="Year %{x}<br>Net: β¬%{y:.1f}k<extra></extra>"))
|
| 971 |
+
fig_cum.add_hline(y=0,line_color="#94a3b8",line_width=1.5,line_dash="dash")
|
| 972 |
+
fig_cum.update_layout(**_ch(300,"3-year cumulative ROI projection (β¬ thousands)"))
|
| 973 |
+
fig_cum.update_xaxes(**_xax(title="Year",tickvals=[0,1,2,3],ticktext=["Now","Year 1","Year 2","Year 3"]))
|
| 974 |
+
fig_cum.update_yaxes(**_yax(title="β¬ thousands"))
|
| 975 |
+
st.plotly_chart(fig_cum,use_container_width=True)
|
| 976 |
+
|
| 977 |
+
# ββ CO2 & sustainability ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 978 |
+
st.markdown("#### Sustainability Impact")
|
| 979 |
+
sc=st.columns(3)
|
| 980 |
+
trees_equiv = round(co2_saved_tonnes * 45) # ~45 trees absorb 1 tonne CO2/year
|
| 981 |
+
cars_equiv = round(co2_saved_tonnes / 2.3) # avg car emits 2.3 tonnes CO2/year
|
| 982 |
+
sc[0].metric("COβ saved per year", f"{co2_saved_tonnes:.1f} tonnes")
|
| 983 |
+
sc[1].metric("Equivalent trees planted", f"{trees_equiv:,}")
|
| 984 |
+
sc[2].metric("Cars taken off the road", f"{cars_equiv:,}")
|
| 985 |
+
|
| 986 |
+
fig_co2=go.Figure(go.Bar(
|
| 987 |
+
x=["Fuel savings\n(route opt.)","Green routing\n(COβ mode)","Total COβ\nreduction"],
|
| 988 |
+
y=[round(co2_saved_tonnes*0.75,1), round(co2_saved_tonnes*0.25,1), round(co2_saved_tonnes,1)],
|
| 989 |
+
marker_color=[GREEN,BLUE,AMBER],
|
| 990 |
+
text=[f"{v:.1f}t" for v in [co2_saved_tonnes*0.75, co2_saved_tonnes*0.25, co2_saved_tonnes]],
|
| 991 |
+
textposition="outside",textfont_color=FG,width=0.45,
|
| 992 |
+
hovertemplate="%{x}<br>%{y:.1f} tonnes COβ/year<extra></extra>",
|
| 993 |
+
))
|
| 994 |
+
fig_co2.update_layout(**_ch(280,"Annual COβ reduction (tonnes)"))
|
| 995 |
+
fig_co2.update_xaxes(**_xax())
|
| 996 |
+
fig_co2.update_yaxes(**_yax(title="Tonnes COβ"))
|
| 997 |
+
st.plotly_chart(fig_co2,use_container_width=True)
|
| 998 |
+
|
| 999 |
+
# ββ implementation roadmap ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1000 |
+
st.markdown("#### Implementation Roadmap")
|
| 1001 |
+
phases=[
|
| 1002 |
+
dict(Task="Phase 1: Route Optimisation (A*)", Start=0, Finish=2, Color=BLUE),
|
| 1003 |
+
dict(Task="Phase 2: Bias Fix (Segmentation)", Start=1, Finish=3, Color=AMBER),
|
| 1004 |
+
dict(Task="Phase 3: Demand Forecasting (ML)", Start=2, Finish=5, Color=GREEN),
|
| 1005 |
+
dict(Task="Phase 4: Integration & Testing", Start=4, Finish=6, Color=PURPLE),
|
| 1006 |
+
dict(Task="Phase 5: Full Deployment", Start=6, Finish=8, Color=RED),
|
| 1007 |
+
]
|
| 1008 |
+
fig_rm=go.Figure()
|
| 1009 |
+
for i,p in enumerate(phases):
|
| 1010 |
+
fig_rm.add_trace(go.Bar(
|
| 1011 |
+
x=[p["Finish"]-p["Start"]], y=[p["Task"]],
|
| 1012 |
+
base=p["Start"], orientation="h",
|
| 1013 |
+
marker_color=p["Color"], marker_opacity=0.85,
|
| 1014 |
+
text=f"Month {p['Start']+1}β{p['Finish']}",
|
| 1015 |
+
textposition="inside", textfont=dict(color="#fff",size=11),
|
| 1016 |
+
showlegend=False,
|
| 1017 |
+
hovertemplate=f"<b>{p['Task']}</b><br>Month {p['Start']+1} β {p['Finish']}<extra></extra>",
|
| 1018 |
+
))
|
| 1019 |
+
fig_rm.update_layout(**_ch(280,"Deployment timeline (months)"))
|
| 1020 |
+
fig_rm.update_xaxes(**_xax(title="Month",tickvals=list(range(9)),
|
| 1021 |
+
ticktext=[f"M{i}" for i in range(9)]))
|
| 1022 |
+
fig_rm.update_yaxes(**_yax(autorange="reversed"))
|
| 1023 |
+
st.plotly_chart(fig_rm,use_container_width=True)
|
| 1024 |
+
|
| 1025 |
+
# ββ summary box βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1026 |
+
st.markdown(
|
| 1027 |
+
f"<div style='background:#f0fdf4;border:1px solid #bbf7d0;border-radius:12px;"
|
| 1028 |
+
f"padding:20px 24px;margin-top:8px'>"
|
| 1029 |
+
f"<b style='font-size:1rem;color:#065f46'>Business Summary</b><br><br>"
|
| 1030 |
+
f"EcoCart's AI system delivers an estimated <b>β¬{total_benefit/1000:.0f}k in annual savings</b> "
|
| 1031 |
+
f"across three areas: smarter routing (A* reduces km by {route_saving_pct}%), "
|
| 1032 |
+
f"better stock management (ML cuts waste by {forecast_waste_pct}%), "
|
| 1033 |
+
f"and fairer customer targeting (rural revenue uplift of β¬{segment_revenue}k). "
|
| 1034 |
+
f"The system pays for itself in <b>{payback} months</b> and generates a "
|
| 1035 |
+
f"<b>{roi_yr3}% ROI over 3 years</b>. "
|
| 1036 |
+
f"It also removes <b>{co2_saved_tonnes:.1f} tonnes of COβ</b> annually β "
|
| 1037 |
+
f"directly supporting EcoCart's sustainability commitments."
|
| 1038 |
+
f"</div>",
|
| 1039 |
+
unsafe_allow_html=True,
|
| 1040 |
+
)
|