generated from coulomb/repo-seed
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:
@@ -28,5 +28,6 @@ python3 -m observatory --period 2026-06
|
||||
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
|
||||
replace manual entries.
|
||||
**Current registry state:** one active member (founder), €8.99/month revenue, no
|
||||
running costs recorded. Bubble (Sprint 2) and Stripe (Sprint 3) importers will
|
||||
replace manual entries when integrations land.
|
||||
@@ -1,64 +1,9 @@
|
||||
{
|
||||
"version": 1,
|
||||
"period": "2026-06",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"entries": [],
|
||||
"fx_rates": {
|
||||
"USD/EUR": "0.92"
|
||||
}
|
||||
},
|
||||
"note": "No running costs at present. Future fixed/variable lines (Bubble, Stripe, OpenRouter) added as they become payable."
|
||||
}
|
||||
@@ -1,40 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"snapshot_date": "2026-06-21",
|
||||
"snapshot_date": "2026-06-22",
|
||||
"members": [
|
||||
{
|
||||
"id": "member-001",
|
||||
"id": "member-founder",
|
||||
"external_id": null,
|
||||
"status": "active",
|
||||
"joined_at": "2025-11-03",
|
||||
"plan_id": "flat-899-eur-monthly",
|
||||
"source": "seed"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"source": "manual",
|
||||
"note": "Sole active member (founder)"
|
||||
}
|
||||
],
|
||||
"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."
|
||||
}
|
||||
@@ -2,15 +2,15 @@
|
||||
"version": 1,
|
||||
"entries": [
|
||||
{
|
||||
"id": "rev-2026-06-estimate",
|
||||
"id": "rev-2026-06-founder",
|
||||
"period": "2026-06",
|
||||
"gross_amount": "80.91",
|
||||
"fees_amount": "2.46",
|
||||
"gross_amount": "8.99",
|
||||
"fees_amount": "0.00",
|
||||
"refunds_amount": "0.00",
|
||||
"net_amount": "78.45",
|
||||
"net_amount": "8.99",
|
||||
"currency": "EUR",
|
||||
"source": "manual_estimate",
|
||||
"note": "Seed estimate for Sprint 1; replaced by Stripe sync in Sprint 3"
|
||||
"source": "manual",
|
||||
"note": "Single-member subscription; Stripe sync in Sprint 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
| Metric | Value |
|
||||
|--------|------:|
|
||||
| Active members | 3 |
|
||||
| Monthly revenue | 80.91 EUR |
|
||||
| Monthly cost | 74.16 EUR |
|
||||
| Cost per member | 24.72 EUR |
|
||||
| Gross margin | 6.75 EUR |
|
||||
| Gross margin % | 8.3% |
|
||||
| Active members | 1 |
|
||||
| Monthly revenue | 8.99 EUR |
|
||||
| Monthly cost | 0.00 EUR |
|
||||
| Cost per member | 0.00 EUR |
|
||||
| Gross margin | 8.99 EUR |
|
||||
| Gross margin % | 100.0% |
|
||||
|
||||
_Revenue source: manual_estimate_
|
||||
_Revenue source: manual_
|
||||
|
||||
## Pricing Model Registry
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
||||
|
||||
def test_active_members_counts_only_active_status() -> None:
|
||||
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)
|
||||
models = load_pricing_models(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)
|
||||
|
||||
assert snapshot.active_members == 3
|
||||
assert snapshot.monthly_revenue == Decimal("80.91")
|
||||
assert snapshot.revenue_source == "manual_estimate"
|
||||
assert snapshot.active_members == 1
|
||||
assert snapshot.monthly_revenue == Decimal("8.99")
|
||||
assert snapshot.revenue_source == "manual"
|
||||
assert snapshot.pricing_model_count == 3
|
||||
assert snapshot.monthly_cost > Decimal("0")
|
||||
assert snapshot.cost_per_member == (snapshot.monthly_cost / 3).quantize(Decimal("0.01"))
|
||||
assert snapshot.gross_margin == (snapshot.monthly_revenue - snapshot.monthly_cost).quantize(
|
||||
Decimal("0.01")
|
||||
)
|
||||
assert snapshot.monthly_cost == Decimal("0.00")
|
||||
assert snapshot.cost_per_member == Decimal("0.00")
|
||||
assert snapshot.gross_margin == Decimal("8.99")
|
||||
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)
|
||||
total = monthly_cost_total(costs, fx_rates, Decimal("80.91"), 3)
|
||||
# fixed: 35 USD -> 32.20 EUR + 15 EUR + 25 EUR = 72.20
|
||||
# variable: 1.5% of 80.91 + 0.25 * 3 = 1.21 + 0.75 = 1.96
|
||||
assert total == Decimal("74.16")
|
||||
total = monthly_cost_total(costs, fx_rates, Decimal("8.99"), 1)
|
||||
assert total == Decimal("0.00")
|
||||
|
||||
|
||||
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")
|
||||
assert "# Economics Dashboard v1" 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
|
||||
Reference in New Issue
Block a user