Implement comparable LTV engine and close WP-0005

This commit is contained in:
codex
2026-07-02 22:50:16 +02:00
parent 656bbb81a5
commit 386c8a46fe
13 changed files with 1060 additions and 68 deletions

View File

@@ -1,70 +1,64 @@
from __future__ import annotations
from decimal import Decimal, ROUND_HALF_UP
from decimal import Decimal
from typing import Any
from .ltv import build_ltv_simulations
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")
def _fallback_catalog(models: list[PricingModel]) -> dict[str, Any]:
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,
"version": 1,
"currency": "EUR",
"horizon_months": 24,
"monthly_discount_rate_pct": "1.0",
"required_improvement_factor": "1.05",
"profiles": [
{
"id": "observatory-default",
"name": "Observatory default",
"segment": "coulomb-social-members",
"eligible_model_ids": [model.id for model in models if model.status in ("active", "candidate")],
"members_per_customer": 1,
"expected_monthly_usage_units": "120000",
"usage_variance_pct": "25",
"monthly_churn_pct": "5.0",
"monthly_default_pct": "1.0",
"monthly_support_cost": "0.00",
"monthly_risk_cost": "0.00",
"acquisition_cost": "0.00",
"upfront_investment_cost": "0.00",
"notes": "Fallback scenario when no explicit LTV scenario catalog is provided."
}
],
"notes": "Fallback scenario catalog generated inside observatory.simulator.",
}
def _fallback_usage_records(snapshot: EconomicsSnapshot, ai_cost_per_member: Decimal) -> list[dict[str, Any]]:
return [
{
"id": "fallback-usage",
"period": snapshot.period,
"member_id": "fallback",
"tokens": 120000,
"cost_eur": ai_cost_per_member,
"source": "fallback",
}
]
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.",
}
usage_records: list[dict[str, Any]] | None = None,
scenario_catalog: dict[str, Any] | None = None,
) -> dict[str, Any]:
return build_ltv_simulations(
snapshot,
models,
usage_records or _fallback_usage_records(snapshot, ai_cost_per_member),
scenario_catalog or _fallback_catalog(models),
)