Ticket 075: Minimum Battery Sizing Calculator¶
Goal¶
Add a bvlos-sim size-battery command (or estimate --find-min-battery) that
computes the minimum battery capacity needed for a mission to be feasible.
Given a mission and vehicle, binary-search the battery capacity until the
energy reserve just meets the configured threshold, and report the required Wh
with a safety margin recommendation.
Motivation¶
Operators frequently work backwards from a route:
"I want to fly this pipeline survey. My vehicle supports 900 Wh or 1200 Wh packs. Which one do I need?"
Today the workflow is:
1. Run estimate with the current vehicle profile.
2. If infeasible, manually edit battery_capacity_wh in the YAML, re-run.
3. Repeat until the reserve is positive.
4. Add a safety margin above the minimum.
This is tedious and error-prone. A single size-battery command closes the
loop in one call, answering "you need at least 843 Wh (20% safety margin: 1012 Wh)".
Output Specification¶
## Battery Sizing: pipeline_survey_001
Mission energy required: 328.4 Wh
Reserve threshold (25 %): 225.0 Wh (of battery capacity)
Minimum feasible capacity: 843.2 Wh
With 10 % safety margin: 927.5 Wh
With 20 % safety margin: 1011.8 Wh
With 30 % safety margin: 1096.2 Wh
Recommendation: use ≥ 927.5 Wh battery (10 % margin above minimum feasible)
Status: SIZED
If the current battery is already sufficient:
## Battery Sizing: pipeline_demo_001
Current capacity: 900.0 Wh
Reserve at landing: 585.0 Wh (65.0% of capacity)
Minimum feasible: 550.3 Wh
Margin over minimum: 349.7 Wh (63.5% above minimum)
Status: FEASIBLE (current battery exceeds minimum by 63.5%)
Implementation¶
1 — adapters/battery_sizer.py (new)¶
@dataclass(frozen=True)
class BatterySizingResult:
mission_energy_wh: float
reserve_threshold_wh: float
minimum_capacity_wh: float
current_capacity_wh: float
current_reserve_wh: float
current_reserve_pct: float
is_current_feasible: bool
def compute_minimum_battery_capacity(
mission: MissionPlan,
vehicle: VehicleProfile,
*,
wind_provider=None,
terrain_provider=None,
geofences=None,
landing_zones=None,
tolerance_wh: float = 1.0,
max_iterations: int = 40,
) -> BatterySizingResult:
"""Binary-search battery capacity until reserve just meets threshold."""
def render_battery_sizing_markdown(
result: BatterySizingResult,
*,
mission_id: str,
safety_margins: list[int] | None = None,
) -> str:
"""Render the sizing result as Markdown."""
Binary search bounds:
- Lower bound: start at mission_energy_wh + reserve_threshold_wh (minimum
possible feasible capacity where reserve equals threshold exactly)
- Upper bound: start at 2 × current_battery_capacity_wh or
mission_energy_wh × 4 if current is already infeasible
- Convergence: when upper − lower < tolerance_wh
The vehicle VehicleProfile is patched per-iteration using
model_copy(update={"energy": energy.model_copy(update={"battery_capacity_wh": cap})}).
2 — New size-battery CLI command¶
bvlos-sim size-battery mission.yaml vehicle.yaml
bvlos-sim size-battery mission.yaml vehicle.yaml --margin 20
bvlos-sim size-battery mission.yaml vehicle.yaml --format json
Or as estimate --find-min-battery if that is preferred.
Adding a dedicated size-battery command is cleaner — it has a different
exit code contract (always exits 0 unless there's an error, regardless of
whether the current battery is feasible).
3 — Exit codes¶
| Status | Exit code |
|---|---|
| Sizing succeeded | 0 |
| Invalid input | 11 |
| Internal error | 13 |
4 — Tests¶
tests/test_battery_sizer.py:
- test_binary_search_finds_minimum_capacity: known mission + vehicle, check
minimum capacity is close to expected value
- test_minimum_capacity_is_at_feasibility_boundary: estimate with
minimum_capacity_wh → reserve ≥ threshold; with minimum_capacity_wh - 1
→ reserve < threshold
- test_oversized_battery_reports_current_feasible: current vehicle has
excess capacity
- test_markdown_contains_recommendation_line
- test_markdown_shows_safety_margin_recommendations
- CLI integration: size-battery mission.yaml vehicle.yaml exits 0
5 — Documentation¶
Update docs/USAGE.md with a size-battery section.
Integration¶
Reads only from existing MissionPlan, VehicleProfile, and mission asset
bundle. Uses try_estimate_mission_distance_time — no new execution paths.
No schema changes required.
Acceptance Criteria¶
size-batteryexits 0 on the success fixture and outputsStatus: FEASIBLE.size-batteryexits 0 on the infeasible fixture and outputs the minimum battery capacity needed.- The minimum capacity, when applied to the vehicle profile and re-estimated, produces a reserve at or above the reserve threshold.
- All existing tests continue to pass.