generated from coulomb/repo-seed
Add file-based Bubble, Stripe, and OpenRouter importers; usage attribution, cost allocation, pricing simulator, credit wallets, and recommendations in the dashboard API. Document whynot-design UI workflow and archive the finished workplan with all ten tasks marked done.
138 lines
5.3 KiB
Python
138 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .economics import build_liquidity_summary, build_snapshot
|
|
from .load import (
|
|
default_data_dir,
|
|
latest_period,
|
|
load_budget,
|
|
load_expense_records,
|
|
load_market_signals,
|
|
load_membership,
|
|
load_monthly_ledger,
|
|
load_payment_records,
|
|
load_pricing_models,
|
|
load_product,
|
|
load_value_range,
|
|
)
|
|
from .allocation import build_cost_allocation
|
|
from .credits import build_credit_summary, load_credit_wallets
|
|
from .membership_analytics import build_membership_analytics
|
|
from .pricing_context import build_cost_floor, build_market_price_view, build_value_range_view
|
|
from .recommendations import build_pricing_recommendations
|
|
from .simulator import build_pricing_simulations
|
|
from .usage import build_usage_summary, load_usage_records
|
|
|
|
|
|
def _serialize(value: Any) -> Any:
|
|
if isinstance(value, Decimal):
|
|
return str(value)
|
|
if hasattr(value, "__dataclass_fields__"):
|
|
return {key: _serialize(getattr(value, key)) for key in value.__dataclass_fields__}
|
|
if isinstance(value, list):
|
|
return [_serialize(item) for item in value]
|
|
if isinstance(value, dict):
|
|
return {key: _serialize(item) for key, item in value.items()}
|
|
return value
|
|
|
|
|
|
def _load_json_catalog(data_dir: Path, name: str) -> dict:
|
|
path = data_dir / "infrastructure" / name
|
|
if not path.exists():
|
|
return {}
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def build_dashboard_payload(data_dir: Path | None = None, period: str | None = None) -> dict:
|
|
root = data_dir or default_data_dir()
|
|
product = load_product(root)
|
|
budget = load_budget(root)
|
|
models = load_pricing_models(root)
|
|
members = load_membership(root)
|
|
payments = load_payment_records(root)
|
|
expenses = load_expense_records(root)
|
|
ledger = load_monthly_ledger(root)
|
|
target_period = period or latest_period(ledger)
|
|
|
|
snapshot = build_snapshot(target_period, product, models, members, payments, ledger)
|
|
liquidity = build_liquidity_summary(budget, payments, ledger, target_period)
|
|
payment_by_period = {record.period: record for record in payments}
|
|
|
|
history = []
|
|
for month in sorted(ledger, key=lambda row: row.period):
|
|
if month.period > target_period:
|
|
continue
|
|
payment = payment_by_period.get(month.period)
|
|
net_payment = payment.net_amount if payment else Decimal("0")
|
|
history.append(
|
|
{
|
|
"period": month.period,
|
|
"active_members": month.active_members,
|
|
"gross_revenue": month.gross_revenue,
|
|
"infrastructure_cost": month.infrastructure_cost,
|
|
"payment_processing_cost": month.payment_processing_cost,
|
|
"total_platform_cost": month.total_platform_cost,
|
|
"net_payment": net_payment,
|
|
"net_liquidity": net_payment - month.infrastructure_cost,
|
|
}
|
|
)
|
|
|
|
value_range_raw = load_value_range(root)
|
|
market_raw = load_market_signals(root)
|
|
usage_records = load_usage_records(root)
|
|
usage_summary = build_usage_summary(usage_records, target_period)
|
|
cost_floor = build_cost_floor(snapshot, models)
|
|
value_range = build_value_range_view(value_range_raw, snapshot, product, models)
|
|
market_price = build_market_price_view(market_raw)
|
|
cost_allocation = build_cost_allocation(snapshot, usage_records)
|
|
ai_cost_per_member = usage_summary["cost_per_active_user_eur"]
|
|
simulations = build_pricing_simulations(snapshot, models, ai_cost_per_member)
|
|
credit_wallets = load_credit_wallets(root)
|
|
credit_summary = build_credit_summary(
|
|
credit_wallets,
|
|
{key: value for key, value in usage_summary["by_member"].items()},
|
|
target_period,
|
|
)
|
|
recommendations = build_pricing_recommendations(
|
|
cost_floor, value_range, market_price, simulations, usage_summary
|
|
)
|
|
|
|
return _serialize(
|
|
{
|
|
"design_reference": "https://claude.ai/design/p/fb2eef8c-c1fc-4c75-bff4-3782552e5511",
|
|
"period": target_period,
|
|
"product": product,
|
|
"budget": budget,
|
|
"snapshot": snapshot,
|
|
"liquidity": liquidity,
|
|
"history": history,
|
|
"pricing_models": models,
|
|
"members": members,
|
|
"payments": payments,
|
|
"expense_record_count": len(expenses),
|
|
"membership_analytics": build_membership_analytics(
|
|
members, target_period, [row["period"] for row in history]
|
|
),
|
|
"cost_floor": cost_floor,
|
|
"value_range": value_range,
|
|
"market_price": market_price,
|
|
"usage": usage_summary,
|
|
"cost_allocation": cost_allocation,
|
|
"pricing_simulations": simulations,
|
|
"credit_wallets": credit_summary,
|
|
"recommendations": recommendations,
|
|
"infrastructure": {
|
|
"domains": _load_json_catalog(root, "domains.json"),
|
|
"virtual_servers": _load_json_catalog(root, "virtual_servers.json"),
|
|
"stripe": _load_json_catalog(root, "stripe.json"),
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
def payload_json(data_dir: Path | None = None, period: str | None = None) -> str:
|
|
return json.dumps(build_dashboard_payload(data_dir, period), indent=2) |