observatory stuff

This commit is contained in:
2026-06-22 23:05:05 +02:00
parent 1bdb518a94
commit 4ef04dd5e5
22 changed files with 1005 additions and 159 deletions

View File

@@ -11,12 +11,15 @@ from .load import (
latest_period,
load_budget,
load_expense_records,
load_market_signals,
load_membership,
load_monthly_ledger,
load_payment_records,
load_pricing_models,
load_product,
load_value_range,
)
from .pricing_context import build_cost_floor, build_market_price_view, build_value_range_view
def _serialize(value: Any) -> Any:
@@ -72,6 +75,9 @@ def build_dashboard_payload(data_dir: Path | None = None, period: str | None = N
}
)
value_range_raw = load_value_range(root)
market_raw = load_market_signals(root)
return _serialize(
{
"design_reference": "https://claude.ai/design/p/fb2eef8c-c1fc-4c75-bff4-3782552e5511",
@@ -85,6 +91,9 @@ def build_dashboard_payload(data_dir: Path | None = None, period: str | None = N
"members": members,
"payments": payments,
"expense_record_count": len(expenses),
"cost_floor": build_cost_floor(snapshot, models),
"value_range": build_value_range_view(value_range_raw, snapshot, product, models),
"market_price": build_market_price_view(market_raw),
"infrastructure": {
"domains": _load_json_catalog(root, "domains.json"),
"virtual_servers": _load_json_catalog(root, "virtual_servers.json"),

View File

@@ -116,6 +116,14 @@ def load_payment_records(data_dir: Path | None = None) -> list[PaymentRecord]:
]
def load_value_range(data_dir: Path | None = None) -> dict:
return _read_json((data_dir or default_data_dir()) / "value_range.json")
def load_market_signals(data_dir: Path | None = None) -> dict:
return _read_json((data_dir or default_data_dir()) / "market_signals.json")
def load_membership(data_dir: Path | None = None) -> list[MembershipRecord]:
raw = _read_json((data_dir or default_data_dir()) / "membership.json")
return [

View File

@@ -0,0 +1,93 @@
from __future__ import annotations
from decimal import Decimal, ROUND_HALF_UP
from .economics import active_pricing_model
from .models import EconomicsSnapshot, PricingModel, Product
TWOPLACES = Decimal("0.01")
def _quantize(value: Decimal) -> Decimal:
return value.quantize(TWOPLACES, rounding=ROUND_HALF_UP)
def build_cost_floor(
snapshot: EconomicsSnapshot,
models: list[PricingModel],
) -> dict:
active = next((m for m in models if m.status == "active"), None)
return {
"period": snapshot.period,
"currency": snapshot.currency,
"monthly_infrastructure_cost": snapshot.monthly_infrastructure_cost,
"monthly_payment_processing_cost": snapshot.monthly_payment_processing_cost,
"monthly_total_platform_cost": snapshot.monthly_total_platform_cost,
"cost_per_member": snapshot.cost_per_member,
"active_members": snapshot.active_members,
"monthly_revenue": snapshot.monthly_revenue,
"gross_margin": snapshot.gross_margin,
"gross_margin_pct": snapshot.gross_margin_pct,
"active_price": active.access_fee_amount if active else Decimal("0"),
"active_model_id": active.id if active else None,
"active_model_name": active.name if active else None,
}
def build_value_range_view(
raw: dict,
snapshot: EconomicsSnapshot,
product: Product,
models: list[PricingModel],
) -> dict:
model = active_pricing_model(models, product)
current = model.access_fee_amount
segments = []
lows: list[Decimal] = []
highs: list[Decimal] = []
for item in raw.get("segments", []):
low = Decimal(str(item["low_eur"]))
high = Decimal(str(item["high_eur"]))
lows.append(low)
highs.append(high)
segments.append(
{
**item,
"headroom_to_high_eur": _quantize(high - current),
"below_floor": current < low,
}
)
aggregate_low = min(lows) if lows else current
aggregate_high = max(highs) if highs else current
return {
"currency": raw.get("currency", snapshot.currency),
"product_id": raw.get("product_id", product.id),
"current_price_eur": current,
"aggregate_low_eur": aggregate_low,
"aggregate_high_eur": aggregate_high,
"cost_per_member_eur": snapshot.cost_per_member,
"segments": segments,
"value_drivers": raw.get("value_drivers", []),
"notes": raw.get("notes", ""),
}
def build_market_price_view(raw: dict) -> dict:
alternatives = list(raw.get("alternatives", []))
prices = [
Decimal(str(item["price_monthly_eur"]))
for item in alternatives
if item.get("price_monthly_eur") not in (None, "", "0", "0.00")
]
return {
"currency": raw.get("currency", "EUR"),
"last_reviewed": raw.get("last_reviewed"),
"alternative_count": len(alternatives),
"priced_alternative_count": len(prices),
"market_low_eur": min(prices) if prices else None,
"market_high_eur": max(prices) if prices else None,
"alternatives": alternatives,
"notes": raw.get("notes", ""),
}