from __future__ import annotations import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent / "src")) import joblib import pandas as pd from batteryswap_public.utils import iterate_scenarios, load_dataset from batteryswapai.competition_features import scenario_history_snapshot def _assert_complete_plan( plan: pd.DataFrame, locations: pd.DataFrame, scenario_name: str ) -> None: required = {"day", "battery"} missing = required - set(plan.columns) if missing: raise AssertionError( f"plan {scenario_name} lacks columns: {sorted(missing)}" ) expected = locations["battery"].astype(str) actual = plan["battery"].astype(str) if len(plan) != len(locations) or actual.duplicated().any(): raise AssertionError( f"plan {scenario_name} must contain each live battery exactly once" ) if set(actual) != set(expected): missing_batteries = sorted(set(expected) - set(actual)) extra_batteries = sorted(set(actual) - set(expected)) raise AssertionError( f"plan {scenario_name} battery mismatch: " f"missing={missing_batteries[:3]}, extra={extra_batteries[:3]}" ) parsed_days = pd.to_datetime(plan["day"], errors="coerce") if parsed_days.isna().any(): raise AssertionError(f"plan {scenario_name} contains an invalid day") def main() -> None: dataset_path = Path(os.environ.get("BATTERYSWAP_DATASET_PATH", "/tmp/data")) artifact_path = Path( os.environ.get( "BATTERYSWAP_PLANNER_PATH", "submission_artifacts/weekly99_planner.joblib", ) ) splits = [ split.strip() for split in os.environ.get("BATTERYSWAP_SPLITS", "public,private").split(",") if split.strip() ] output_path = Path(os.environ.get("BATTERYSWAP_SUBMISSION_PATH", "submission.csv")) if not artifact_path.is_file(): raise FileNotFoundError(f"Planner artifact does not exist: {artifact_path}") planner = joblib.load(artifact_path) plans = [] for split in splits: locations, timeseries, eol_times_for_iterator, scenarios = load_dataset( dataset_path / split ) reset_split = getattr(planner, "reset_split", None) if reset_split is not None: reset_split(split) for scenario, locs, visible_history, _ in iterate_scenarios( locations, timeseries, eol_times_for_iterator, scenarios, ): snapshot = scenario_history_snapshot( visible_history, locs, scenario["name"], scenario["start_time"], ) plan_scenario = getattr(planner, "plan_scenario", None) if plan_scenario is None: plan = planner.plan_snapshot( snapshot, locs, scenario["travel_costs"], scenario["settings"], scenario["start_time"], ) else: plan = plan_scenario( visible_history, snapshot, locs, scenario["travel_costs"], scenario["settings"], scenario["start_time"], ) _assert_complete_plan(plan, locs, str(scenario["name"])) plan["split"] = split plan["scenario"] = scenario["name"] plans.append(plan) submission = pd.concat(plans, ignore_index=True) if submission.duplicated(["split", "scenario", "battery"]).any(): raise AssertionError("submission contains duplicate split/scenario/battery rows") submission.to_csv(output_path, index=False) if not output_path.exists(): raise RuntimeError(f"Submission was not created: {output_path}") if __name__ == "__main__": main()