Spaces:
Running
Running
Commit ·
ece90cc
1
Parent(s): 15d717a
Fix: use_column_width for older Streamlit on HF
Browse filesCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -101,7 +101,7 @@ selected_product = st.sidebar.selectbox(
|
|
| 101 |
config = products[selected_product]
|
| 102 |
|
| 103 |
if "image_url" in products[selected_product].get("product", {}):
|
| 104 |
-
st.sidebar.image(products[selected_product]["product"]["image_url"],
|
| 105 |
|
| 106 |
st.sidebar.markdown("---")
|
| 107 |
st.sidebar.header("View")
|
|
@@ -490,7 +490,7 @@ if selected_view == "Strategy":
|
|
| 490 |
xaxis_title="Months", yaxis_title="Cumulative Profit (NOK)",
|
| 491 |
showlegend=False, yaxis=dict(separatethousands=True),
|
| 492 |
)
|
| 493 |
-
st.plotly_chart(fig_be,
|
| 494 |
|
| 495 |
st.markdown("---")
|
| 496 |
|
|
@@ -543,7 +543,7 @@ if selected_view == "Strategy":
|
|
| 543 |
yaxis_title="NOK", showlegend=False,
|
| 544 |
yaxis=dict(separatethousands=True),
|
| 545 |
)
|
| 546 |
-
st.plotly_chart(fig_rc,
|
| 547 |
|
| 548 |
mfg_share = s_mfg_cost / reuse_cycles if reuse_cycles > 0 else s_mfg_cost
|
| 549 |
st.caption(f"Blended cost breakdown: Mfg share {sfmt(mfg_share)} + logistics {sfmt(logistics_per_cycle)} + repair {sfmt(repair_per_cycle)} = **{sfmt(blended_cost_per_product)} NOK** per product per cycle")
|
|
@@ -622,7 +622,7 @@ if selected_view == "Strategy":
|
|
| 622 |
xaxis_title="Months", yaxis_title="Cumulative Profit (NOK)",
|
| 623 |
showlegend=False, yaxis=dict(separatethousands=True),
|
| 624 |
)
|
| 625 |
-
st.plotly_chart(fig_trajectory,
|
| 626 |
|
| 627 |
final_profit = cumulative_profit[-1]
|
| 628 |
st.caption(f"5-year projection with {sfmt(fleet_needed)} unit fleet. Final cumulative profit: **{sfmt(final_profit)} NOK**")
|
|
@@ -662,7 +662,7 @@ if selected_view == "Strategy":
|
|
| 662 |
yaxis=dict(separatethousands=True, rangemode="tozero"),
|
| 663 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 664 |
)
|
| 665 |
-
st.plotly_chart(fig_fleet,
|
| 666 |
savings = customers_served[-1] - portfolio_size
|
| 667 |
st.caption(f"{sfmt(portfolio_size)} products serve **{sfmt(customers_served[-1])} families** over {reuse_cycles} cycles — **{sfmt(savings)} fewer products** manufactured vs linear model.")
|
| 668 |
|
|
@@ -705,7 +705,7 @@ elif selected_view == "Design":
|
|
| 705 |
)
|
| 706 |
fig_grades.update_traces(texttemplate='%{text:.0f}%', textposition='outside')
|
| 707 |
fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns")
|
| 708 |
-
st.plotly_chart(fig_grades,
|
| 709 |
|
| 710 |
st.info(f"**Design insight:** Reducing repair rate from {grade_b_pct:.0%} to {max(5, grade_b_pct*100-10):.0f}% would save €{(grade_b_pct - max(0.05, grade_b_pct-0.10)) * refurb_b:.0f} per returned product.")
|
| 711 |
|
|
@@ -758,7 +758,7 @@ elif selected_view == "Design":
|
|
| 758 |
yaxis_title="Material Composition (%)", yaxis_range=[0, 120],
|
| 759 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 760 |
)
|
| 761 |
-
st.plotly_chart(fig_materials,
|
| 762 |
|
| 763 |
final_original = lifecycle_df.iloc[-1]["Original Material %"]
|
| 764 |
final_new = lifecycle_df.iloc[-1]["New Parts Added %"]
|
|
@@ -805,7 +805,7 @@ elif selected_view == "Logistics":
|
|
| 805 |
)
|
| 806 |
fig_grades.update_traces(texttemplate='%{text:.0f}%', textposition='outside')
|
| 807 |
fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns")
|
| 808 |
-
st.plotly_chart(fig_grades,
|
| 809 |
|
| 810 |
with grade_col2:
|
| 811 |
st.subheader("Cost per Cycle")
|
|
@@ -827,7 +827,7 @@ elif selected_view == "Logistics":
|
|
| 827 |
fig_cycle.add_trace(go.Bar(name="Logistics", x=df_cycle_costs["Cycle"], y=df_cycle_costs["Logistics"],
|
| 828 |
marker_color=COLORS["secondary"], text=[f"€{v:.0f}" for v in df_cycle_costs["Logistics"]], textposition="inside"))
|
| 829 |
fig_cycle.update_layout(barmode="stack", yaxis_title="Cost (EUR)", legend=dict(orientation="h", yanchor="bottom", y=1.02))
|
| 830 |
-
st.plotly_chart(fig_cycle,
|
| 831 |
|
| 832 |
st.markdown(f"**Key insight:** Cycle 1 costs **€{new_unit_cost:.0f}**. Cycles 2+ cost **€{refurb_unit_cost:.0f}** ({(1-refurb_unit_cost/new_unit_cost)*100:.0f}% reduction)")
|
| 833 |
|
|
@@ -875,7 +875,7 @@ elif selected_view == "Logistics":
|
|
| 875 |
xaxis_title="",
|
| 876 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 877 |
)
|
| 878 |
-
st.plotly_chart(fig_products,
|
| 879 |
|
| 880 |
final_in_service = lifecycle_df.iloc[-1]["Products in Service"]
|
| 881 |
final_lost = lifecycle_df.iloc[-1]["Products Lost"]
|
|
@@ -974,7 +974,7 @@ elif selected_view == "Sourcing":
|
|
| 974 |
totals={"marker": {"color": COLORS["info"]}},
|
| 975 |
))
|
| 976 |
fig_waterfall.update_layout(title=f"Manufacturing Cost: €{mfg_cost:.0f}", yaxis_title="EUR", showlegend=False)
|
| 977 |
-
st.plotly_chart(fig_waterfall,
|
| 978 |
|
| 979 |
with cost_col2:
|
| 980 |
st.subheader("Bill of Materials")
|
|
@@ -982,7 +982,7 @@ elif selected_view == "Sourcing":
|
|
| 982 |
st.dataframe(mat_df.style.format({
|
| 983 |
"Weight (kg)": "{:.1f}", "Cost/kg": "€{:.2f}",
|
| 984 |
"Total Cost": "€{:.2f}", "Recycled %": "{:.0f}%",
|
| 985 |
-
}),
|
| 986 |
|
| 987 |
# Summary
|
| 988 |
recyclable_weight = sum(m["Weight (kg)"] for m in material_data if m["Recyclable"] == "Yes")
|
|
@@ -1092,7 +1092,7 @@ elif selected_view == "Market":
|
|
| 1092 |
))
|
| 1093 |
fig_customer.update_layout(title="Customer Cost vs Retail Price", xaxis_title="Monthly Lease Price (EUR)",
|
| 1094 |
yaxis_title="Total Customer Cost (EUR)")
|
| 1095 |
-
st.plotly_chart(fig_customer,
|
| 1096 |
|
| 1097 |
if retail_price > 0 and total_lease_cost < retail_price:
|
| 1098 |
st.success(f"At €{lease_price}/mo, customers **save €{retail_price - total_lease_cost}** ({(retail_price - total_lease_cost)/retail_price*100:.0f}% less than buying)")
|
|
|
|
| 101 |
config = products[selected_product]
|
| 102 |
|
| 103 |
if "image_url" in products[selected_product].get("product", {}):
|
| 104 |
+
st.sidebar.image(products[selected_product]["product"]["image_url"], use_column_width=True)
|
| 105 |
|
| 106 |
st.sidebar.markdown("---")
|
| 107 |
st.sidebar.header("View")
|
|
|
|
| 490 |
xaxis_title="Months", yaxis_title="Cumulative Profit (NOK)",
|
| 491 |
showlegend=False, yaxis=dict(separatethousands=True),
|
| 492 |
)
|
| 493 |
+
st.plotly_chart(fig_be, use_column_width=True)
|
| 494 |
|
| 495 |
st.markdown("---")
|
| 496 |
|
|
|
|
| 543 |
yaxis_title="NOK", showlegend=False,
|
| 544 |
yaxis=dict(separatethousands=True),
|
| 545 |
)
|
| 546 |
+
st.plotly_chart(fig_rc, use_column_width=True)
|
| 547 |
|
| 548 |
mfg_share = s_mfg_cost / reuse_cycles if reuse_cycles > 0 else s_mfg_cost
|
| 549 |
st.caption(f"Blended cost breakdown: Mfg share {sfmt(mfg_share)} + logistics {sfmt(logistics_per_cycle)} + repair {sfmt(repair_per_cycle)} = **{sfmt(blended_cost_per_product)} NOK** per product per cycle")
|
|
|
|
| 622 |
xaxis_title="Months", yaxis_title="Cumulative Profit (NOK)",
|
| 623 |
showlegend=False, yaxis=dict(separatethousands=True),
|
| 624 |
)
|
| 625 |
+
st.plotly_chart(fig_trajectory, use_column_width=True)
|
| 626 |
|
| 627 |
final_profit = cumulative_profit[-1]
|
| 628 |
st.caption(f"5-year projection with {sfmt(fleet_needed)} unit fleet. Final cumulative profit: **{sfmt(final_profit)} NOK**")
|
|
|
|
| 662 |
yaxis=dict(separatethousands=True, rangemode="tozero"),
|
| 663 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 664 |
)
|
| 665 |
+
st.plotly_chart(fig_fleet, use_column_width=True)
|
| 666 |
savings = customers_served[-1] - portfolio_size
|
| 667 |
st.caption(f"{sfmt(portfolio_size)} products serve **{sfmt(customers_served[-1])} families** over {reuse_cycles} cycles — **{sfmt(savings)} fewer products** manufactured vs linear model.")
|
| 668 |
|
|
|
|
| 705 |
)
|
| 706 |
fig_grades.update_traces(texttemplate='%{text:.0f}%', textposition='outside')
|
| 707 |
fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns")
|
| 708 |
+
st.plotly_chart(fig_grades, use_column_width=True)
|
| 709 |
|
| 710 |
st.info(f"**Design insight:** Reducing repair rate from {grade_b_pct:.0%} to {max(5, grade_b_pct*100-10):.0f}% would save €{(grade_b_pct - max(0.05, grade_b_pct-0.10)) * refurb_b:.0f} per returned product.")
|
| 711 |
|
|
|
|
| 758 |
yaxis_title="Material Composition (%)", yaxis_range=[0, 120],
|
| 759 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 760 |
)
|
| 761 |
+
st.plotly_chart(fig_materials, use_column_width=True)
|
| 762 |
|
| 763 |
final_original = lifecycle_df.iloc[-1]["Original Material %"]
|
| 764 |
final_new = lifecycle_df.iloc[-1]["New Parts Added %"]
|
|
|
|
| 805 |
)
|
| 806 |
fig_grades.update_traces(texttemplate='%{text:.0f}%', textposition='outside')
|
| 807 |
fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns")
|
| 808 |
+
st.plotly_chart(fig_grades, use_column_width=True)
|
| 809 |
|
| 810 |
with grade_col2:
|
| 811 |
st.subheader("Cost per Cycle")
|
|
|
|
| 827 |
fig_cycle.add_trace(go.Bar(name="Logistics", x=df_cycle_costs["Cycle"], y=df_cycle_costs["Logistics"],
|
| 828 |
marker_color=COLORS["secondary"], text=[f"€{v:.0f}" for v in df_cycle_costs["Logistics"]], textposition="inside"))
|
| 829 |
fig_cycle.update_layout(barmode="stack", yaxis_title="Cost (EUR)", legend=dict(orientation="h", yanchor="bottom", y=1.02))
|
| 830 |
+
st.plotly_chart(fig_cycle, use_column_width=True)
|
| 831 |
|
| 832 |
st.markdown(f"**Key insight:** Cycle 1 costs **€{new_unit_cost:.0f}**. Cycles 2+ cost **€{refurb_unit_cost:.0f}** ({(1-refurb_unit_cost/new_unit_cost)*100:.0f}% reduction)")
|
| 833 |
|
|
|
|
| 875 |
xaxis_title="",
|
| 876 |
legend=dict(orientation="h", yanchor="bottom", y=1.02),
|
| 877 |
)
|
| 878 |
+
st.plotly_chart(fig_products, use_column_width=True)
|
| 879 |
|
| 880 |
final_in_service = lifecycle_df.iloc[-1]["Products in Service"]
|
| 881 |
final_lost = lifecycle_df.iloc[-1]["Products Lost"]
|
|
|
|
| 974 |
totals={"marker": {"color": COLORS["info"]}},
|
| 975 |
))
|
| 976 |
fig_waterfall.update_layout(title=f"Manufacturing Cost: €{mfg_cost:.0f}", yaxis_title="EUR", showlegend=False)
|
| 977 |
+
st.plotly_chart(fig_waterfall, use_column_width=True)
|
| 978 |
|
| 979 |
with cost_col2:
|
| 980 |
st.subheader("Bill of Materials")
|
|
|
|
| 982 |
st.dataframe(mat_df.style.format({
|
| 983 |
"Weight (kg)": "{:.1f}", "Cost/kg": "€{:.2f}",
|
| 984 |
"Total Cost": "€{:.2f}", "Recycled %": "{:.0f}%",
|
| 985 |
+
}), use_column_width=True, hide_index=True)
|
| 986 |
|
| 987 |
# Summary
|
| 988 |
recyclable_weight = sum(m["Weight (kg)"] for m in material_data if m["Recyclable"] == "Yes")
|
|
|
|
| 1092 |
))
|
| 1093 |
fig_customer.update_layout(title="Customer Cost vs Retail Price", xaxis_title="Monthly Lease Price (EUR)",
|
| 1094 |
yaxis_title="Total Customer Cost (EUR)")
|
| 1095 |
+
st.plotly_chart(fig_customer, use_column_width=True)
|
| 1096 |
|
| 1097 |
if retail_price > 0 and total_lease_cost < retail_price:
|
| 1098 |
st.success(f"At €{lease_price}/mo, customers **save €{retail_price - total_lease_cost}** ({(retail_price - total_lease_cost)/retail_price*100:.0f}% less than buying)")
|