generated from coulomb/repo-seed
Fix cumulative platform cost Stripe double-counting
Split infrastructure vs payment-processing costs. Liquidity burn now uses infrastructure cash out only (€1,155.20 cumulative) because Stripe fees are already deducted from net member payments. Total platform cost (€1,158.24) remains visible for gross-margin economics.
This commit is contained in:
@@ -24,18 +24,27 @@ separate from platform cost accrual.
|
||||
### LQ-003 — Budget tracking
|
||||
|
||||
Maintain an operator liquidity budget (initial: **€1,000**) and compute
|
||||
remaining budget after cumulative platform spend minus cumulative member
|
||||
payments received.
|
||||
remaining budget after cumulative **infrastructure** spend minus cumulative net
|
||||
member payments received.
|
||||
|
||||
### LQ-004 — Liquidity position
|
||||
|
||||
Report whether the project is **burning**, **neutral**, or **generating**
|
||||
liquidity each period:
|
||||
|
||||
- `period_net = member_payments - platform_costs`
|
||||
- `period_net = net_member_payments - infrastructure_cost`
|
||||
- `cumulative_net = sum(period_net)`
|
||||
- `remaining_budget = initial_budget + cumulative_net`
|
||||
|
||||
**No double-counting:** payment-processing fees (Stripe) are deducted from net
|
||||
member payments. They are tracked separately for economics reporting but must
|
||||
**not** be subtracted again in the liquidity formula.
|
||||
|
||||
- `total_platform_cost = infrastructure_cost + payment_processing_cost` (for
|
||||
gross-margin economics vs gross revenue)
|
||||
- `cumulative_total_platform_cost` is informational; liquidity burn uses
|
||||
`cumulative_infrastructure_cost` only
|
||||
|
||||
Negative remaining budget means the MVP has consumed more liquidity than the
|
||||
allocated budget.
|
||||
|
||||
|
||||
@@ -51,22 +51,22 @@
|
||||
}
|
||||
],
|
||||
"monthly_history": [
|
||||
{"period": "2025-03", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-04", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-05", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-06", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-07", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-08", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-09", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-10", "platform_cost": "72.20", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-11", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2025-12", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-01", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-02", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-03", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-04", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-05", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-06", "platform_cost": "72.58", "active_members": 1, "gross_revenue": "8.99"}
|
||||
{"period": "2025-03", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-04", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-05", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-06", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-07", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-08", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-09", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-10", "infrastructure_cost": "72.20", "payment_processing_cost": "0.00", "active_members": 0, "gross_revenue": "0.00"},
|
||||
{"period": "2025-11", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2025-12", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-01", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-02", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-03", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-04", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-05", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"},
|
||||
{"period": "2026-06", "infrastructure_cost": "72.20", "payment_processing_cost": "0.38", "active_members": 1, "gross_revenue": "8.99"}
|
||||
],
|
||||
"note": "Platform costs are operator expenses. Customer cost-pass-through billing is not active yet."
|
||||
"note": "Infrastructure costs are operator cash outflows. Payment processing is tracked separately and already deducted from net member payments — do not double-count in liquidity."
|
||||
}
|
||||
@@ -30,10 +30,11 @@ def _history_rows(
|
||||
continue
|
||||
payment = revenue_by_period.get(month.period)
|
||||
net_payment = payment.net_amount if payment else Decimal("0")
|
||||
period_net = net_payment - month.platform_cost
|
||||
period_net = net_payment - month.infrastructure_cost
|
||||
lines.append(
|
||||
f"| {month.period} | {month.active_members} | {month.gross_revenue} | "
|
||||
f"{month.platform_cost} | {period_net:.2f} |"
|
||||
f"{month.infrastructure_cost} | {month.payment_processing_cost} | "
|
||||
f"{month.total_platform_cost} | {period_net:.2f} |"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -73,12 +74,15 @@ def render_dashboard(
|
||||
|--------|------:|
|
||||
| Active members | {snapshot.active_members} |
|
||||
| Member payments (gross) | {snapshot.monthly_revenue} {snapshot.currency} |
|
||||
| Platform cost | {snapshot.monthly_platform_cost} {snapshot.currency} |
|
||||
| Infrastructure cost | {snapshot.monthly_infrastructure_cost} {snapshot.currency} |
|
||||
| Payment processing cost | {snapshot.monthly_payment_processing_cost} {snapshot.currency} |
|
||||
| Total platform cost | {snapshot.monthly_total_platform_cost} {snapshot.currency} |
|
||||
| Platform cost per member | {snapshot.cost_per_member} {snapshot.currency} |
|
||||
| Period gross margin | {snapshot.gross_margin} {snapshot.currency} |
|
||||
| Period gross margin % | {snapshot.gross_margin_pct}% |
|
||||
| Period net liquidity | {snapshot.period_net_liquidity} {snapshot.currency} ({snapshot.liquidity_status}) |
|
||||
|
||||
_Period net liquidity = net member payments − infrastructure cost (processing fees already netted from payments)._
|
||||
_Revenue source: {snapshot.revenue_source}_
|
||||
|
||||
## Liquidity & Budget (through {liquidity.through_period})
|
||||
@@ -87,15 +91,17 @@ _Revenue source: {snapshot.revenue_source}_
|
||||
|--------|------:|
|
||||
| Initial budget | {liquidity.initial_budget} {liquidity.currency} |
|
||||
| Cumulative member payments (net) | {liquidity.cumulative_member_payments} {liquidity.currency} |
|
||||
| Cumulative platform cost | {liquidity.cumulative_platform_cost} {liquidity.currency} |
|
||||
| Cumulative infrastructure cost | {liquidity.cumulative_infrastructure_cost} {liquidity.currency} |
|
||||
| Cumulative payment processing | {liquidity.cumulative_payment_processing_cost} {liquidity.currency} |
|
||||
| Cumulative total platform cost | {liquidity.cumulative_total_platform_cost} {liquidity.currency} |
|
||||
| Cumulative net liquidity | {liquidity.cumulative_net_liquidity} {liquidity.currency} ({liquidity.liquidity_status}) |
|
||||
| Remaining budget | {liquidity.remaining_budget} {liquidity.currency} ({budget_state}) |
|
||||
| Months tracked | {liquidity.months_tracked} |
|
||||
|
||||
## Monthly History
|
||||
|
||||
| Period | Members | Gross revenue | Platform cost | Net liquidity |
|
||||
|--------|--------:|--------------:|--------------:|--------------:|
|
||||
| Period | Members | Gross revenue | Infrastructure | Processing | Total platform | Net liquidity |
|
||||
|--------|--------:|--------------:|---------------:|-----------:|---------------:|--------------:|
|
||||
{history_lines}
|
||||
|
||||
## Pricing Model Registry
|
||||
|
||||
@@ -112,22 +112,29 @@ def build_liquidity_summary(
|
||||
cost_by_period = {item.period: item for item in monthly_costs}
|
||||
|
||||
cumulative_payments = Decimal("0")
|
||||
cumulative_cost = Decimal("0")
|
||||
cumulative_infrastructure = Decimal("0")
|
||||
cumulative_processing = Decimal("0")
|
||||
for period in tracked:
|
||||
month = cost_by_period[period]
|
||||
cumulative_cost += month.platform_cost
|
||||
cumulative_infrastructure += month.infrastructure_cost
|
||||
cumulative_processing += month.payment_processing_cost
|
||||
payment = revenue_for_period(period, revenue_entries)
|
||||
cumulative_payments += payment.net_amount if payment else Decimal("0")
|
||||
|
||||
cumulative_net = _quantize(cumulative_payments - cumulative_cost)
|
||||
# Liquidity uses net member payments vs infrastructure cash out only.
|
||||
# Payment-processing fees are already deducted from net payments.
|
||||
cumulative_net = _quantize(cumulative_payments - cumulative_infrastructure)
|
||||
remaining = _quantize(budget.initial_budget + cumulative_net)
|
||||
cumulative_total = _quantize(cumulative_infrastructure + cumulative_processing)
|
||||
|
||||
return LiquiditySummary(
|
||||
currency=budget.currency,
|
||||
through_period=through_period,
|
||||
initial_budget=budget.initial_budget,
|
||||
cumulative_member_payments=_quantize(cumulative_payments),
|
||||
cumulative_platform_cost=_quantize(cumulative_cost),
|
||||
cumulative_infrastructure_cost=_quantize(cumulative_infrastructure),
|
||||
cumulative_payment_processing_cost=_quantize(cumulative_processing),
|
||||
cumulative_total_platform_cost=cumulative_total,
|
||||
cumulative_net_liquidity=cumulative_net,
|
||||
remaining_budget=remaining,
|
||||
liquidity_status=liquidity_status_for(cumulative_net),
|
||||
@@ -148,22 +155,26 @@ def build_snapshot(
|
||||
gross_revenue, net_revenue, revenue_source = estimate_monthly_revenue(
|
||||
period, product, models, members, revenue_entries, monthly_costs
|
||||
)
|
||||
platform_cost = month.platform_cost
|
||||
cost_per_member = _quantize(platform_cost / count) if count else Decimal("0.00")
|
||||
gross_margin = _quantize(gross_revenue - platform_cost)
|
||||
infrastructure = month.infrastructure_cost
|
||||
processing = month.payment_processing_cost
|
||||
total_platform = month.total_platform_cost
|
||||
cost_per_member = _quantize(total_platform / count) if count else Decimal("0.00")
|
||||
gross_margin = _quantize(gross_revenue - total_platform)
|
||||
margin_pct = (
|
||||
_quantize((gross_margin / gross_revenue) * Decimal("100"), PCTPLACES)
|
||||
if gross_revenue
|
||||
else Decimal("-100.0") if platform_cost else Decimal("0.0")
|
||||
else Decimal("-100.0") if total_platform else Decimal("0.0")
|
||||
)
|
||||
period_net = _quantize(net_revenue - platform_cost)
|
||||
period_net = _quantize(net_revenue - infrastructure)
|
||||
|
||||
return EconomicsSnapshot(
|
||||
period=period,
|
||||
currency=product.currency,
|
||||
active_members=count,
|
||||
monthly_revenue=_quantize(gross_revenue),
|
||||
monthly_platform_cost=platform_cost,
|
||||
monthly_infrastructure_cost=infrastructure,
|
||||
monthly_payment_processing_cost=processing,
|
||||
monthly_total_platform_cost=total_platform,
|
||||
cost_per_member=cost_per_member,
|
||||
gross_margin=gross_margin,
|
||||
gross_margin_pct=margin_pct,
|
||||
|
||||
@@ -4,6 +4,8 @@ import json
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
_ZERO = Decimal("0")
|
||||
|
||||
from .models import (
|
||||
Budget,
|
||||
CostEntry,
|
||||
@@ -89,15 +91,25 @@ def load_cost_rate_card(data_dir: Path | None = None) -> tuple[list[CostEntry],
|
||||
|
||||
def load_monthly_platform_costs(data_dir: Path | None = None) -> list[MonthlyPlatformCost]:
|
||||
raw = _read_json((data_dir or default_data_dir()) / "costs.json")
|
||||
return [
|
||||
MonthlyPlatformCost(
|
||||
period=item["period"],
|
||||
platform_cost=_money(item["platform_cost"]),
|
||||
active_members=item["active_members"],
|
||||
gross_revenue=_money(item["gross_revenue"]),
|
||||
rows: list[MonthlyPlatformCost] = []
|
||||
for item in raw.get("monthly_history", []):
|
||||
if "infrastructure_cost" in item:
|
||||
infrastructure = _money(item["infrastructure_cost"])
|
||||
processing = _money(item.get("payment_processing_cost", "0"))
|
||||
else:
|
||||
# Legacy single platform_cost field treated as infrastructure only.
|
||||
infrastructure = _money(item["platform_cost"])
|
||||
processing = _ZERO
|
||||
rows.append(
|
||||
MonthlyPlatformCost(
|
||||
period=item["period"],
|
||||
infrastructure_cost=infrastructure,
|
||||
payment_processing_cost=processing,
|
||||
active_members=item["active_members"],
|
||||
gross_revenue=_money(item["gross_revenue"]),
|
||||
)
|
||||
)
|
||||
for item in raw.get("monthly_history", [])
|
||||
]
|
||||
return rows
|
||||
|
||||
|
||||
def load_revenue(data_dir: Path | None = None) -> list[RevenueEntry]:
|
||||
|
||||
@@ -46,10 +46,15 @@ class CostEntry:
|
||||
@dataclass(frozen=True)
|
||||
class MonthlyPlatformCost:
|
||||
period: str
|
||||
platform_cost: Decimal
|
||||
infrastructure_cost: Decimal
|
||||
payment_processing_cost: Decimal
|
||||
active_members: int
|
||||
gross_revenue: Decimal
|
||||
|
||||
@property
|
||||
def total_platform_cost(self) -> Decimal:
|
||||
return self.infrastructure_cost + self.payment_processing_cost
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Budget:
|
||||
@@ -85,7 +90,9 @@ class EconomicsSnapshot:
|
||||
currency: str
|
||||
active_members: int
|
||||
monthly_revenue: Decimal
|
||||
monthly_platform_cost: Decimal
|
||||
monthly_infrastructure_cost: Decimal
|
||||
monthly_payment_processing_cost: Decimal
|
||||
monthly_total_platform_cost: Decimal
|
||||
cost_per_member: Decimal
|
||||
gross_margin: Decimal
|
||||
gross_margin_pct: Decimal
|
||||
@@ -101,7 +108,9 @@ class LiquiditySummary:
|
||||
through_period: str
|
||||
initial_budget: Decimal
|
||||
cumulative_member_payments: Decimal
|
||||
cumulative_platform_cost: Decimal
|
||||
cumulative_infrastructure_cost: Decimal
|
||||
cumulative_payment_processing_cost: Decimal
|
||||
cumulative_total_platform_cost: Decimal
|
||||
cumulative_net_liquidity: Decimal
|
||||
remaining_budget: Decimal
|
||||
liquidity_status: LiquidityStatus
|
||||
|
||||
@@ -13,12 +13,15 @@
|
||||
|--------|------:|
|
||||
| Active members | 1 |
|
||||
| Member payments (gross) | 8.99 EUR |
|
||||
| Platform cost | 72.58 EUR |
|
||||
| Infrastructure cost | 72.20 EUR |
|
||||
| Payment processing cost | 0.38 EUR |
|
||||
| Total platform cost | 72.58 EUR |
|
||||
| Platform cost per member | 72.58 EUR |
|
||||
| Period gross margin | -63.59 EUR |
|
||||
| Period gross margin % | -707.3% |
|
||||
| Period net liquidity | -63.97 EUR (burning) |
|
||||
| Period net liquidity | -63.59 EUR (burning) |
|
||||
|
||||
_Period net liquidity = net member payments − infrastructure cost (processing fees already netted from payments)._
|
||||
_Revenue source: manual_
|
||||
|
||||
## Liquidity & Budget (through 2026-06)
|
||||
@@ -27,31 +30,33 @@ _Revenue source: manual_
|
||||
|--------|------:|
|
||||
| Initial budget | 1000.00 EUR |
|
||||
| Cumulative member payments (net) | 68.88 EUR |
|
||||
| Cumulative platform cost | 1158.24 EUR |
|
||||
| Cumulative net liquidity | -1089.36 EUR (burning) |
|
||||
| Remaining budget | -89.36 EUR (over budget) |
|
||||
| Cumulative infrastructure cost | 1155.20 EUR |
|
||||
| Cumulative payment processing | 3.04 EUR |
|
||||
| Cumulative total platform cost | 1158.24 EUR |
|
||||
| Cumulative net liquidity | -1086.32 EUR (burning) |
|
||||
| Remaining budget | -86.32 EUR (over budget) |
|
||||
| Months tracked | 16 |
|
||||
|
||||
## Monthly History
|
||||
|
||||
| Period | Members | Gross revenue | Platform cost | Net liquidity |
|
||||
|--------|--------:|--------------:|--------------:|--------------:|
|
||||
| 2025-03 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-04 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-05 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-06 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-07 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-08 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-09 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-10 | 0 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-11 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2025-12 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-01 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-02 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-03 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-04 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-05 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| 2026-06 | 1 | 8.99 | 72.58 | -63.97 |
|
||||
| Period | Members | Gross revenue | Infrastructure | Processing | Total platform | Net liquidity |
|
||||
|--------|--------:|--------------:|---------------:|-----------:|---------------:|--------------:|
|
||||
| 2025-03 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-04 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-05 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-06 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-07 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-08 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-09 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-10 | 0 | 0.00 | 72.20 | 0.00 | 72.20 | -72.20 |
|
||||
| 2025-11 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2025-12 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-01 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-02 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-03 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-04 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-05 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
| 2026-06 | 1 | 8.99 | 72.20 | 0.38 | 72.58 | -63.59 |
|
||||
|
||||
## Pricing Model Registry
|
||||
|
||||
|
||||
@@ -27,19 +27,16 @@ def test_active_members_counts_only_active_status() -> None:
|
||||
assert active_members(members) == 1
|
||||
|
||||
|
||||
def test_rate_card_matches_documented_platform_cost_without_revenue() -> None:
|
||||
def test_rate_card_splits_infrastructure_and_payment_processing() -> None:
|
||||
rate_card, fx_rates = load_cost_rate_card(DATA_DIR)
|
||||
total = monthly_cost_from_rate_card(rate_card, fx_rates, Decimal("0"), 0)
|
||||
assert total == Decimal("72.20")
|
||||
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_rate_card_matches_platform_cost_with_one_member() -> None:
|
||||
rate_card, fx_rates = load_cost_rate_card(DATA_DIR)
|
||||
total = monthly_cost_from_rate_card(rate_card, fx_rates, Decimal("8.99"), 1)
|
||||
assert total == Decimal("72.58")
|
||||
|
||||
|
||||
def test_build_snapshot_june_2026_shows_liquidity_burn() -> None:
|
||||
def test_build_snapshot_june_2026_avoids_stripe_double_count_in_liquidity() -> None:
|
||||
product = load_product(DATA_DIR)
|
||||
models = load_pricing_models(DATA_DIR)
|
||||
members = load_membership(DATA_DIR)
|
||||
@@ -48,37 +45,34 @@ def test_build_snapshot_june_2026_shows_liquidity_burn() -> None:
|
||||
|
||||
snapshot = build_snapshot("2026-06", product, models, members, revenue, monthly_costs)
|
||||
|
||||
assert snapshot.active_members == 1
|
||||
assert snapshot.monthly_revenue == Decimal("8.99")
|
||||
assert snapshot.monthly_platform_cost == Decimal("72.58")
|
||||
assert snapshot.period_net_liquidity == Decimal("-63.97")
|
||||
assert snapshot.liquidity_status == "burning"
|
||||
assert snapshot.monthly_infrastructure_cost == Decimal("72.20")
|
||||
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")
|
||||
|
||||
|
||||
def test_liquidity_summary_tracks_budget_burn_through_june_2026() -> None:
|
||||
def test_liquidity_summary_uses_infrastructure_only_for_cumulative_burn() -> None:
|
||||
budget = load_budget(DATA_DIR)
|
||||
revenue = load_revenue(DATA_DIR)
|
||||
monthly_costs = load_monthly_platform_costs(DATA_DIR)
|
||||
|
||||
summary = build_liquidity_summary(budget, revenue, monthly_costs, "2026-06")
|
||||
|
||||
assert summary.initial_budget == Decimal("1000.00")
|
||||
assert summary.cumulative_platform_cost == Decimal("1158.24")
|
||||
assert summary.cumulative_infrastructure_cost == Decimal("1155.20")
|
||||
assert summary.cumulative_payment_processing_cost == Decimal("3.04")
|
||||
assert summary.cumulative_total_platform_cost == Decimal("1158.24")
|
||||
assert summary.cumulative_member_payments == Decimal("68.88")
|
||||
assert summary.cumulative_net_liquidity == Decimal("-1089.36")
|
||||
assert summary.remaining_budget == Decimal("-89.36")
|
||||
assert summary.cumulative_net_liquidity == Decimal("-1086.32")
|
||||
assert summary.remaining_budget == Decimal("-86.32")
|
||||
assert summary.liquidity_status == "burning"
|
||||
assert summary.months_tracked == 16
|
||||
|
||||
|
||||
def test_dashboard_renders_liquidity_sections() -> None:
|
||||
def test_dashboard_renders_split_cost_columns() -> None:
|
||||
from observatory.dashboard import generate_dashboard
|
||||
|
||||
report = generate_dashboard(DATA_DIR, "2026-06")
|
||||
assert "Liquidity & Budget" in report
|
||||
assert "Monthly History" in report
|
||||
assert "2025-03" in report
|
||||
assert "2025-11" in report
|
||||
assert "remaining budget" not in report.lower() or "Remaining budget" in report
|
||||
assert "-89.36" in report
|
||||
assert "Cumulative infrastructure cost" in report
|
||||
assert "1155.20" in report
|
||||
assert "-86.32" in report
|
||||
Reference in New Issue
Block a user