generated from coulomb/repo-seed
Add Coulomb observatory package with JSON registries (product, pricing models, costs, revenue, membership), economics snapshot engine, Economics Dashboard v1 CLI, sample report, and pytest coverage. Complete T01 and queue Sprint 2 Bubble.io integration.
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
from .models import (
|
|
CostEntry,
|
|
MembershipRecord,
|
|
PricingModel,
|
|
Product,
|
|
RevenueEntry,
|
|
)
|
|
|
|
|
|
def _money(value: str | int | float) -> Decimal:
|
|
return Decimal(str(value))
|
|
|
|
|
|
def _read_json(path: Path) -> dict:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def default_data_dir() -> Path:
|
|
return Path(__file__).resolve().parent.parent / "data"
|
|
|
|
|
|
def load_product(data_dir: Path | None = None) -> Product:
|
|
raw = _read_json((data_dir or default_data_dir()) / "product.json")
|
|
return Product(
|
|
id=raw["id"],
|
|
name=raw["name"],
|
|
lifecycle_phase=raw["lifecycle_phase"],
|
|
currency=raw["currency"],
|
|
description=raw["description"],
|
|
active_pricing_model_id=raw["active_pricing_model_id"],
|
|
)
|
|
|
|
|
|
def load_pricing_models(data_dir: Path | None = None) -> list[PricingModel]:
|
|
raw = _read_json((data_dir or default_data_dir()) / "pricing-models.json")
|
|
return [
|
|
PricingModel(
|
|
id=item["id"],
|
|
name=item["name"],
|
|
model_type=item["model_type"],
|
|
lifecycle_phase=item["lifecycle_phase"],
|
|
currency=item["currency"],
|
|
access_fee_amount=_money(item["access_fee_amount"]),
|
|
access_fee_cadence=item["access_fee_cadence"],
|
|
status=item["status"],
|
|
)
|
|
for item in raw["models"]
|
|
]
|
|
|
|
|
|
def load_costs(data_dir: Path | None = None) -> tuple[str, list[CostEntry], dict[str, Decimal]]:
|
|
raw = _read_json((data_dir or default_data_dir()) / "costs.json")
|
|
entries = [
|
|
CostEntry(
|
|
id=item["id"],
|
|
name=item["name"],
|
|
category=item["category"],
|
|
amount=_money(item["amount"]),
|
|
currency=item["currency"],
|
|
cadence=item["cadence"],
|
|
allocation=item["allocation"],
|
|
)
|
|
for item in raw["entries"]
|
|
]
|
|
fx = {pair: _money(rate) for pair, rate in raw.get("fx_rates", {}).items()}
|
|
return raw.get("period", ""), entries, fx
|
|
|
|
|
|
def load_revenue(data_dir: Path | None = None) -> list[RevenueEntry]:
|
|
raw = _read_json((data_dir or default_data_dir()) / "revenue.json")
|
|
return [
|
|
RevenueEntry(
|
|
id=item["id"],
|
|
period=item["period"],
|
|
gross_amount=_money(item["gross_amount"]),
|
|
fees_amount=_money(item["fees_amount"]),
|
|
refunds_amount=_money(item.get("refunds_amount", "0")),
|
|
net_amount=_money(item["net_amount"]),
|
|
currency=item["currency"],
|
|
source=item["source"],
|
|
)
|
|
for item in raw["entries"]
|
|
]
|
|
|
|
|
|
def load_membership(data_dir: Path | None = None) -> list[MembershipRecord]:
|
|
raw = _read_json((data_dir or default_data_dir()) / "membership.json")
|
|
return [
|
|
MembershipRecord(
|
|
id=item["id"],
|
|
status=item["status"],
|
|
joined_at=item["joined_at"],
|
|
plan_id=item["plan_id"],
|
|
churned_at=item.get("churned_at"),
|
|
)
|
|
for item in raw["members"]
|
|
] |