""" LiiS Circular Economy Simulator - C-Level View (Draft) Simplified executive dashboard focused on portfolio-level economics. Run with: streamlit run strategy_v2.py """ import streamlit as st import plotly.graph_objects as go import pandas as pd import plotly.io as pio # โ”€โ”€ LiiS Brand Palette โ”€โ”€ COLORS = { "bg": "#f9fcf3", "surface": "#ffffff", "surface_alt": "#eef5e6", "primary": "#124b5f", "secondary": "#785964", "text": "#1a1a2e", "text_muted": "#4a5568", "text_subtle": "#718096", "border": "#d1ddc8", "positive": "#124b5f", "negative": "#a0444a", "warning": "#c87941", "info": "#4a7c9b", } CHART_COLORS = ["#124b5f", "#785964", "#2a7f62", "#c87941", "#4a7c9b", "#a0444a"] LIIS_LAYOUT = dict( paper_bgcolor=COLORS["bg"], plot_bgcolor=COLORS["surface"], font=dict(family="Futura, Century Gothic, Arial, sans-serif", color=COLORS["text"]), xaxis=dict(gridcolor="#e2e8f0", zerolinecolor=COLORS["border"]), yaxis=dict(gridcolor="#e2e8f0", zerolinecolor=COLORS["border"]), colorway=CHART_COLORS, legend=dict(orientation="h", yanchor="bottom", y=1.02), hovermode="x unified", ) liis_template = pio.templates["plotly"] liis_template.layout.update(LIIS_LAYOUT) pio.templates["liis"] = liis_template pio.templates.default = "liis" # Page config st.set_page_config( page_title="LiiS Strategy", page_icon="๐Ÿ’ผ", layout="wide", ) # Custom CSS โ€” LiiS brand st.markdown(f""" """, unsafe_allow_html=True) # Header st.markdown("""

LiiS Strategy Dashboard

C-Level View โ€” Portfolio Economics & Break-Even Analysis

""", unsafe_allow_html=True) # ============================================================ # SIDEBAR - INPUT CONTROLS (4 Sliders) # ============================================================ st.sidebar.header("Portfolio Parameters") st.sidebar.caption("Adjust to see impact on profitability") portfolio_size = st.sidebar.slider( "Product Portfolio", min_value=50, max_value=1000, value=300, step=50, help="Number of units in your fleet" ) monthly_lease = st.sidebar.slider( "Monthly Lease Price", min_value=250, max_value=650, value=250, step=25, format="%d NOK", help="What customers pay per month" ) mfg_cost = st.sidebar.slider( "Manufacturing Cost", min_value=2500, max_value=7500, value=2500, step=250, format="%d NOK", help="Cost to produce one unit" ) reuse_cycles = st.sidebar.slider( "Reuse Cycles", min_value=2, max_value=10, value=3, step=1, help="How many families use each product" ) # ============================================================ # CALCULATIONS # ============================================================ # Per-product economics lease_term_months = 12 # Each cycle is 12 months revenue_per_product = monthly_lease * lease_term_months * reuse_cycles # Costs per product (simplified model) logistics_per_cycle = 150 # NOK - pickup + delivery per cycle repair_per_cycle = 200 # NOK - average repair/refurb per cycle total_cost_per_product = mfg_cost + (logistics_per_cycle + repair_per_cycle) * reuse_cycles blended_cost_per_product = total_cost_per_product / reuse_cycles # Profit per product profit_per_product = revenue_per_product - total_cost_per_product operating_margin = (profit_per_product / revenue_per_product) * 100 if revenue_per_product > 0 else 0 # Portfolio level revenue_portfolio = portfolio_size * revenue_per_product cost_portfolio = portfolio_size * total_cost_per_product blended_cost_portfolio = portfolio_size * blended_cost_per_product profit_portfolio = portfolio_size * profit_per_product # Break-even calculation (months) monthly_revenue_portfolio = portfolio_size * monthly_lease monthly_cost_portfolio = portfolio_size * (repair_per_cycle / lease_term_months) # Monthly degradation initial_investment = portfolio_size * mfg_cost # Cumulative profit over time to find break-even breakeven_month = None cumulative = -initial_investment for month in range(1, 121): # Add logistics cost at cycle transitions if month % lease_term_months == 0: cumulative -= portfolio_size * logistics_per_cycle cumulative += monthly_revenue_portfolio - monthly_cost_portfolio if cumulative >= 0 and breakeven_month is None: breakeven_month = month break if breakeven_month is None: breakeven_month = "> 120" # Linear model comparison linear_margin = 0.20 # 20% typical retail margin linear_profit_per_sale = mfg_cost * linear_margin / (1 - linear_margin) # Selling at cost + margin linear_revenue_per_product = mfg_cost / (1 - linear_margin) linear_breakeven_units = int(initial_investment / linear_profit_per_sale) if linear_profit_per_sale > 0 else 999 # Time to sell that many units (assume 1 sale per unit in portfolio per cycle) linear_breakeven_months = max(24, int(linear_breakeven_units / portfolio_size * lease_term_months)) # ============================================================ # TIER 1: PRIMARY METRICS # ============================================================ st.markdown('

Tier 1 โ€” Primary Metrics

', unsafe_allow_html=True) hero_col1, hero_col2 = st.columns(2) with hero_col1: st.markdown(f"""

