Implement ADAPTIVE-WP-0002 Sprint 1 economic foundations
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.
This commit is contained in:
parent
d648a3263d
commit
a1a4aa972f
17 changed files with 709 additions and 19 deletions
103
projects/coulomb-pricing/observatory/load.py
Normal file
103
projects/coulomb-pricing/observatory/load.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
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"]
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue