#!/usr/bin/env python3 """Resolve billing-compatible user_unit_cost grouping from bundled policy.""" from __future__ import annotations import argparse import datetime as dt import hashlib import json from pathlib import Path from typing import Any DEFAULT_POLICY_PATH = ( Path(__file__).resolve().parents[1] / "references" / "user-unit-cost-grouping-policy.json" ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "Resolve standard versus ignore-labels grouping for user_unit_cost " "from the bundled billing compatibility policy." ) ) parser.add_argument("--tenant-id", required=True) parser.add_argument( "--period-start", required=True, help="Billing period start as YYYY-MM-DD.", ) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--policy", type=Path, default=DEFAULT_POLICY_PATH) return parser.parse_args() def load_policy(path: Path) -> tuple[dict[str, Any], str]: raw = path.read_bytes() policy = json.loads(raw) if not isinstance(policy, dict): raise ValueError("Grouping policy must be a JSON object.") required = { "format", "source_url", "source_ref", "verified_at", "ignore_labels_cutoff", "disable_cluster_display_tenants", } missing = sorted(required - policy.keys()) if missing: raise ValueError(f"Grouping policy is missing: {', '.join(missing)}.") tenants = policy["disable_cluster_display_tenants"] if not isinstance(tenants, list) or any(not isinstance(item, str) for item in tenants): raise ValueError("disable_cluster_display_tenants must be a list of strings.") if len(tenants) != len(set(tenants)): raise ValueError("disable_cluster_display_tenants contains duplicates.") return policy, hashlib.sha256(raw).hexdigest() def resolve_grouping( tenant_id: str, period_start: dt.date, policy: dict[str, Any], policy_sha256: str, ) -> dict[str, Any]: cutoff = dt.date.fromisoformat(str(policy["ignore_labels_cutoff"])) configured_tenants = set(policy["disable_cluster_display_tenants"]) tenant_is_configured = tenant_id in configured_tenants cutoff_applies = period_start >= cutoff ignore_labels = tenant_is_configured and cutoff_applies if ignore_labels: reason = "tenant_is_configured_and_period_is_on_or_after_cutoff" elif not tenant_is_configured: reason = "tenant_is_not_configured" else: reason = "period_is_before_cutoff" return { "format": "manual-invoice-user-unit-cost-grouping-decision/v1", "tenant_id": tenant_id, "period_start": period_start.isoformat(), "grouping_mode": "ignore_labels" if ignore_labels else "standard", "ignore_labels": ignore_labels, "reason": reason, "policy": { "format": policy["format"], "source_url": policy["source_url"], "source_ref": policy["source_ref"], "verified_at": policy["verified_at"], "sha256": policy_sha256, "ignore_labels_cutoff": cutoff.isoformat(), "configured_tenant_count": len(configured_tenants), "tenant_is_configured": tenant_is_configured, }, } def write_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") def main() -> int: args = parse_args() try: period_start = dt.date.fromisoformat(args.period_start) except ValueError as exc: raise ValueError("--period-start must use YYYY-MM-DD.") from exc policy, policy_sha256 = load_policy(args.policy) decision = resolve_grouping( str(args.tenant_id), period_start, policy, policy_sha256 ) write_json(args.output, decision) print(json.dumps(decision)) return 0 if __name__ == "__main__": raise SystemExit(main())