generated from coulomb/repo-seed
Complete Economic Observatory MVP (ADAPTIVE-WP-0002)
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.
This commit is contained in:
@@ -21,6 +21,10 @@ def test_dashboard_payload_contains_live_ledger_totals() -> None:
|
||||
assert payload["cost_floor"]["active_price"] == "8.99"
|
||||
assert len(payload["value_range"]["segments"]) == 2
|
||||
assert payload["market_price"]["alternative_count"] == 4
|
||||
assert payload["membership_analytics"]["active_members"] == 1
|
||||
assert payload["usage"]["record_count"] == 1
|
||||
assert len(payload["pricing_simulations"]["scenarios"]) == 3
|
||||
assert payload["recommendations"]
|
||||
|
||||
|
||||
def test_payload_json_is_valid() -> None:
|
||||
|
||||
42
projects/coulomb-pricing/tests/test_importers.py
Normal file
42
projects/coulomb-pricing/tests/test_importers.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from observatory.importers.bubble import import_membership
|
||||
from observatory.importers.openrouter import import_usage
|
||||
from observatory.importers.stripe import import_payments
|
||||
|
||||
IMPORTS = Path(__file__).resolve().parent.parent / "data" / "imports"
|
||||
|
||||
|
||||
def test_bubble_import_maps_active_member() -> None:
|
||||
import json
|
||||
|
||||
export = json.loads((IMPORTS / "bubble-export.sample.json").read_text(encoding="utf-8"))
|
||||
payload = import_membership(export)
|
||||
|
||||
assert len(payload["members"]) == 1
|
||||
assert payload["members"][0]["id"] == "member-tegwick"
|
||||
assert payload["members"][0]["status"] == "active"
|
||||
assert payload["members"][0]["source"] == "bubble"
|
||||
|
||||
|
||||
def test_stripe_import_normalises_charge() -> None:
|
||||
import json
|
||||
|
||||
export = json.loads((IMPORTS / "stripe-export.sample.json").read_text(encoding="utf-8"))
|
||||
payload = import_payments(export)
|
||||
|
||||
assert payload["records"][0]["gross_amount"] == "8.99"
|
||||
assert payload["records"][0]["net_amount"] == "8.55"
|
||||
assert payload["records"][0]["member_username"] == "tegwick"
|
||||
|
||||
|
||||
def test_openrouter_import_converts_cost_to_eur() -> None:
|
||||
import json
|
||||
|
||||
export = json.loads((IMPORTS / "openrouter-export.sample.json").read_text(encoding="utf-8"))
|
||||
payload = import_usage(export, fx_usd_eur="0.92")
|
||||
|
||||
assert payload["records"][0]["cost_eur"] == "0.06"
|
||||
assert payload["records"][0]["member_id"] == "member-tegwick"
|
||||
96
projects/coulomb-pricing/tests/test_mvp_sprints.py
Normal file
96
projects/coulomb-pricing/tests/test_mvp_sprints.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
from observatory.allocation import build_cost_allocation
|
||||
from observatory.api import build_dashboard_payload
|
||||
from observatory.credits import build_credit_summary, load_credit_wallets
|
||||
from observatory.economics import build_snapshot
|
||||
from observatory.load import (
|
||||
load_membership,
|
||||
load_monthly_ledger,
|
||||
load_payment_records,
|
||||
load_pricing_models,
|
||||
load_product,
|
||||
)
|
||||
from observatory.membership_analytics import build_membership_analytics
|
||||
from observatory.recommendations import build_pricing_recommendations
|
||||
from observatory.simulator import build_pricing_simulations
|
||||
from observatory.usage import build_usage_summary, load_usage_records
|
||||
|
||||
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
||||
|
||||
|
||||
def _snapshot(period: str = "2026-06"):
|
||||
product = load_product(DATA_DIR)
|
||||
models = load_pricing_models(DATA_DIR)
|
||||
members = load_membership(DATA_DIR)
|
||||
payments = load_payment_records(DATA_DIR)
|
||||
ledger = load_monthly_ledger(DATA_DIR)
|
||||
return build_snapshot(period, product, models, members, payments, ledger)
|
||||
|
||||
|
||||
def test_membership_analytics_counts_active_member() -> None:
|
||||
members = load_membership(DATA_DIR)
|
||||
analytics = build_membership_analytics(members, "2026-06", ["2026-05", "2026-06"])
|
||||
|
||||
assert analytics["active_members"] == 1
|
||||
assert analytics["total_members"] == 1
|
||||
|
||||
|
||||
def test_usage_summary_attributes_member_cost() -> None:
|
||||
records = load_usage_records(DATA_DIR)
|
||||
summary = build_usage_summary(records, "2026-06")
|
||||
|
||||
assert summary["record_count"] == 1
|
||||
assert summary["by_member"]["member-tegwick"] == Decimal("0.06")
|
||||
|
||||
|
||||
def test_cost_allocation_includes_ai_variable_cost() -> None:
|
||||
snapshot = _snapshot()
|
||||
allocation = build_cost_allocation(snapshot, load_usage_records(DATA_DIR))
|
||||
|
||||
assert allocation["variable_ai_eur"] == Decimal("0.06")
|
||||
assert allocation["cost_floor_eur"] == snapshot.cost_per_member
|
||||
|
||||
|
||||
def test_pricing_simulator_compares_candidate_models() -> None:
|
||||
snapshot = _snapshot()
|
||||
models = load_pricing_models(DATA_DIR)
|
||||
simulations = build_pricing_simulations(snapshot, models, Decimal("0.06"))
|
||||
|
||||
assert len(simulations["scenarios"]) == 3
|
||||
assert simulations["active_scenario_id"] == "flat-899-eur-monthly"
|
||||
|
||||
|
||||
def test_credit_summary_tracks_remaining_allowance() -> None:
|
||||
wallets = load_credit_wallets(DATA_DIR)
|
||||
summary = build_credit_summary(wallets, {"member-tegwick": Decimal("0.06")}, "2026-06")
|
||||
|
||||
assert summary["wallets"][0]["remaining_eur"] == Decimal("1.94")
|
||||
|
||||
|
||||
def test_recommendations_include_hold_or_action() -> None:
|
||||
payload = build_dashboard_payload(DATA_DIR, "2026-06")
|
||||
recs = build_pricing_recommendations(
|
||||
payload["cost_floor"],
|
||||
payload["value_range"],
|
||||
payload["market_price"],
|
||||
payload["pricing_simulations"],
|
||||
payload["usage"],
|
||||
)
|
||||
|
||||
assert recs
|
||||
assert recs[0]["id"] in {"margin-pressure", "usage-pricing-signal", "value-headroom", "hold-course"}
|
||||
|
||||
|
||||
def test_dashboard_payload_includes_mvp_sections() -> None:
|
||||
payload = build_dashboard_payload(DATA_DIR, "2026-06")
|
||||
|
||||
assert "membership_analytics" in payload
|
||||
assert "usage" in payload
|
||||
assert "cost_allocation" in payload
|
||||
assert "pricing_simulations" in payload
|
||||
assert "credit_wallets" in payload
|
||||
assert "recommendations" in payload
|
||||
Reference in New Issue
Block a user