Skip to content

investigate: near-threshold BATTERY_EXPORT flips between runs (root cause unverified) #196

Description

@johanzander

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

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bot-analyzedTriage bot has processed this issueenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions