Adapt economics dashboard to sole-member zero-cost reality

Update membership, revenue, and cost registries to one active member with
no running costs. Refresh dashboard report and tests for €8.99 revenue and
100% gross margin.
This commit is contained in:
2026-06-22 01:41:59 +02:00
parent a1a4aa972f
commit 8f42220d81
6 changed files with 38 additions and 118 deletions

View File

@@ -28,5 +28,6 @@ python3 -m observatory --period 2026-06
python3 -m observatory --period 2026-06 --output reports/economics-2026-06.md python3 -m observatory --period 2026-06 --output reports/economics-2026-06.md
``` ```
Seed data is used until Bubble (Sprint 2) and Stripe (Sprint 3) importers **Current registry state:** one active member (founder), €8.99/month revenue, no
replace manual entries. running costs recorded. Bubble (Sprint 2) and Stripe (Sprint 3) importers will
replace manual entries when integrations land.

View File

@@ -1,64 +1,9 @@
{ {
"version": 1, "version": 1,
"period": "2026-06", "period": "2026-06",
"entries": [ "entries": [],
{
"id": "bubble-subscription",
"name": "Bubble.io platform",
"category": "fixed",
"amount": "35.00",
"currency": "USD",
"cadence": "monthly",
"allocation": "flat"
},
{
"id": "domains",
"name": "Domains",
"category": "fixed",
"amount": "15.00",
"currency": "EUR",
"cadence": "monthly",
"allocation": "flat"
},
{
"id": "operational-overhead",
"name": "Operational overhead",
"category": "fixed",
"amount": "25.00",
"currency": "EUR",
"cadence": "monthly",
"allocation": "flat"
},
{
"id": "stripe-percentage",
"name": "Stripe percentage fee",
"category": "variable",
"amount": "0.015",
"currency": "ratio",
"cadence": "per_transaction",
"allocation": "percent_of_gross_revenue"
},
{
"id": "stripe-fixed",
"name": "Stripe fixed fee",
"category": "variable",
"amount": "0.25",
"currency": "EUR",
"cadence": "per_transaction",
"allocation": "per_active_member"
},
{
"id": "openrouter-ai",
"name": "OpenRouter AI consumption",
"category": "variable",
"amount": "0.00",
"currency": "EUR",
"cadence": "monthly",
"allocation": "flat",
"note": "Populated in Sprint 4 after usage import"
}
],
"fx_rates": { "fx_rates": {
"USD/EUR": "0.92" "USD/EUR": "0.92"
} },
"note": "No running costs at present. Future fixed/variable lines (Bubble, Stripe, OpenRouter) added as they become payable."
} }

View File

@@ -1,40 +1,16 @@
{ {
"version": 1, "version": 1,
"snapshot_date": "2026-06-21", "snapshot_date": "2026-06-22",
"members": [ "members": [
{ {
"id": "member-001", "id": "member-founder",
"external_id": null, "external_id": null,
"status": "active", "status": "active",
"joined_at": "2025-11-03", "joined_at": "2025-11-03",
"plan_id": "flat-899-eur-monthly", "plan_id": "flat-899-eur-monthly",
"source": "seed" "source": "manual",
}, "note": "Sole active member (founder)"
{
"id": "member-002",
"external_id": null,
"status": "active",
"joined_at": "2026-01-15",
"plan_id": "flat-899-eur-monthly",
"source": "seed"
},
{
"id": "member-003",
"external_id": null,
"status": "active",
"joined_at": "2026-03-28",
"plan_id": "flat-899-eur-monthly",
"source": "seed"
},
{
"id": "member-004",
"external_id": null,
"status": "churned",
"joined_at": "2025-08-10",
"churned_at": "2026-02-01",
"plan_id": "flat-899-eur-monthly",
"source": "seed"
} }
], ],
"note": "Seed data for Sprint 1; replaced by Bubble importer in Sprint 2" "note": "Current reality: one active member. Bubble importer (Sprint 2) will sync live data."
} }

View File

@@ -2,15 +2,15 @@
"version": 1, "version": 1,
"entries": [ "entries": [
{ {
"id": "rev-2026-06-estimate", "id": "rev-2026-06-founder",
"period": "2026-06", "period": "2026-06",
"gross_amount": "80.91", "gross_amount": "8.99",
"fees_amount": "2.46", "fees_amount": "0.00",
"refunds_amount": "0.00", "refunds_amount": "0.00",
"net_amount": "78.45", "net_amount": "8.99",
"currency": "EUR", "currency": "EUR",
"source": "manual_estimate", "source": "manual",
"note": "Seed estimate for Sprint 1; replaced by Stripe sync in Sprint 3" "note": "Single-member subscription; Stripe sync in Sprint 3"
} }
] ]
} }

View File

@@ -8,14 +8,14 @@
| Metric | Value | | Metric | Value |
|--------|------:| |--------|------:|
| Active members | 3 | | Active members | 1 |
| Monthly revenue | 80.91 EUR | | Monthly revenue | 8.99 EUR |
| Monthly cost | 74.16 EUR | | Monthly cost | 0.00 EUR |
| Cost per member | 24.72 EUR | | Cost per member | 0.00 EUR |
| Gross margin | 6.75 EUR | | Gross margin | 8.99 EUR |
| Gross margin % | 8.3% | | Gross margin % | 100.0% |
_Revenue source: manual_estimate_ _Revenue source: manual_
## Pricing Model Registry ## Pricing Model Registry

View File

@@ -17,10 +17,10 @@ DATA_DIR = Path(__file__).resolve().parent.parent / "data"
def test_active_members_counts_only_active_status() -> None: def test_active_members_counts_only_active_status() -> None:
members = load_membership(DATA_DIR) members = load_membership(DATA_DIR)
assert active_members(members) == 3 assert active_members(members) == 1
def test_build_snapshot_uses_seed_revenue_for_period() -> None: def test_build_snapshot_reflects_sole_member_and_zero_costs() -> None:
product = load_product(DATA_DIR) product = load_product(DATA_DIR)
models = load_pricing_models(DATA_DIR) models = load_pricing_models(DATA_DIR)
members = load_membership(DATA_DIR) members = load_membership(DATA_DIR)
@@ -29,23 +29,20 @@ def test_build_snapshot_uses_seed_revenue_for_period() -> None:
snapshot = build_snapshot("2026-06", product, models, members, revenue, costs, fx_rates) snapshot = build_snapshot("2026-06", product, models, members, revenue, costs, fx_rates)
assert snapshot.active_members == 3 assert snapshot.active_members == 1
assert snapshot.monthly_revenue == Decimal("80.91") assert snapshot.monthly_revenue == Decimal("8.99")
assert snapshot.revenue_source == "manual_estimate" assert snapshot.revenue_source == "manual"
assert snapshot.pricing_model_count == 3 assert snapshot.pricing_model_count == 3
assert snapshot.monthly_cost > Decimal("0") assert snapshot.monthly_cost == Decimal("0.00")
assert snapshot.cost_per_member == (snapshot.monthly_cost / 3).quantize(Decimal("0.01")) assert snapshot.cost_per_member == Decimal("0.00")
assert snapshot.gross_margin == (snapshot.monthly_revenue - snapshot.monthly_cost).quantize( assert snapshot.gross_margin == Decimal("8.99")
Decimal("0.01") assert snapshot.gross_margin_pct == Decimal("100.0")
)
def test_monthly_cost_includes_fixed_and_variable_components() -> None: def test_monthly_cost_is_zero_with_empty_registry() -> None:
_, costs, fx_rates = load_costs(DATA_DIR) _, costs, fx_rates = load_costs(DATA_DIR)
total = monthly_cost_total(costs, fx_rates, Decimal("80.91"), 3) total = monthly_cost_total(costs, fx_rates, Decimal("8.99"), 1)
# fixed: 35 USD -> 32.20 EUR + 15 EUR + 25 EUR = 72.20 assert total == Decimal("0.00")
# variable: 1.5% of 80.91 + 0.25 * 3 = 1.21 + 0.75 = 1.96
assert total == Decimal("74.16")
def test_dashboard_module_renders_markdown() -> None: def test_dashboard_module_renders_markdown() -> None:
@@ -54,4 +51,5 @@ def test_dashboard_module_renders_markdown() -> None:
report = generate_dashboard(DATA_DIR, "2026-06") report = generate_dashboard(DATA_DIR, "2026-06")
assert "# Economics Dashboard v1" in report assert "# Economics Dashboard v1" in report
assert "Pricing Model Registry" in report assert "Pricing Model Registry" in report
assert "flat-899-eur-monthly" in report assert "flat-899-eur-monthly" in report
assert "Active members | 1" in report