"""Build the frozen hierarchical-FPT wrapper around the public V05 artifact. The wrapper carries two operational corrections on top of V05's policy, each validated independently over 48 dates x 27 bases: * ``capacity_lookback_days`` 14 -> 42 (V06): the repair pass may relocate work across the whole horizon instead of a fortnight. -4.68, t=-4.38. * ``emergency_operational_scale`` 0 -> 0.5 (V07): a missed battery becomes its own working day with a dedicated round trip, measured at 65.56 beyond the late penalty against the flat 2.0 the selection assumed. -14.62, t=-4.11. """ from __future__ import annotations import argparse import hashlib import importlib.metadata import io import json import subprocess from pathlib import Path import joblib from dataclasses import replace from batteryswapai.hierarchical_fpt import ( EOL_VOLTAGE, HORIZON_DAYS, MAX_STATE_STALENESS_DAYS, MIN_WEEKLY_INCREMENTS, RANK_RESIDUAL_WEIGHT, SCHEMA_VERSION, TERMINAL_WEEKS, HierarchicalFPTPlanner, ) PUBLIC_V05_GIT_SPEC = "public/v05:submission_artifacts/planner.joblib" PUBLIC_V05_SHA256 = "63d71091d7f46e8fb11798b7c2be936cc7e0022954824a4eb98c07dc1bf9b0b3" CAPACITY_LOOKBACK_DAYS = 42 EMERGENCY_OPERATIONAL_SCALE = 0.5 CAPACITY_WEEKLY_LIMIT_FRACTION = 0.99 def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--output", type=Path, default=Path("submission_artifacts/hierarchical_fpt_planner.joblib"), ) parser.add_argument( "--manifest", type=Path, default=Path("submission_artifacts/hierarchical_fpt_planner.json"), ) parser.add_argument("--base-git-spec", default=PUBLIC_V05_GIT_SPEC) return parser.parse_args() def _sha_bytes(payload: bytes) -> str: return hashlib.sha256(payload).hexdigest() def _sha(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def _git_bytes(spec: str) -> bytes: completed = subprocess.run( ["git", "show", spec], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) return completed.stdout def main() -> None: args = parse_args() repo = Path(__file__).resolve().parents[1] payload = _git_bytes(args.base_git_spec) base_sha = _sha_bytes(payload) if base_sha != PUBLIC_V05_SHA256: raise AssertionError( f"public V05 SHA mismatch: expected {PUBLIC_V05_SHA256}, found {base_sha}" ) base_planner = joblib.load(io.BytesIO(payload)) if int(base_planner.policy.capacity_lookback_days) != 14: raise AssertionError("embedded planner is not public V05 capacity policy") base_planner.policy = replace( base_planner.policy, capacity_lookback_days=CAPACITY_LOOKBACK_DAYS, emergency_operational_scale=EMERGENCY_OPERATIONAL_SCALE, capacity_weekly_limit_fraction=CAPACITY_WEEKLY_LIMIT_FRACTION, ) wrapper = HierarchicalFPTPlanner( base_planner=base_planner, base_artifact_sha256=base_sha, ) args.output.parent.mkdir(parents=True, exist_ok=True) joblib.dump(wrapper, args.output, compress=3) loaded = joblib.load(args.output) if loaded.base_artifact_sha256 != PUBLIC_V05_SHA256: raise AssertionError("serialized wrapper lost its public V05 provenance") manifest = { "artifact": args.output.as_posix(), "artifact_sha256": _sha(args.output), "schema_version": SCHEMA_VERSION, "base_artifact": args.base_git_spec, "base_artifact_sha256": base_sha, "configuration": { "eol_voltage": EOL_VOLTAGE, "horizon_days": HORIZON_DAYS, "terminal_nonoverlap_weeks": TERMINAL_WEEKS, "minimum_weekly_increments": MIN_WEEKLY_INCREMENTS, "maximum_state_staleness_days": MAX_STATE_STALENESS_DAYS, "rank_residual_weight": RANK_RESIDUAL_WEIGHT, "capacity_lookback_days": CAPACITY_LOOKBACK_DAYS, "emergency_operational_scale": EMERGENCY_OPERATIONAL_SCALE, "capacity_weekly_limit_fraction": CAPACITY_WEEKLY_LIMIT_FRACTION, "outer_pool": "leave current target building out", "smoothing": ( "strict 10=5; rolling seven-calendar-day " "median min_periods=3; terminal calendar day strictly before cutoff" ), }, "promotion_evidence": { "official_48": "artifacts/hierarchical_fpt_official.json", "robust_48x27": "artifacts/hierarchical_fpt_grid.cases.json", }, "source_sha256": { name: _sha(repo / name) for name in ( "src/batteryswapai/hierarchical_fpt.py", "src/batteryswapai/identity_ensemble.py", "src/batteryswapai/competition_planner.py", "scripts/experiment_hierarchical_fpt.py", "scripts/evaluate_hierarchical_fpt_grid.py", "scripts/build_hierarchical_fpt_submission.py", "script.py", "Dockerfile", "requirements.txt", ) }, "runtime_versions": { package: importlib.metadata.version(package) for package in ( "batteryswap_public", "joblib", "numpy", "pandas", "scipy", "scikit-learn", ) }, } args.manifest.write_text(json.dumps(manifest, indent=2), encoding="utf-8") print(json.dumps(manifest, indent=2)) if __name__ == "__main__": main()