BREAK EVEN

{breakeven_month} MONTHS

For entire portfolio of {portfolio_size:,} units

""", unsafe_allow_html=True) with hero_col2: profit_color = COLORS["positive"] if profit_portfolio > 0 else COLORS["negative"] st.markdown(f"""

TOTAL PORTFOLIO PROFIT

{profit_portfolio:,.0f} NOK

Net revenue across {portfolio_size:,} units over {reuse_cycles} cycles

""", unsafe_allow_html=True) st.markdown("---") # ============================================================ # TIER 2: SUPPORTING METRICS # ============================================================ st.markdown('

Tier 2 โ€” Unit Economics

', unsafe_allow_html=True) metric_col1, metric_col2, metric_col3 = st.columns(3) with metric_col1: st.metric( "Revenue per Product", f"{revenue_per_product:,.0f} NOK", delta=f"{monthly_lease} ร— {lease_term_months}mo ร— {reuse_cycles} cycles", help="Total revenue generated by one product over its lifetime" ) st.metric( "Revenue per Portfolio", f"{revenue_portfolio:,.0f} NOK", delta=f"{portfolio_size:,} units", help="Total portfolio revenue" ) with metric_col2: st.metric( "Blended Cost per Product", f"{blended_cost_per_product:,.0f} NOK", delta=f"Total {total_cost_per_product:,.0f} รท {reuse_cycles} cycles", help="Average cost per cycle (manufacturing + logistics + repair)" ) st.metric( "Blended Cost per Portfolio", f"{blended_cost_portfolio:,.0f} NOK", delta=f"{portfolio_size:,} units", help="Total portfolio blended cost" ) with metric_col3: margin_color = "normal" if operating_margin >= 50 else "off" st.metric( "Operating Margin", f"{operating_margin:.0f}%", delta="healthy" if operating_margin >= 50 else "review pricing", delta_color=margin_color, help="Profit / Revenue - same for all units" ) st.metric( "Profit per Product", f"{profit_per_product:,.0f} NOK", delta="lifetime profit", help="Revenue - Total Cost per product" ) st.markdown("---") # ============================================================ # CHART 1: Break Even Comparison # ============================================================ st.markdown('

Tier 2 โ€” Model Comparison

', unsafe_allow_html=True) st.subheader("Break Even: Circular vs Linear Model") comp_col1, comp_col2 = st.columns(2) with comp_col1: st.markdown(f"""

LINEAR MODEL

{linear_breakeven_months} MONTHS

{linear_margin*100:.0f}% margin

Sell once, done

""", unsafe_allow_html=True) with comp_col2: st.markdown(f"""

CIRCULAR MODEL

{breakeven_month} MONTHS

{operating_margin:.0f}% margin

Reuse {reuse_cycles}ร— per product

