What happened
While investigating #289, I found that input_data["initial_soe"] in the debug export/schedule JSON does not represent the SOE the DP optimizer actually started from for a given run, once more than one optimization has happened that day.
Root cause
core/bess/battery_system_manager.py:2094-2104:
total_cap = self.battery_settings.total_capacity
if self._initial_soc_pct is not None:
result.input_data["initial_soe"] = (
self._initial_soc_pct / 100.0 * total_cap
)
elif not prepare_next_day:
current_soc = self._get_current_battery_soc()
if current_soc is not None:
result.input_data["initial_soe"] = current_soc / 100.0 * total_cap
self._initial_soc_pct is documented at battery_system_manager.py:171 as "SOC at midnight (%), set at period 0", and is set once per day at battery_system_manager.py:1277. Because the if self._initial_soc_pct is not None: branch always wins once it's set, input_data["initial_soe"] is overwritten with the day-start (midnight) SOE on every re-optimization for the rest of the day — not the SOE the DP actually used as its starting state for that specific run.
Evidence
In a user-submitted debug bundle for #289 (BESS v9.8.1, optimization triggered at 08:00, optimization_period=32):
input_data.initial_soe = 9.6 kWh
- The same run's own period-32 decision row shows
battery_soe_start = 6.2 kWh
The DP's real starting SOE for that run was 6.2 kWh (confirmed by reproducing the run with the real dp_battery_algorithm.py code); 9.6 was the midnight SOC, carried over from _initial_soc_pct.
Why this matters
This field is one of the primary things a maintainer (or an AI agent, per docs/agents/) reads first when debugging a reported schedule from a user's debug bundle. Because it silently means "day-start SOE" instead of "this run's starting SOE" after the first run of the day, it actively misleads root-cause analysis — it did exactly that during the #289 investigation before we caught it by cross-checking against the period data.
Suggested fix
Split the concepts instead of overloading one field, e.g.:
- Keep
initial_soe meaning "the SOE this optimization run actually started from" (i.e. always populate it from the current run's real starting state, not _initial_soc_pct).
- Add a separate, distinctly-named field such as
day_start_soe for the midnight value DailyViewBuilder needs for chart anchoring.
Happy to have an agent pick this up via the usual @claude-bot analyze / @claude-bot fix flow.
What happened
While investigating #289, I found that
input_data["initial_soe"]in the debug export/schedule JSON does not represent the SOE the DP optimizer actually started from for a given run, once more than one optimization has happened that day.Root cause
core/bess/battery_system_manager.py:2094-2104:self._initial_soc_pctis documented atbattery_system_manager.py:171as "SOC at midnight (%), set at period 0", and is set once per day atbattery_system_manager.py:1277. Because theif self._initial_soc_pct is not None:branch always wins once it's set,input_data["initial_soe"]is overwritten with the day-start (midnight) SOE on every re-optimization for the rest of the day — not the SOE the DP actually used as its starting state for that specific run.Evidence
In a user-submitted debug bundle for #289 (BESS v9.8.1, optimization triggered at 08:00,
optimization_period=32):input_data.initial_soe=9.6kWhbattery_soe_start = 6.2kWhThe DP's real starting SOE for that run was 6.2 kWh (confirmed by reproducing the run with the real
dp_battery_algorithm.pycode);9.6was the midnight SOC, carried over from_initial_soc_pct.Why this matters
This field is one of the primary things a maintainer (or an AI agent, per
docs/agents/) reads first when debugging a reported schedule from a user's debug bundle. Because it silently means "day-start SOE" instead of "this run's starting SOE" after the first run of the day, it actively misleads root-cause analysis — it did exactly that during the #289 investigation before we caught it by cross-checking against the period data.Suggested fix
Split the concepts instead of overloading one field, e.g.:
initial_soemeaning "the SOE this optimization run actually started from" (i.e. always populate it from the current run's real starting state, not_initial_soc_pct).day_start_soefor the midnight valueDailyViewBuilderneeds for chart anchoring.Happy to have an agent pick this up via the usual
@claude-bot analyze/@claude-bot fixflow.