Example Model for BatterySwapAI 2026
This repository shows how to create a solution for the BatterySwapAI 2026 challenge.
It contains working example code that can be submitted as-is, along with tools to check the solution before submitting.
Setup
Prerequisites
You need to have the following installed
- git
- Python 3.10+
- Docker
Setting up virtual environment
On Linux / Mac OS / Windows Subsystem for Linux (WSL)
python -m venv venv
source venv/bin/activate
Install dependencies
pip install -r requirements.txt -r requirements.dev.txt
Developing
Develop in virtual environment
Run the training
python batteryswap_example/train.py
Making submissions
Submitting trained models
Trained models or other output used by submission processing (script.py),
must be committed in the git repository.
An example is batteryswap_example/planners/best.pickle.
Test submissions in Docker - recommended
Using Docker allows to have exactly the same software versions as the submissions system.
This helps to ensure there are no errors when running in the submission environment.
NOTE: this requires around 20 GB+ of disk space.
Build Docker image
docker build -t batteryswapai-2026-example .
Make submissions and run evaluation (mount your local data folder to /tmp/data)
docker run --name batteryswapai -v ./BatterySwapAI-2026-Public:/tmp/data batteryswapai-2026-example bash -c "/app/env/bin/python3 script.py && /app/env/bin/python3 -m batteryswap_public.metric"
Copy submission.csv out of container
docker cp batteryswapai:/app/submission.csv ./submission.csv
Create new submission
NOTE: remember to commit and push your changes to the HuggingFace model repository.
Use New submission in the competition application to submit your current code for evaluation.
Reproducing the submitted model
The committed batteryswap_example/planners/best.pickle is produced by:
BATTERYSWAP_DATASET_PATH=BatterySwapAI-2026-Public \
venv/bin/python3 batteryswap_example/train.py
Exact configuration used (all hardcoded defaults in batteryswap_example/train.py,
so no environment variables are required at submission time):
RUL model:
final_model = 'survival'(lifelines Weibull AFT). Chosen by lowestevaluate_plantotal_cost summed over all scenarios, not by MAE β MAE only scores observed (non-censored) devices and is blind to the survival model's censoring-aware conservatism on long-lived devices.Right-censoring bound:
censor_horizon='true'(per-device). 82% of devices (379/461) have a blank EOL, i.e. are right-censored, and an AFT likelihood contributesS(C)for each of them β soCmust be the time we actually know the device survived to. The original code used a constant 120 days for every censored device, although the data shows they were still alive at the dataset cutoff (~334 days afters_0's start). Measured (sweep_censoring.py, day-caps trimmed to32,inf):censor_horizon total early_swap late_swap 'true' 106,733 41,437 43,220 365.0 112,393 33,596 59,660 240.0 112,551 32,046 60,930 120.0 (old) 112,641 21,724 70,780 The 240/365 arms are controls:
'true'beats equal-magnitude constants by ~5,700, so the win comes from the per-device variation, not merely from a larger bound. Mechanism: a correct bound spreads predicted RUL out, so the planner can prioritise genuinely-dying devices instead of treating the whole fleet as due at once βlate_swapfalls 70,780 β 43,220.early_swaprises, but that is a good trade at the evaluator's 20:1 late:early ratio.Scheduling quantile:
OrderedPlanner.SCHEDULE_QUANTILE = 'p10'(PLANNER_SCHEDULE_QUANTILEdefault). Re-validated AFTER the censoring fix (sweep_quantiles.py, trained withcensor_horizon='true', day-caps32,inf):q total early_swap late_swap p10 106,733 41,437 43,220 p12 112,046 32,065 56,970 p08 115,759 61,014 32,540 p15 114,331 22,290 73,400 p05 139,788 102,092 13,980 p50 151,106 463 137,790 p10 is a clear, stable minimum (next-best p12 is +5,312). Note the empirical optimum sits ABOVE the newsvendor anchor
q* = early/(early+late) = 0.5/10.5 β p05: perfect-calibration theory says p05, but the day-cap/batching congestion penalises over-early swaps more than the raw 20:1 ratio implies, so p05 actually scores 139,788 β trusting theory over measurement would cost ~33k. NOTE: this default is what the grader uses, because the scheduling quantile is a class attribute read at import and the grader sets no env var.final_quantilesmust include the chosen quantile or the planner silently falls back to p50.Planner day-work caps:
PLANNER_DAY_CAPSdefault24,32,48,inf(self-tuned per scenario byevaluate_planagainst the predicted EOL).Random seed:
seed=0(default intrain_rul_model).Training data:
split='train',limit_scenarios=None(ALL 48 scenarios feed RUL training; every scenario is the same physical fleet at a +7-day-shifted start, so this exposes each device at many life-stages rather than duplicating rows). Measured-best: survival + p10 + all-scenarios sums to 112,641 total_cost across all scenarios, ~16% better than the oldlimit_scenarios=1(134,576). The gain is monotonic across1,2,4,8,16,all(seesweep_training_data.py), consistent with the RUL model being data-starved atlimit_scenarios=1(only ~82 observed failures). NOTE: this local harness evaluates on the same fleet it trains on, so treat the absolute number as optimistic β the hidden competition devices are the real judge β but the monotonic trend makes the direction trustworthy.
Files required for a complete, reproducible submission
Runtime (copied into the competition Docker image via Dockerfile):
script.pyβ submission entrypoint (loadsbest.pickle, callsmake_submissions).Dockerfileβ build recipe (COPY batteryswap_example/,COPY script.py).requirements.txtβ fixed by the competition (already includeslifelines,scikit-learn, etc.).batteryswap_example/train.pyβ definesOrderedPlanner+ RUL models (must be importable for unpicklingbest.pickle).batteryswap_example/planners/best.pickleβ the trained planner artifact.
Repository / reproducibility:
README.mdβ this file (documents the exact reproduce command).LICENSEβ MIT (edit the copyright holder).
Not part of the grader run and kept out of the submission repo via .gitignore
(the Dockerfile only copies batteryswap_example/ and script.py; these are
local-only tuning/investigation tools that informed the hardcoded defaults in
train.py, plus generated outputs and the organizer-provided dataset):
compare_runs.py, inspect_data.py, investigate_*.py, sweep_*.py
(sweep_quantiles.py, sweep_training_data.py, sweep_censoring.py),
results/, submission.csv (generated), BatterySwapAI-2026-Public/ (dataset).
requirements.dev.txt is kept (referenced by the setup instructions above).