""", unsafe_allow_html=True) st.markdown("---") # ============================================================ # CHART 2: Blended Cost vs Revenue Across Cycles # ============================================================ st.subheader("Cost & Revenue Trajectory by Reuse Cycles") cycles_range = list(range(2, 11)) chart_data = [] for c in cycles_range: rev = monthly_lease * lease_term_months * c total_cost = mfg_cost + (logistics_per_cycle + repair_per_cycle) * c blended = total_cost / c profit = rev - total_cost margin = (profit / rev) * 100 if rev > 0 else 0 chart_data.append({ "Cycles": c, "Revenue": rev, "Total Cost": total_cost, "Blended Cost": blended, "Profit": profit, "Margin": margin, }) chart_df = pd.DataFrame(chart_data) fig_cycles = go.Figure() # Revenue line (constant per cycle ร— cycles) fig_cycles.add_trace(go.Scatter( x=chart_df["Cycles"], y=chart_df["Revenue"], mode="lines+markers", name="Revenue per Product", line=dict(color=COLORS["positive"], width=3), marker=dict(size=8), )) # Blended cost line (decreasing) fig_cycles.add_trace(go.Scatter( x=chart_df["Cycles"], y=chart_df["Blended Cost"], mode="lines+markers", name="Blended Cost per Product", line=dict(color=COLORS["negative"], width=3), marker=dict(size=8), )) # Total cost line fig_cycles.add_trace(go.Scatter( x=chart_df["Cycles"], y=chart_df["Total Cost"], mode="lines+markers", name="Total Cost per Product", line=dict(color=COLORS["negative"], width=2, dash="dot"), marker=dict(size=6), opacity=0.6, )) # Mark current cycles fig_cycles.add_vline( x=reuse_cycles, line_dash="dash", line_color=COLORS["warning"], annotation_text=f"Current: {reuse_cycles} cycles", annotation_position="top" ) fig_cycles.update_layout( xaxis_title="Reuse Cycles", yaxis_title="NOK per Product", legend=dict(orientation="h", yanchor="bottom", y=1.02), hovermode="x unified", ) st.plotly_chart(fig_cycles, use_container_width=True) st.info(f"**Key insight:** More cycles = lower blended cost = higher profit. At {reuse_cycles} cycles, blended cost is {blended_cost_per_product:,.0f} NOK vs {mfg_cost:,} NOK manufacturing cost.") st.markdown("---") # ============================================================ # TIER 3: AT SCALE # ============================================================ st.markdown('

Tier 3 โ€” Scale Understanding

', unsafe_allow_html=True) st.subheader("At Scale: Customer Base Projection") scale_col1, scale_col2 = st.columns([1, 2]) with scale_col1: customer_base = st.slider( "Customer Base", min_value=500, max_value=10000, value=1000, step=100, help="Target number of active customers" ) utilization_rate = 0.80 # 80% of fleet in use at any time fleet_needed = int(customer_base / utilization_rate) annual_revenue_scale = customer_base * monthly_lease * lease_term_months total_cost_scale = fleet_needed * blended_cost_per_product annual_profit_scale = annual_revenue_scale - total_cost_scale st.metric("Fleet Size Needed", f"{fleet_needed:,} units", delta=f"{utilization_rate*100:.0f}% utilization") st.metric("Annual Revenue", f"{annual_revenue_scale:,.0f} NOK", delta=f"{customer_base:,} customers") st.metric("Total Blended Cost", f"{total_cost_scale:,.0f} NOK", delta=f"{fleet_needed:,} units") profit_delta = "profit" if annual_profit_scale > 0 else "loss" st.metric("Annual Profit", f"{annual_profit_scale:,.0f} NOK", delta=profit_delta) with scale_col2: # Chart 3: 5-Year Cumulative Profit st.markdown("**5-Year Cumulative Profit Trajectory**") # Calculate with 10% utilization decline per cycle months = list(range(0, 61)) cumulative_profit = [] cumulative = -fleet_needed * mfg_cost # Initial investment current_utilization = utilization_rate for month in months: if month == 0: cumulative_profit.append(cumulative) continue # Decline utilization at each cycle end if month > 0 and month % lease_term_months == 0: current_utilization = max(0.5, current_utilization - 0.10) cumulative -= fleet_needed * logistics_per_cycle # Logistics at cycle transition active_customers = int(fleet_needed * current_utilization) monthly_rev = active_customers * monthly_lease monthly_cost = active_customers * (repair_per_cycle / lease_term_months) cumulative += monthly_rev - monthly_cost cumulative_profit.append(cumulative) fig_trajectory = go.Figure() fig_trajectory.add_trace(go.Scatter( x=months, y=cumulative_profit, mode="lines", name="Cumulative Profit", line=dict(color=COLORS["positive"], width=3), fill="tozeroy", fillcolor="rgba(18, 75, 95, 0.15)", )) # Add zero line fig_trajectory.add_hline(y=0, line_color=COLORS["border"], line_width=1) # Mark cycle transitions for cycle_month in range(lease_term_months, 61, lease_term_months): fig_trajectory.add_vline( x=cycle_month, line_dash="dot", line_color=COLORS["border"], ) # Find and mark breakeven for i, cp in enumerate(cumulative_profit): if cp >= 0 and i > 0: fig_trajectory.add_trace(go.Scatter( x=[i], y=[cp], mode="markers+text", name="Breakeven", marker=dict(size=12, color=COLORS["warning"], symbol="star"), text=["Breakeven"], textposition="top center", )) break fig_trajectory.update_layout( xaxis_title="Months", yaxis_title="Cumulative Profit (NOK)", showlegend=False, yaxis=dict(tickformat=","), ) st.plotly_chart(fig_trajectory, use_container_width=True) final_profit = cumulative_profit[-1] st.caption(f"5-year projection with {fleet_needed:,} unit fleet. Final cumulative profit: **{final_profit:,.0f} NOK**") # ============================================================ # FOOTER # ============================================================ st.markdown("---") st.caption(f"Portfolio: {portfolio_size:,} units | Lease: {monthly_lease} NOK/mo | Mfg: {mfg_cost:,} NOK | Cycles: {reuse_cycles}")