generated from coulomb/repo-seed
Introduce ui/ dashboard (dark observatory layout), JSON API, and local dev server. All metrics load from expense and payment record ledgers. Links Claude design reference for visual alignment.
98 lines
3.4 KiB
Python
98 lines
3.4 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_membership,
|
|
load_monthly_ledger,
|
|
load_payment_records,
|
|
load_pricing_models,
|
|
load_product,
|
|
)
|
|
|
|
|
|
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,
|
|
}
|
|
)
|
|
|
|
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),
|
|
"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) |