#!/usr/bin/env python3 """Produce planning evidence and consume fin-hub booked-cost projections.""" from __future__ import annotations import json import sys from calendar import monthrange from datetime import date from decimal import Decimal, ROUND_HALF_EVEN from pathlib import Path CENT = Decimal("0.01") def money(value: object) -> Decimal: return Decimal(str(value)).quantize(CENT, rounding=ROUND_HALF_EVEN) def money_text(value: object) -> str: return format(money(value), ".2f") def forecast_records(payload: dict, *, resource_id: str, source_ref: str) -> list[dict]: if payload.get("schema_version") != "0.1" or payload.get("record_type") != "forecast": raise ValueError("expected a resource-control forecast v0.1") if not resource_id.startswith("resource:"): raise ValueError("resource_id must use the resource: namespace") created_at = payload["created_at"] version = f"{payload['provider_id']}:{created_at}" records = [] for row in payload["rows"]: infrastructure = money(row["infrastructure_eur"]) labor = money(row["internal_labor_eur"]) if money(row["total_eur"]) != infrastructure + labor: raise ValueError("total_eur does not match infrastructure plus internal labor") year, month = (int(part) for part in row["period"].split("-")) period_start = date(year, month, 1) period_end = date(year, month, monthrange(year, month)[1]) records.append({ "schema_version": "0.1", "record_type": "forecast", "record_id": f"forecast:{payload['provider_id']}:{payload['cost_attribution_key']}:{row['period']}:{created_at}", "revision_of": payload.get("forecast_ref"), "resource_id": resource_id, "service_id": payload["provider_id"], "workload_id": payload["workload"], "tenant_id": None, "environment": "production", "cost_attribution_key": payload["cost_attribution_key"], "period_start": period_start.isoformat(), "period_end": period_end.isoformat(), "currency": "EUR", "source_evidence": [source_ref, *row.get("evidence", [])], "created_at": created_at, "scenario": payload.get("scenario") or "base", "forecast_version": version, "costs": { "infrastructure": money_text(infrastructure), "internal_labor": money_text(labor), "external_services": "0.00", "setup": "0.00", "other": "0.00" }, "uncertainty": None, "assumptions": [ f"database_gb={row['database_gb']}", f"stored_gb={row['stored_gb']}", f"wal_gb={row['wal_gb']}", f"restore_egress_gb={row['restore_egress_gb']}", f"write_requests={row['write_requests']}", f"read_requests={row['read_requests']}", f"internal_labor_hours={row['internal_labor_hours']}" ] }) return records def usage_records(payload: dict, *, resource_id: str, source_ref: str) -> list[dict]: """Normalize a monthly technical observation for the fin-hub exchange. Only the resource-control-to-fin-hub direction: technical usage and valued internal labour. Infrastructure cost is deliberately absent — fin-hub owns the booked fact, and an uninvoiced period carries no amount to send. A null stays null; it is never sent as 0.00. """ if payload.get("schema_version") not in {"0.1", "0.2"}: raise ValueError("expected a resource-control observation v0.1 or v0.2") if payload.get("record_type") != "usage_observation": raise ValueError("expected record_type usage_observation") if not resource_id.startswith("resource:"): raise ValueError("resource_id must use the resource: namespace") records = [] for row in payload["rows"]: year, month = (int(part) for part in row["period"].split("-")) period_start = date(year, month, 1) period_end = date(year, month, monthrange(year, month)[1]) labor = row["internal_labor_eur"] records.append({ "schema_version": "0.1", "record_type": "technical_usage", "record_id": ( f"usage:{payload['provider_id']}:{payload['cost_attribution_key']}:{row['period']}" ), "resource_id": resource_id, "service_id": payload["provider_id"], "workload_id": payload["workload"], "tenant_id": None, "environment": "production", "cost_attribution_key": payload["cost_attribution_key"], "period_start": period_start.isoformat(), "period_end": period_end.isoformat(), "currency": "EUR", "created_at": payload["created_at"], "source_evidence": [source_ref, *payload.get("evidence", [])], "usage": { key: row[key] for key in ( "database_gb", "stored_gb", "wal_gb", "restore_egress_gb", "write_requests", "read_requests", ) }, "service_evidence": { key: row.get(key) for key in ("backup_success_pct", "restore_rto_minutes") }, "costs": { # resource-control originates internal labour only. Booked # infrastructure cost is fin-hub's to record, not ours to echo. "internal_labor": None if labor is None else money_text(labor), "internal_labor_hours": row["internal_labor_hours"], }, "measurement_gaps": row.get("measurement_gaps", []), "notes": payload.get("notes", []), }) return records def validate_booked_cost(record: dict) -> dict: required = { "schema_version", "record_type", "financial_fact_id", "adjustment_kind", "source_document_id", "source_line_id", "content_fingerprint", "provider", "accounting_period", "currency", "gross_amount", "adjustment_amount", "effective_amount", "source_evidence_ref", "recorded_at" } missing = required - record.keys() if missing: raise ValueError(f"booked-cost evidence missing {sorted(missing)}") if record["schema_version"] != "0.1" or record["record_type"] != "booked_cost": raise ValueError("unsupported booked-cost schema") if len(record["currency"]) != 3 or record["currency"] != record["currency"].upper(): raise ValueError("currency must be an uppercase three-letter code") if money(record["effective_amount"]) != money(record["gross_amount"]) + money(record["adjustment_amount"]): raise ValueError("effective_amount must equal gross plus adjustment") if record.get("tax_status") == "unknown" and record.get("tax_amount") is not None: raise ValueError("unknown tax must not have a tax amount") return record def reconcile(forecasts: list[dict], booked_costs: list[dict]) -> list[dict]: planned = { (record["period_start"][:7], record["currency"], record.get("cost_attribution_key")): money(record["costs"]["infrastructure"]) for record in forecasts if record["record_type"] == "forecast" } observed: dict[tuple[str, str, str | None], Decimal] = {} fact_ids: dict[tuple[str, str, str | None], list[str]] = {} for raw in booked_costs: record = validate_booked_cost(raw) key = (record["accounting_period"], record["currency"], record.get("cost_attribution_key")) observed[key] = observed.get(key, Decimal("0")) + money(record["effective_amount"]) fact_ids.setdefault(key, []).append(record["financial_fact_id"]) rows = [] for key in sorted(set(planned) | set(observed), key=lambda item: (item[0], item[1], item[2] or "")): forecast = planned.get(key) actual = observed.get(key) rows.append({ "period": key[0], "currency": key[1], "cost_attribution_key": key[2], "forecast_infrastructure": None if forecast is None else money_text(forecast), "booked_effective": None if actual is None else money_text(actual), "variance": None if forecast is None or actual is None else money_text(actual - forecast), "financial_fact_ids": fact_ids.get(key, []), "status": "reconciled" if forecast is not None and actual is not None else "missing-booked-cost" if forecast is not None else "missing-forecast" }) return rows def main() -> int: if len(sys.argv) < 3 or sys.argv[1] not in {"forecast", "usage", "reconcile"}: print( f"usage: {sys.argv[0]} forecast FORECAST.json [RESOURCE_ID]" f" | usage OBSERVATION.json [RESOURCE_ID]" f" | reconcile FORECAST_EVIDENCE.json BOOKED_COST.json", file=sys.stderr, ) return 2 if sys.argv[1] in {"forecast", "usage"}: payload = json.loads(Path(sys.argv[2]).read_text()) resource_id = sys.argv[3] if len(sys.argv) > 3 else "resource:platform_audit_storage" build = forecast_records if sys.argv[1] == "forecast" else usage_records print(json.dumps(build(payload, resource_id=resource_id, source_ref=sys.argv[2]), indent=2)) return 0 forecasts = json.loads(Path(sys.argv[2]).read_text()) booked = json.loads(Path(sys.argv[3]).read_text()) print(json.dumps(reconcile(forecasts, booked), indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())