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
54
tests/test_control_cycle.py
Normal file
54
tests/test_control_cycle.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parents[1] / "tools"))
|
||||
from control_cycle import compare
|
||||
|
||||
|
||||
def record(kind="forecast", resource_class="storage"):
|
||||
result = {
|
||||
"record_id": f"test:{kind}", "record_type": kind,
|
||||
"resource_id": "resource:test", "resource_class": resource_class,
|
||||
"period": "2026-09", "usage_proxies": {"stored_gb": {"value": 100, "unit": "GB-month"}},
|
||||
"costs": {"currency": "EUR", "infrastructure": 10, "internal_labor": 60, "external_labor": 0, "total": 70},
|
||||
"allocation": {"method": "direct", "driver": "stored_gb", "method_version": "1", "unattributed_eur": 0},
|
||||
}
|
||||
if kind == "actual":
|
||||
result["forecast_ref"] = "test:forecast"
|
||||
return result
|
||||
|
||||
|
||||
class ControlCycleTest(unittest.TestCase):
|
||||
def test_same_mechanism_supports_required_resource_classes(self):
|
||||
for resource_class in ("storage", "cluster_compute", "shared_platform_service"):
|
||||
forecast = record(resource_class=resource_class)
|
||||
actual = record("actual", resource_class)
|
||||
actual["usage_proxies"]["stored_gb"]["value"] = 120
|
||||
result = compare(forecast, actual)
|
||||
self.assertEqual(resource_class, result["resource_class"])
|
||||
self.assertEqual("demand", result["usage_proxies"]["stored_gb"]["category"])
|
||||
|
||||
def test_explicit_attribution_and_cost_split_are_preserved(self):
|
||||
forecast, actual = record(), record("actual")
|
||||
actual["costs"].update(infrastructure=12, internal_labor=90, total=102)
|
||||
actual["variance_attribution"] = {"costs.infrastructure": "model", "costs.internal_labor": "labor"}
|
||||
result = compare(forecast, actual)
|
||||
self.assertEqual("model", result["costs"]["infrastructure"]["category"])
|
||||
self.assertEqual(30, result["costs"]["internal_labor"]["error"])
|
||||
|
||||
def test_rejects_wrong_forecast_reference(self):
|
||||
forecast, actual = record(), record("actual")
|
||||
actual["forecast_ref"] = "test:other"
|
||||
with self.assertRaisesRegex(ValueError, "immutable forecast"):
|
||||
compare(forecast, actual)
|
||||
|
||||
def test_missing_proxy_is_data_quality_error(self):
|
||||
forecast, actual = record(), record("actual")
|
||||
actual["usage_proxies"] = {"cpu_hours": {"value": 4, "unit": "vCPU-hour"}}
|
||||
result = compare(forecast, actual)
|
||||
self.assertEqual("data_quality", result["usage_proxies"]["stored_gb"]["category"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue