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.
96 lines
3.3 KiB
Python
96 lines
3.3 KiB
Python
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 |