generated from coulomb/repo-seed
Add liquidity tracking, budget, and platform cost history
Restore operator platform costs (Bubble, domains, Stripe) with monthly history from March 2025 and member payments from November 2025. Track €1,000 starting budget, cumulative burn, and remaining liquidity in the economics dashboard. Document LQ requirements in REQUIREMENTS.md.
This commit is contained in:
@@ -1,49 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
from .economics import build_snapshot
|
||||
from .economics import build_liquidity_summary, build_snapshot
|
||||
from .load import (
|
||||
default_data_dir,
|
||||
load_costs,
|
||||
latest_period,
|
||||
load_budget,
|
||||
load_membership,
|
||||
load_monthly_platform_costs,
|
||||
load_pricing_models,
|
||||
load_product,
|
||||
load_revenue,
|
||||
)
|
||||
from .models import EconomicsSnapshot, PricingModel, Product
|
||||
from .models import EconomicsSnapshot, LiquiditySummary, MonthlyPlatformCost, PricingModel, Product, RevenueEntry
|
||||
|
||||
|
||||
def _history_rows(
|
||||
monthly_costs: list[MonthlyPlatformCost],
|
||||
revenue_entries: list[RevenueEntry],
|
||||
through_period: str,
|
||||
) -> str:
|
||||
revenue_by_period = {entry.period: entry for entry in revenue_entries}
|
||||
lines: list[str] = []
|
||||
for month in sorted(monthly_costs, key=lambda item: item.period):
|
||||
if month.period > through_period:
|
||||
continue
|
||||
payment = revenue_by_period.get(month.period)
|
||||
net_payment = payment.net_amount if payment else Decimal("0")
|
||||
period_net = net_payment - month.platform_cost
|
||||
lines.append(
|
||||
f"| {month.period} | {month.active_members} | {month.gross_revenue} | "
|
||||
f"{month.platform_cost} | {period_net:.2f} |"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_dashboard(
|
||||
product: Product,
|
||||
models: list[PricingModel],
|
||||
snapshot: EconomicsSnapshot,
|
||||
liquidity: LiquiditySummary,
|
||||
monthly_costs: list[MonthlyPlatformCost],
|
||||
revenue_entries: list[RevenueEntry],
|
||||
) -> str:
|
||||
active = next(m for m in models if m.id == product.active_pricing_model_id)
|
||||
registry_lines = "\n".join(
|
||||
f"| {model.id} | {model.name} | {model.model_type} | {model.status} |"
|
||||
for model in models
|
||||
)
|
||||
history_lines = _history_rows(monthly_costs, revenue_entries, snapshot.period)
|
||||
budget_state = (
|
||||
"over budget"
|
||||
if liquidity.remaining_budget < Decimal("0")
|
||||
else "within budget"
|
||||
)
|
||||
|
||||
return f"""# Economics Dashboard v1 — {product.name}
|
||||
|
||||
**Period:** {snapshot.period}
|
||||
**Lifecycle phase:** {product.lifecycle_phase}
|
||||
**Active pricing model:** {active.name} ({active.access_fee_amount} {active.currency}/{active.access_fee_cadence})
|
||||
|
||||
## Key Metrics
|
||||
> Platform costs accrue to the operator. Customer cost-pass-through billing is
|
||||
> **not active** in MVP — members pay subscription only.
|
||||
|
||||
## Key Metrics (current period)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|------:|
|
||||
| Active members | {snapshot.active_members} |
|
||||
| Monthly revenue | {snapshot.monthly_revenue} {snapshot.currency} |
|
||||
| Monthly cost | {snapshot.monthly_cost} {snapshot.currency} |
|
||||
| Cost per member | {snapshot.cost_per_member} {snapshot.currency} |
|
||||
| Gross margin | {snapshot.gross_margin} {snapshot.currency} |
|
||||
| Gross margin % | {snapshot.gross_margin_pct}% |
|
||||
| Member payments (gross) | {snapshot.monthly_revenue} {snapshot.currency} |
|
||||
| Platform cost | {snapshot.monthly_platform_cost} {snapshot.currency} |
|
||||
| Platform cost per member | {snapshot.cost_per_member} {snapshot.currency} |
|
||||
| Period gross margin | {snapshot.gross_margin} {snapshot.currency} |
|
||||
| Period gross margin % | {snapshot.gross_margin_pct}% |
|
||||
| Period net liquidity | {snapshot.period_net_liquidity} {snapshot.currency} ({snapshot.liquidity_status}) |
|
||||
|
||||
_Revenue source: {snapshot.revenue_source}_
|
||||
|
||||
## Liquidity & Budget (through {liquidity.through_period})
|
||||
|
||||
| Metric | Value |
|
||||
|--------|------:|
|
||||
| Initial budget | {liquidity.initial_budget} {liquidity.currency} |
|
||||
| Cumulative member payments (net) | {liquidity.cumulative_member_payments} {liquidity.currency} |
|
||||
| Cumulative platform cost | {liquidity.cumulative_platform_cost} {liquidity.currency} |
|
||||
| Cumulative net liquidity | {liquidity.cumulative_net_liquidity} {liquidity.currency} ({liquidity.liquidity_status}) |
|
||||
| Remaining budget | {liquidity.remaining_budget} {liquidity.currency} ({budget_state}) |
|
||||
| Months tracked | {liquidity.months_tracked} |
|
||||
|
||||
## Monthly History
|
||||
|
||||
| Period | Members | Gross revenue | Platform cost | Net liquidity |
|
||||
|--------|--------:|--------------:|--------------:|--------------:|
|
||||
{history_lines}
|
||||
|
||||
## Pricing Model Registry
|
||||
|
||||
| ID | Name | Type | Status |
|
||||
@@ -53,32 +107,28 @@ _Revenue source: {snapshot.revenue_source}_
|
||||
## Registries Loaded
|
||||
|
||||
- Product model (`data/product.json`)
|
||||
- Budget (`data/budget.json`)
|
||||
- Pricing model registry (`data/pricing-models.json`)
|
||||
- Cost registry (`data/costs.json`)
|
||||
- Revenue registry (`data/revenue.json`)
|
||||
- Membership registry (`data/membership.json`)
|
||||
- Platform costs (`data/costs.json`)
|
||||
- Member payments (`data/revenue.json`)
|
||||
- Membership (`data/membership.json`)
|
||||
- Requirements (`REQUIREMENTS.md`)
|
||||
"""
|
||||
|
||||
|
||||
def generate_dashboard(data_dir: Path | None = None, period: str | None = None) -> str:
|
||||
root = data_dir or default_data_dir()
|
||||
product = load_product(root)
|
||||
budget = load_budget(root)
|
||||
models = load_pricing_models(root)
|
||||
members = load_membership(root)
|
||||
revenue = load_revenue(root)
|
||||
cost_period, costs, fx_rates = load_costs(root)
|
||||
target_period = period or cost_period
|
||||
monthly_costs = load_monthly_platform_costs(root)
|
||||
target_period = period or latest_period(monthly_costs)
|
||||
|
||||
snapshot = build_snapshot(
|
||||
target_period,
|
||||
product,
|
||||
models,
|
||||
members,
|
||||
revenue,
|
||||
costs,
|
||||
fx_rates,
|
||||
)
|
||||
return render_dashboard(product, models, snapshot)
|
||||
snapshot = build_snapshot(target_period, product, models, members, revenue, monthly_costs)
|
||||
liquidity = build_liquidity_summary(budget, revenue, monthly_costs, target_period)
|
||||
return render_dashboard(product, models, snapshot, liquidity, monthly_costs, revenue)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
Reference in New Issue
Block a user