feat(portfolio): complete RESOURCE-WP-0003 T06 optimization cases and T07 reporting
T06: optimization-case schema, fail-closed evaluator, cadence and decision template. Every option including the baseline must present all ten decision fields; one unknown blocks the comparison. Validated on the storage case (Hetzner computes and loses to Scaleway by EUR 29.14/month on operator labour; Host Europe blocks on four named gaps) and on the non-storage reef-railiance k3s rightsizing case (low utilization is real, but nothing is costable while the railiance01 price is unknown). T07: portfolio report over coverage, lifecycle, utilization, cost, renewals, risks, open cases, and next actions, derived only from committed evidence. Portfolio spend is reported null rather than as a partial sum, unattributed cost is a named list rather than a spread, and unmeasurable resources are reported rather than dropped. RESOURCE-WP-0003 is finished; both cases remain blocked_on_evidence against live delegated records in other repositories. RESOURCE-WP-0002 is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
54dd45c926
commit
2c2a6073ff
63 changed files with 4662 additions and 69 deletions
79
tools/validate.py
Normal file
79
tools/validate.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Dependency-free validation for resource-control JSON declarations."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from optimization import validate_case
|
||||
from portfolio import validate_record
|
||||
from portfolio_report import build as build_portfolio_report
|
||||
|
||||
|
||||
def load(path: str) -> dict:
|
||||
return json.loads(Path(path).read_text())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
demand = load("data/demand/platform-audit-storage.json")
|
||||
providers = load("data/providers/object-storage.json")
|
||||
schema = load("schemas/resource-inventory.schema.json")
|
||||
observation_schema = load("schemas/monthly-resource-observation.schema.json")
|
||||
planning_schema = load("schemas/planning-evidence.schema.json")
|
||||
control_schema = load("schemas/resource-control-cycle.schema.json")
|
||||
forecasts = [load(str(path)) for path in Path("data/forecasts").glob("*.json")]
|
||||
resource_paths = list(Path("data/resources").glob("*.json"))
|
||||
resource_paths += list(Path("examples/portfolio").glob("*.json"))
|
||||
resources = [load(str(path)) for path in resource_paths]
|
||||
control_records = [load(str(path)) for path in Path("examples/control-cycle").glob("*.json")]
|
||||
assert demand["schema_version"] == providers["schema_version"] == "0.1"
|
||||
assert demand["retention_days"] >= 30
|
||||
assert set(demand["scenarios"]) == {"low", "base", "high"}
|
||||
assert len({p["id"] for p in providers["providers"]}) == len(providers["providers"])
|
||||
assert {"Host Europe", "Scaleway", "Hetzner", "AWS", "Microsoft Azure", "Google Cloud", "STACKIT"} <= {p["provider"] for p in providers["providers"]}
|
||||
assert schema["$schema"].endswith("2020-12/schema")
|
||||
assert observation_schema["$schema"].endswith("2020-12/schema")
|
||||
assert planning_schema["$schema"].endswith("2020-12/schema")
|
||||
assert control_schema["$schema"].endswith("2020-12/schema")
|
||||
assert len(planning_schema["oneOf"]) == 5
|
||||
for forecast in forecasts:
|
||||
assert forecast["record_type"] == "forecast"
|
||||
assert len({row["period"] for row in forecast["rows"]}) == len(forecast["rows"])
|
||||
assert all(row["total_eur"] == round(row["infrastructure_eur"] + row["internal_labor_eur"], 2) for row in forecast["rows"])
|
||||
assert schema["properties"]["schema_version"]["const"] == "0.2"
|
||||
required = set(schema["required"])
|
||||
for resource in resources:
|
||||
assert not required - resource.keys(), f"missing fields: {required - resource.keys()}"
|
||||
validate_record(resource)
|
||||
record_ids = {record["record_id"] for record in control_records}
|
||||
assert len(record_ids) == len(control_records)
|
||||
assert {record["resource_class"] for record in control_records} == {"storage", "cluster_compute", "shared_platform_service"}
|
||||
for record in control_records:
|
||||
assert record["schema_version"] == "0.1"
|
||||
assert record["resource_id"].startswith("resource:")
|
||||
costs = record["costs"]
|
||||
assert costs["total"] == round(costs["infrastructure"] + costs["internal_labor"] + costs["external_labor"], 2)
|
||||
if record["record_type"] == "actual":
|
||||
assert record["forecast_ref"] in record_ids
|
||||
case_schema = load("schemas/optimization-case.schema.json")
|
||||
assert case_schema["$schema"].endswith("2020-12/schema")
|
||||
assert case_schema["properties"]["schema_version"]["const"] == "0.1"
|
||||
cases = [load(str(path)) for path in Path("data/optimization").glob("*.json")]
|
||||
assert len({case["case_id"] for case in cases}) == len(cases)
|
||||
for case in cases:
|
||||
assert not set(case_schema["required"]) - case.keys()
|
||||
validate_case(case)
|
||||
# The optimization process must be validated on the backup case and on at
|
||||
# least one non-storage portfolio candidate (RESOURCE-WP-0003-T06).
|
||||
case_resources = {rid for case in cases for rid in case["resource_ids"]}
|
||||
assert "resource:platform:audit-storage" in case_resources
|
||||
assert case_resources - {"resource:platform:audit-storage"}
|
||||
report = build_portfolio_report(Path("."))
|
||||
assert report["resource_count"] == len(resource_paths) - len(list(Path("examples/portfolio").glob("*.json")))
|
||||
assert report["cost"]["known_monthly_spend_eur"] is None
|
||||
assert report["next_actions"]
|
||||
print("resource-control declarations: valid")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue