from __future__ import annotations from decimal import Decimal, ROUND_HALF_UP from .models import EconomicsSnapshot, PricingModel TWOPLACES = Decimal("0.01") OVERAGE_RATE = Decimal("0.002") # EUR per token above allowance (observatory estimate) def _money(value: Decimal) -> Decimal: return value.quantize(TWOPLACES, rounding=ROUND_HALF_UP) def _simulate_model( model: PricingModel, snapshot: EconomicsSnapshot, ai_cost_per_member: Decimal, included_tokens: int = 100_000, actual_tokens: int = 120_000, ) -> dict: members = snapshot.active_members or 1 subscription_revenue = model.access_fee_amount * members overage_revenue = Decimal("0") if model.model_type == "hybrid_subscription_usage" and actual_tokens > included_tokens: overage_tokens = actual_tokens - included_tokens overage_revenue = OVERAGE_RATE * overage_tokens * members gross_revenue = subscription_revenue + overage_revenue platform_cost = snapshot.monthly_total_platform_cost + (ai_cost_per_member * members) margin = gross_revenue - platform_cost margin_pct = (margin / gross_revenue * Decimal("100")) if gross_revenue else Decimal("0") return { "model_id": model.id, "model_name": model.name, "model_type": model.model_type, "status": model.status, "access_fee_eur": model.access_fee_amount, "projected_revenue_eur": _money(gross_revenue), "projected_overage_eur": _money(overage_revenue), "projected_platform_cost_eur": _money(platform_cost), "projected_margin_eur": _money(margin), "projected_margin_pct": _money(margin_pct), "assumed_tokens_per_member": actual_tokens, "included_tokens": included_tokens if model.model_type != "flat_subscription" else None, } def build_pricing_simulations( snapshot: EconomicsSnapshot, models: list[PricingModel], ai_cost_per_member: Decimal, ) -> dict: scenarios = [ _simulate_model(model, snapshot, ai_cost_per_member) for model in models if model.status in ("active", "candidate") ] active = next((item for item in scenarios if item["status"] == "active"), scenarios[0]) best_margin = max(scenarios, key=lambda item: item["projected_margin_eur"]) return { "period": snapshot.period, "currency": snapshot.currency, "active_scenario_id": active["model_id"], "best_margin_scenario_id": best_margin["model_id"], "scenarios": scenarios, "notes": "Projections hold member count and infrastructure cost constant; overage uses observatory token estimate.", }