#!/usr/bin/env python3 """Compare monthly resource actuals with the immutable decision forecast.""" from __future__ import annotations import json import sys from pathlib import Path METRICS = ( "database_gb", "stored_gb", "wal_gb", "restore_egress_gb", "write_requests", "read_requests", "infrastructure_eur", "internal_labor_hours", "internal_labor_eur", "total_eur", ) def compare(forecast: dict, actual: dict) -> dict: expected = {row["period"]: row for row in forecast["rows"]} rows = [] for observed in actual["rows"]: period = observed["period"] if period not in expected: rows.append({"period": period, "status": "no-forecast", "metrics": {}}) continue metrics = {} for metric in METRICS: planned = expected[period][metric] measured = observed[metric] # An uninvoiced period has no cost to compare against. Treating a # missing amount as zero would report a 100% favourable variance. if planned is None or measured is None: metrics[metric] = { "forecast": planned, "actual": measured, "status": "unknown", "category": "data_quality", } continue error = measured - planned metrics[metric] = { "forecast": planned, "actual": measured, "error": round(error, 4), "absolute_percentage_error": None if planned == 0 else round(abs(error) / planned * 100, 2), } rows.append({"period": period, "status": "compared", "metrics": metrics}) return { "forecast_created_at": forecast["created_at"], "provider_id": actual["provider_id"], "rows": rows, } def main() -> int: if len(sys.argv) != 3: print(f"usage: {sys.argv[0]} FORECAST.json ACTUAL.json", file=sys.stderr) return 2 forecast = json.loads(Path(sys.argv[1]).read_text()) actual = json.loads(Path(sys.argv[2]).read_text()) print(json.dumps(compare(forecast, actual), indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())