Faze 0A: battery guard carve-out — neblokovat import na nabiti pri zaporne cene

_apply_export_plan_guard / _build_setpoints: kdyz slot CHARGE / importuje na
nabiti baterie (grid_sp>0 & bat>0), guard vrati sp beze zmeny a export_ban se
nenastavi. Opravuje, ze se baterie nedobila v zapornych cenach (CHARGE+17kW
prekloplen na PASSIVE -> Deye nenabijel ze site). Diagnoza: agent a599eecc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-13 22:40:18 +02:00
parent 6e89b044f5
commit 521a3653d3
4 changed files with 276 additions and 2 deletions

View File

@@ -125,11 +125,20 @@ def _build_setpoints(
export_limit = 0
elif export_limit <= 0 and grid_sp < 0:
export_limit = abs(grid_sp)
bat_w = int(pi["battery_setpoint_w"] or 0)
# Záporný výkup sám o sobě neblokuje export, pokud plán export explicitně žádá.
export_ban = sell_f is not None and float(sell_f) < 0 and grid_sp >= 0
# A nesmí blokovat ani IMPORT na nabití baterie (CHARGE / grid>0 & bat>0) —
# jinak MI cut-off (178) / 145=0 zbytečně odstaví pole B a Deye nenabije
# ze sítě v záporných cenách (bug 2026-06-13). §6 blokuje jen export.
is_grid_charge = pm == "CHARGE" or (grid_sp > 0 and bat_w > 0)
export_ban = (
sell_f is not None
and float(sell_f) < 0
and grid_sp >= 0
and not is_grid_charge
)
gen_cutoff_raw = pi.get("deye_gen_cutoff_enabled")
gen_cutoff = bool(gen_cutoff_raw) if gen_cutoff_raw is not None else False
bat_w = int(pi["battery_setpoint_w"] or 0)
pv_a_allowed: int | None = None
if bool(reg340_pv_a_control_enabled) and int(pv_a_cap_w) > 0:
forecast = int(pi.get("pv_a_forecast_solver_w") or 0)
@@ -294,6 +303,16 @@ def _apply_export_plan_guard(
)
grid_sp = int(pi.get("grid_setpoint_w") or sp.grid_setpoint_w or 0)
# Carve-out: nabíjecí / importní slot NENÍ export. Guard řeší jen zákaz
# exportu při sell<0 — když plán importuje na nabití baterie (CHARGE, nebo
# grid_sp>0 & bat_sp>0), překlopení na PASSIVE by zařízlo grid charge
# (bug 2026-06-13: baterie se nedobila v záporných cenách). §6 zakazuje
# jen export, ne import (§7).
pm = str(pi.get("deye_physical_mode") or "").strip().upper()
bat_sp = int(pi.get("battery_setpoint_w") or 0)
if pm == "CHARGE" or (grid_sp > 0 and bat_sp > 0):
return sp
neg_sell = sell_f is not None and float(sell_f) < 0
plan_no_export = export_mode == "NONE" and grid_sp >= 0
if not neg_sell and not plan_no_export: