""" LiiS Circular Economy Simulator Core calculations for CTI metrics and P&L projections. """ from dataclasses import dataclass from pathlib import Path import yaml @dataclass class CTIMetrics: """Circular Transition Indicator metrics.""" eolrr: float # End-of-Life Recovery Rate (0-1) pri: float # Primary Resource Input (0-1, lower is better) lifetime_extension: float # Ratio of actual to design lifetime units_recovered: int # Absolute units recovered per cycle blended_cost: float # EUR per unit (new + refurb weighted) @dataclass class PLProjection: """Profit & Loss projection.""" revenue_per_unit: float cost_per_unit: float margin_per_unit: float margin_percent: float break_even_price: float def load_product(name: str) -> dict: """Load product configuration from YAML.""" config_path = Path(__file__).parent.parent / "products" / name / "config.yaml" if not config_path.exists(): raise FileNotFoundError(f"Product config not found: {config_path}") return yaml.safe_load(config_path.read_text()) def calculate_cti(config: dict, distributed_units: int = 200) -> CTIMetrics: """ Calculate CTI metrics for a product configuration. Args: config: Product configuration dict (from YAML) distributed_units: Number of units distributed in pilot Returns: CTIMetrics with calculated values """ returns = config["return_assumptions"] design = config["design"] costs = config["costs"] # EoLRR = Return Rate × (Grade A + Grade B percentage) return_rate = returns["return_rate"] grade_a = returns["grade_distribution"]["a"] grade_b = returns["grade_distribution"]["b"] eolrr = return_rate * (grade_a + grade_b) # Units recovered units_recovered = int(distributed_units * eolrr) # PRI for Year 2 steady state (simplified) # Assumes monthly demand of distributed_units / 12 monthly_demand = distributed_units / 12 months_until_return = returns["months_until_return"] refurb_available = units_recovered / (months_until_return / 12) # New units needed = demand - refurb available new_units_monthly = max(0, monthly_demand - refurb_available) total_monthly = monthly_demand pri = new_units_monthly / total_monthly if total_monthly > 0 else 1.0 # Lifetime Extension cycles = design["max_cycles"] use_per_cycle = design["use_cycle_years"] design_lifetime = design["lifetime_years"] lifetime_extension = (cycles * use_per_cycle) / design_lifetime # Blended cost mfg_cost = costs["manufacturing_unit_cost"] avg_refurb_cost = ( costs["refurbishment"]["grade_a"] * grade_a + costs["refurbishment"]["grade_b"] * grade_b ) / (grade_a + grade_b) blended_cost = ( new_units_monthly * mfg_cost + refurb_available * avg_refurb_cost ) / total_monthly return CTIMetrics( eolrr=round(eolrr, 3), pri=round(pri, 3), lifetime_extension=round(lifetime_extension, 2), units_recovered=units_recovered, blended_cost=round(blended_cost, 2), ) def calculate_pl(config: dict, cti: CTIMetrics) -> PLProjection: """ Calculate P&L projection based on CTI metrics. Args: config: Product configuration dict cti: Calculated CTI metrics Returns: PLProjection with financial metrics """ pricing = config["pricing"] costs = config["costs"] # Revenue per unit over lease term monthly_price = pricing["lease_monthly"] lease_months = pricing["lease_term_months"] revenue_per_unit = monthly_price * lease_months # Cost per unit (blended manufacturing + logistics) logistics_cost = costs["logistics"]["delivery"] + costs["logistics"]["return"] cost_per_unit = cti.blended_cost + logistics_cost # Margin margin_per_unit = revenue_per_unit - cost_per_unit margin_percent = (margin_per_unit / revenue_per_unit) * 100 if revenue_per_unit > 0 else 0 # Break-even monthly price break_even_price = cost_per_unit / lease_months return PLProjection( revenue_per_unit=round(revenue_per_unit, 2), cost_per_unit=round(cost_per_unit, 2), margin_per_unit=round(margin_per_unit, 2), margin_percent=round(margin_percent, 1), break_even_price=round(break_even_price, 2), ) def format_report(config: dict, cti: CTIMetrics, pl: PLProjection) -> str: """Generate a markdown report for a product.""" name = config["product"]["name"] report = f"""# CTI Analysis: {name} ## Summary | Metric | Value | Target | |--------|-------|--------| | EoLRR | {cti.eolrr:.1%} | {config['cti_parameters']['target_eolrr']:.0%} | | PRI (Year 2) | {cti.pri:.1%} | {config['cti_parameters']['target_pri_year2']:.0%} | | Lifetime Extension | {cti.lifetime_extension:.1f}x | 1.0x | ## P&L Projection | Metric | Value | |--------|-------| | Revenue per unit | EUR {pl.revenue_per_unit:.2f} | | Cost per unit (blended) | EUR {pl.cost_per_unit:.2f} | | Margin per unit | EUR {pl.margin_per_unit:.2f} | | Margin % | {pl.margin_percent:.1f}% | | Break-even price | EUR {pl.break_even_price:.2f}/month | ## Configuration - **Lease price**: EUR {config['pricing']['lease_monthly']}/month - **Lease term**: {config['pricing']['lease_term_months']} months - **Manufacturing cost**: EUR {config['costs']['manufacturing_unit_cost']} - **Return rate**: {config['return_assumptions']['return_rate']:.0%} ## Grade Distribution | Grade | % | Refurb Cost | |-------|---|-------------| | A (reusable) | {config['return_assumptions']['grade_distribution']['a']:.0%} | EUR {config['costs']['refurbishment']['grade_a']} | | B (refurbish) | {config['return_assumptions']['grade_distribution']['b']:.0%} | EUR {config['costs']['refurbishment']['grade_b']} | | C (scrap) | {config['return_assumptions']['grade_distribution']['c']:.0%} | EUR {config['costs']['refurbishment']['grade_c']} | """ return report if __name__ == "__main__": # Example usage for product_name in ["besafe", "hamax"]: print(f"\n{'='*50}") print(f"Analyzing: {product_name}") print("=" * 50) config = load_product(product_name) cti = calculate_cti(config) pl = calculate_pl(config, cti) print(f"EoLRR: {cti.eolrr:.1%}") print(f"PRI: {cti.pri:.1%}") print(f"Lifetime Extension: {cti.lifetime_extension:.1f}x") print(f"Blended Cost: EUR {cti.blended_cost:.2f}") print(f"Margin: {pl.margin_percent:.1f}%")