Status: original analysis was wrong — re-scoped to real findings
The original plan (repurpose min_action_profit_threshold as a per-period
sell_price − cycle_cost floor) is withdrawn. It rested on an incorrect
diagnosis and would have encoded a gross-value error. The real findings are below;
follow up separately, and verify before any code change.
Symptom (real, economically negligible)
In some optimization runs, slot 15:45 (period 63) is scheduled as BATTERY_EXPORT
with ~0.1 kWh / 4% discharge; in later runs the same slot is SOLAR_EXPORT (no
discharge). Battery is full (SOE 20.0 kWh) with a solar surplus. Net effect of the
trade ≈ −0.04 SEK (a small loss). Visible as a brief SOC dip / red bar.
Why the original fix was wrong
- The mechanism is not missing.
_compute_reward
(core/bess/dp_battery_algorithm.py:376-389) already raises the discharge floor
to sell_price when solar will replenish the battery. In the 11:31 run,
cost_basis 0.62 > sell 0.46 correctly blocked the discharge.
sell_price − cycle_cost is the gross-value fallacy. The real alternative to
discharging at 15:45 is "let solar charge one more slot and export one slot
earlier", so the value captured is only the sell-price differential (~6 öre),
not the full sell price. Against ~40 öre wear that is a loss. A flat
sell − wear floor compares gross value to wear and would re-introduce exactly
this error. (See the governing law in docs/agents/bess-knowledge.md.)
Candidate real causes (NEITHER verified — do not fix yet)
A. Shadow-price volatility across runs. The flip is driven by shadow_price for
period 63 moving across re-optimizations near the discharge threshold (e.g.
different measured SOC / solar forecast between the 10:16 and 11:31 runs).
B. Anti-cycling gate over-values stored energy in solar-surplus slots. The gate
uses effective_value = max(buy×eff_d, sell×eff_d)
(dp_battery_algorithm.py:356-359). In this slot solar already covers all home
load, so there is no grid import to avoid — the discharged kWh can only be
exported, making the true value sell×eff_d (~0.41), not buy×eff_d (~0.95).
Including the buy term lets a marginal discharge slip past the gate.
Next step (separate work)
Build a reproduction test (solar > home, battery full, small local sell spike) and
determine which of A/B actually drives the flip before changing any code. Then
decide on the fix. Do not implement a min_action_profit margin-floor parameter.
Status: original analysis was wrong — re-scoped to real findings
The original plan (repurpose
min_action_profit_thresholdas a per-periodsell_price − cycle_costfloor) is withdrawn. It rested on an incorrectdiagnosis and would have encoded a gross-value error. The real findings are below;
follow up separately, and verify before any code change.
Symptom (real, economically negligible)
In some optimization runs, slot 15:45 (period 63) is scheduled as
BATTERY_EXPORTwith ~0.1 kWh / 4% discharge; in later runs the same slot is
SOLAR_EXPORT(nodischarge). Battery is full (SOE 20.0 kWh) with a solar surplus. Net effect of the
trade ≈ −0.04 SEK (a small loss). Visible as a brief SOC dip / red bar.
Why the original fix was wrong
_compute_reward(
core/bess/dp_battery_algorithm.py:376-389) already raises the discharge floorto
sell_pricewhen solar will replenish the battery. In the 11:31 run,cost_basis 0.62 > sell 0.46correctly blocked the discharge.sell_price − cycle_costis the gross-value fallacy. The real alternative todischarging at 15:45 is "let solar charge one more slot and export one slot
earlier", so the value captured is only the sell-price differential (~6 öre),
not the full sell price. Against ~40 öre wear that is a loss. A flat
sell − wearfloor compares gross value to wear and would re-introduce exactlythis error. (See the governing law in
docs/agents/bess-knowledge.md.)Candidate real causes (NEITHER verified — do not fix yet)
A. Shadow-price volatility across runs. The flip is driven by
shadow_priceforperiod 63 moving across re-optimizations near the discharge threshold (e.g.
different measured SOC / solar forecast between the 10:16 and 11:31 runs).
B. Anti-cycling gate over-values stored energy in solar-surplus slots. The gate
uses
effective_value = max(buy×eff_d, sell×eff_d)(
dp_battery_algorithm.py:356-359). In this slot solar already covers all homeload, so there is no grid import to avoid — the discharged kWh can only be
exported, making the true value
sell×eff_d(~0.41), notbuy×eff_d(~0.95).Including the buy term lets a marginal discharge slip past the gate.
Next step (separate work)
Build a reproduction test (solar > home, battery full, small local sell spike) and
determine which of A/B actually drives the flip before changing any code. Then
decide on the fix. Do not implement a
min_action_profitmargin-floor parameter.