Refactor economics to expense-record ledger with correct Bubble cost

Replace pre-aggregated costs.json with expense_records.json (48 line-item
records) and payment_records.json. All monthly and cumulative totals are
computed deterministically in observatory/ledger.py. Correct Bubble.io to
$32/mo (since Feb 2025) — infrastructure €69.44/mo not €72.20.
This commit is contained in:
2026-06-22 02:03:22 +02:00
parent ea2c2c6403
commit 31db9f8f31
12 changed files with 843 additions and 299 deletions

View File

@@ -3,20 +3,17 @@ from __future__ import annotations
from decimal import Decimal
from pathlib import Path
from observatory.economics import (
active_members,
build_liquidity_summary,
build_snapshot,
monthly_cost_from_rate_card,
)
from observatory.economics import active_members, build_liquidity_summary, build_snapshot
from observatory.ledger import aggregate_infrastructure_by_period, build_monthly_ledger
from observatory.load import (
load_budget,
load_cost_rate_card,
load_expense_records,
load_fx_rates,
load_membership,
load_monthly_platform_costs,
load_monthly_ledger,
load_payment_records,
load_pricing_models,
load_product,
load_revenue,
)
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
@@ -27,52 +24,80 @@ def test_active_members_counts_only_active_status() -> None:
assert active_members(members) == 1
def test_rate_card_splits_infrastructure_and_payment_processing() -> None:
rate_card, fx_rates = load_cost_rate_card(DATA_DIR)
infrastructure = monthly_cost_from_rate_card(rate_card, fx_rates, Decimal("0"), 0)
with_revenue = monthly_cost_from_rate_card(rate_card, fx_rates, Decimal("8.99"), 1)
assert infrastructure == Decimal("72.20")
assert with_revenue == Decimal("72.58")
assert with_revenue - infrastructure == Decimal("0.38")
def test_infrastructure_aggregated_from_expense_records() -> None:
expenses = load_expense_records(DATA_DIR)
fx = load_fx_rates(DATA_DIR)
totals = aggregate_infrastructure_by_period(expenses, fx)
assert totals["2025-03"] == Decimal("69.44")
assert totals["2026-06"] == Decimal("69.44")
# Bubble $32 @ 0.92 = 29.44 + 15 domains + 25 overhead
bubble_eur = Decimal("32.00") * Decimal("0.92")
assert bubble_eur == Decimal("29.44")
def test_build_snapshot_june_2026_avoids_stripe_double_count_in_liquidity() -> None:
def test_monthly_ledger_computes_processing_from_payment_records() -> None:
ledger = load_monthly_ledger(DATA_DIR)
june = next(row for row in ledger if row.period == "2026-06")
march = next(row for row in ledger if row.period == "2025-03")
assert march.infrastructure_cost == Decimal("69.44")
assert march.payment_processing_cost == Decimal("0.00")
assert march.gross_revenue == Decimal("0.00")
assert june.infrastructure_cost == Decimal("69.44")
assert june.payment_processing_cost == Decimal("0.38")
assert june.gross_revenue == Decimal("8.99")
def test_build_snapshot_june_2026_uses_ledger_not_hand_totals() -> None:
product = load_product(DATA_DIR)
models = load_pricing_models(DATA_DIR)
members = load_membership(DATA_DIR)
revenue = load_revenue(DATA_DIR)
monthly_costs = load_monthly_platform_costs(DATA_DIR)
payments = load_payment_records(DATA_DIR)
ledger = load_monthly_ledger(DATA_DIR)
snapshot = build_snapshot("2026-06", product, models, members, revenue, monthly_costs)
snapshot = build_snapshot("2026-06", product, models, members, payments, ledger)
assert snapshot.monthly_infrastructure_cost == Decimal("72.20")
assert snapshot.monthly_infrastructure_cost == Decimal("69.44")
assert snapshot.monthly_payment_processing_cost == Decimal("0.38")
assert snapshot.monthly_total_platform_cost == Decimal("72.58")
assert snapshot.period_net_liquidity == Decimal("-63.59")
assert snapshot.gross_margin == Decimal("-63.59")
assert snapshot.monthly_total_platform_cost == Decimal("69.82")
assert snapshot.period_net_liquidity == Decimal("-60.83")
assert snapshot.gross_margin == Decimal("-60.83")
def test_liquidity_summary_uses_infrastructure_only_for_cumulative_burn() -> None:
def test_liquidity_summary_aggregates_ledgers_deterministically() -> None:
budget = load_budget(DATA_DIR)
revenue = load_revenue(DATA_DIR)
monthly_costs = load_monthly_platform_costs(DATA_DIR)
payments = load_payment_records(DATA_DIR)
ledger = load_monthly_ledger(DATA_DIR)
summary = build_liquidity_summary(budget, revenue, monthly_costs, "2026-06")
summary = build_liquidity_summary(budget, payments, ledger, "2026-06")
assert summary.cumulative_infrastructure_cost == Decimal("1155.20")
assert summary.cumulative_infrastructure_cost == Decimal("1111.04")
assert summary.cumulative_payment_processing_cost == Decimal("3.04")
assert summary.cumulative_total_platform_cost == Decimal("1158.24")
assert summary.cumulative_total_platform_cost == Decimal("1114.08")
assert summary.cumulative_member_payments == Decimal("68.88")
assert summary.cumulative_net_liquidity == Decimal("-1086.32")
assert summary.remaining_budget == Decimal("-86.32")
assert summary.cumulative_net_liquidity == Decimal("-1042.16")
assert summary.remaining_budget == Decimal("-42.16")
assert summary.liquidity_status == "burning"
assert summary.months_tracked == 16
def test_dashboard_renders_split_cost_columns() -> None:
def test_build_monthly_ledger_matches_loader() -> None:
budget = load_budget(DATA_DIR)
expenses = load_expense_records(DATA_DIR)
payments = load_payment_records(DATA_DIR)
members = load_membership(DATA_DIR)
fx = load_fx_rates(DATA_DIR)
assert build_monthly_ledger(budget, expenses, payments, members, fx) == load_monthly_ledger(
DATA_DIR
)
def test_dashboard_notes_expense_record_source() -> None:
from observatory.dashboard import generate_dashboard
report = generate_dashboard(DATA_DIR, "2026-06")
assert "Cumulative infrastructure cost" in report
assert "1155.20" in report
assert "-86.32" in report
assert "expense and payment record ledgers" in report
assert "1111.04" in report
assert "-42.16" in report