Spaces:
Running
Running
Commit ·
4e38411
1
Parent(s): 5036ebc
Add pricing sensitivity analysis section
Browse filesNew Section 6 visualizes:
- Profit per product at different monthly prices (line chart)
- Customer cost vs retail price comparison (bar chart)
- Current price highlighted with star marker
- Break-even price calculation
- Customer savings/premium indicator
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -798,6 +798,135 @@ st.markdown(f"""
|
|
| 798 |
- Break-even happens after just **1 cycle** (circular starts equal, then pulls ahead)
|
| 799 |
""")
|
| 800 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
# ============================================================
|
| 802 |
# FOOTER
|
| 803 |
# ============================================================
|
|
|
|
| 798 |
- Break-even happens after just **1 cycle** (circular starts equal, then pulls ahead)
|
| 799 |
""")
|
| 800 |
|
| 801 |
+
# ============================================================
|
| 802 |
+
# SECTION 6: PRICING SENSITIVITY
|
| 803 |
+
# ============================================================
|
| 804 |
+
st.markdown("---")
|
| 805 |
+
st.header("Pricing Analysis: Finding the Sweet Spot")
|
| 806 |
+
|
| 807 |
+
st.markdown("How does monthly lease price affect profitability and customer value?")
|
| 808 |
+
|
| 809 |
+
# Calculate metrics at different price points
|
| 810 |
+
price_range = list(range(15, 51, 5)) # €15 to €50
|
| 811 |
+
pricing_data = []
|
| 812 |
+
|
| 813 |
+
for test_price in price_range:
|
| 814 |
+
test_revenue = test_price * lease_term
|
| 815 |
+
test_total_revenue = test_revenue * max_cycles # Over full product lifecycle
|
| 816 |
+
|
| 817 |
+
# Per-product profit (circular model)
|
| 818 |
+
circ_cost = new_unit_cost + (max_cycles - 1) * refurb_unit_cost
|
| 819 |
+
circ_profit = test_total_revenue - circ_cost
|
| 820 |
+
profit_margin = (circ_profit / test_total_revenue * 100) if test_total_revenue > 0 else 0
|
| 821 |
+
|
| 822 |
+
# Customer perspective: lease vs buy
|
| 823 |
+
customer_total_cost = test_revenue # What customer pays for one lease term
|
| 824 |
+
vs_retail = retail_price - customer_total_cost if retail_price > 0 else 0
|
| 825 |
+
|
| 826 |
+
pricing_data.append({
|
| 827 |
+
"Monthly Price": test_price,
|
| 828 |
+
"Revenue per Cycle": test_revenue,
|
| 829 |
+
"Total Revenue (all cycles)": test_total_revenue,
|
| 830 |
+
"Profit per Product": circ_profit,
|
| 831 |
+
"Profit Margin %": profit_margin,
|
| 832 |
+
"Customer Saves vs Buy": vs_retail,
|
| 833 |
+
})
|
| 834 |
+
|
| 835 |
+
pricing_df = pd.DataFrame(pricing_data)
|
| 836 |
+
|
| 837 |
+
price_col1, price_col2 = st.columns(2)
|
| 838 |
+
|
| 839 |
+
with price_col1:
|
| 840 |
+
st.subheader("Profit vs Monthly Price")
|
| 841 |
+
|
| 842 |
+
fig_price_profit = go.Figure()
|
| 843 |
+
|
| 844 |
+
# Profit line
|
| 845 |
+
fig_price_profit.add_trace(go.Scatter(
|
| 846 |
+
x=pricing_df["Monthly Price"],
|
| 847 |
+
y=pricing_df["Profit per Product"],
|
| 848 |
+
mode="lines+markers",
|
| 849 |
+
name="Profit per Product",
|
| 850 |
+
line=dict(color="#2ecc71", width=3),
|
| 851 |
+
marker=dict(size=10),
|
| 852 |
+
hovertemplate="€%{x}/mo → €%{y:,.0f} profit<extra></extra>"
|
| 853 |
+
))
|
| 854 |
+
|
| 855 |
+
# Mark current price
|
| 856 |
+
current_profit = new_unit_cost + (max_cycles - 1) * refurb_unit_cost
|
| 857 |
+
current_profit = (lease_price * lease_term * max_cycles) - current_profit
|
| 858 |
+
|
| 859 |
+
fig_price_profit.add_trace(go.Scatter(
|
| 860 |
+
x=[lease_price],
|
| 861 |
+
y=[current_profit],
|
| 862 |
+
mode="markers",
|
| 863 |
+
name=f"Current (€{lease_price}/mo)",
|
| 864 |
+
marker=dict(size=16, color="#e74c3c", symbol="star"),
|
| 865 |
+
))
|
| 866 |
+
|
| 867 |
+
# Break-even line
|
| 868 |
+
break_even_price = circ_cost / (lease_term * max_cycles)
|
| 869 |
+
fig_price_profit.add_hline(
|
| 870 |
+
y=0, line_dash="dash", line_color="gray",
|
| 871 |
+
annotation_text="Break-even"
|
| 872 |
+
)
|
| 873 |
+
|
| 874 |
+
fig_price_profit.update_layout(
|
| 875 |
+
xaxis_title="Monthly Lease Price (EUR)",
|
| 876 |
+
yaxis_title="Profit per Product Lifecycle (EUR)",
|
| 877 |
+
hovermode="x unified",
|
| 878 |
+
)
|
| 879 |
+
|
| 880 |
+
st.plotly_chart(fig_price_profit, width="stretch")
|
| 881 |
+
|
| 882 |
+
st.markdown(f"**Break-even price:** €{break_even_price:.0f}/month (over {max_cycles} cycles)")
|
| 883 |
+
|
| 884 |
+
with price_col2:
|
| 885 |
+
st.subheader("Customer Value vs Retail")
|
| 886 |
+
|
| 887 |
+
fig_customer = go.Figure()
|
| 888 |
+
|
| 889 |
+
# What customer pays (bar)
|
| 890 |
+
fig_customer.add_trace(go.Bar(
|
| 891 |
+
x=pricing_df["Monthly Price"],
|
| 892 |
+
y=pricing_df["Revenue per Cycle"],
|
| 893 |
+
name="Lease Cost (1 term)",
|
| 894 |
+
marker_color="#3498db",
|
| 895 |
+
hovertemplate="€%{x}/mo × {lease_term}mo = €%{y}<extra></extra>"
|
| 896 |
+
))
|
| 897 |
+
|
| 898 |
+
# Retail price reference line
|
| 899 |
+
if retail_price > 0:
|
| 900 |
+
fig_customer.add_hline(
|
| 901 |
+
y=retail_price, line_dash="dash", line_color="#e74c3c", line_width=2,
|
| 902 |
+
annotation_text=f"Retail: €{retail_price}",
|
| 903 |
+
annotation_position="right"
|
| 904 |
+
)
|
| 905 |
+
|
| 906 |
+
# Mark current price
|
| 907 |
+
fig_customer.add_trace(go.Scatter(
|
| 908 |
+
x=[lease_price],
|
| 909 |
+
y=[lease_price * lease_term],
|
| 910 |
+
mode="markers",
|
| 911 |
+
name=f"Current (€{lease_price}/mo)",
|
| 912 |
+
marker=dict(size=14, color="#e74c3c", symbol="star"),
|
| 913 |
+
))
|
| 914 |
+
|
| 915 |
+
fig_customer.update_layout(
|
| 916 |
+
xaxis_title="Monthly Lease Price (EUR)",
|
| 917 |
+
yaxis_title="Total Customer Cost (EUR)",
|
| 918 |
+
hovermode="x unified",
|
| 919 |
+
)
|
| 920 |
+
|
| 921 |
+
st.plotly_chart(fig_customer, width="stretch")
|
| 922 |
+
|
| 923 |
+
if retail_price > 0:
|
| 924 |
+
current_total = lease_price * lease_term
|
| 925 |
+
if current_total < retail_price:
|
| 926 |
+
st.success(f"At €{lease_price}/mo, customers **save €{retail_price - current_total}** vs buying ({(retail_price - current_total)/retail_price*100:.0f}% less)")
|
| 927 |
+
else:
|
| 928 |
+
st.warning(f"At €{lease_price}/mo, customers pay **€{current_total - retail_price} more** than retail (premium for flexibility)")
|
| 929 |
+
|
| 930 |
# ============================================================
|
| 931 |
# FOOTER
|
| 932 |
# ============================================================
|