Fix SoC balance on battery export and improve evening push (v39).
Some checks failed
CI and deploy / migration-check (push) Failing after 38s
CI and deploy / deploy (push) Has been skipped

SoC continuity now deducts only bd (ge_bat was double-counted via energy
balance), which stopped the plan from draining ~2× faster than BMS during
evening BATTERY_SELL. Also ships dynamic evening push budget + rolling
hysteresis (v38), drops unused fn_soc_tracking_bundle, and adds tests/docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dusan Vojacek
2026-05-29 00:04:48 +02:00
parent 52e4b68789
commit ba0b55bf10
5 changed files with 432 additions and 58 deletions

View File

@@ -11,7 +11,35 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
**Rozhodnutí home-01** (souhrn v [`docs/06-open-questions.md`](06-open-questions.md)): rampa/**T** odvozené z PV B (bez fixních 80 % v LP); TČ ne v pre-neg exportu; bazén min 4 h/den + Shelly; spirála Loxone; **workshop UI flex zátěží před v37** (§ 9.1 strategie).
## 2026-05-28 — BA81 export A+B + rolling drain (v36g)
## 2026-05-28 — Večerní export: dynamický Wh push + hysteresis (v38)
**Problém:** `_evening_battery_export_push_indices` bral jen **málo slotů** v úzkém pásmu `max0,05` a při řazení podle rozpočtu mohl vynechat dražší 15min (9,5 Kč) a exportovat později levněji (4,8 Kč). `evening_early` zákaz `ge_bat` platil jen **před** prvním push slotem.
**Změna (v38):** Kandidáti = **profitable ∩ noční okno**; push = nejdražší sloty **sell desc**, dokud `kumulované_Wh ≤ push_budget` (`discharge_slot_buffer`, SoC nad `min_soc`) — **žádné pevné top-3** (počet slotů závisí na SoC, typ. ~4,3 kWh/slot při 17 kW BMS, home-01 export cap 13,5 kW × 0,25 h ≈ 3,4 kWh/slot v LP). `evening_early` = `ge_bat=0` pro profitable noční sloty pod `peak0,05` mimo `evening_push_ts` (i po prvním push). Rolling **hysteresis** při malé změně peak sell / SoC.
**Soubory:** `backend/services/planning_engine.py`, `backend/tests/test_planning_dispatch_milp.py`, `docs/04-modules/planning.md`.
**Ověření:** `pytest … -k evening`; tag **`2026-05-28-evening-export-dynamic-v38`**. `solver_params.inputs.evening_push_ts` — délka ≈ `floor(push_budget_wh / per_slot_discharge_wh)`.
## 2026-05-28 — SoC bilance: jen `bd`, ne `bd+ge_bat` (v39)
**Problém:** SoC kontinuita odečítala **`bd + ge_bat`**, ale z energetické bilance `pv + gi + bd = load + bc + ge` už platí **`bd ≈ load + ge_bat`** při exportu z baterie → pokles SoC **~2×** rychleji než BMS ve večerním `BATTERY_SELL`. v37 kalibrace (`discharge_calibration_factor`) to jen maskovala.
**Změna (v39):** SoC rovnice: ` bd[t] / discharge_efficiency × interval_h` (bez druhého `ge_bat`). Odstraněno: `fn_soc_tracking_bundle`, `_soc_tracking_bundle`, `discharge_calibration_factor`.
**Soubory:** `backend/services/planning_engine.py`, `db/routines/R__091_fn_soc_tracking_bundle.sql` (drop), `backend/tests/test_planning_dispatch_milp.py` (`SocBalanceDischargeTests`), `docs/04-modules/planning.md`.
**Ověření:** `SocBalanceDischargeTests::test_export_slot_soc_drop_not_double_ge_bat`; MCP po deploy: `planner_build_tag = 2026-05-28-evening-export-soc-balance-v39`, drift `plan_soc vs actual_soc` při večerním výboji.
## 2026-05-28 — SoC tracking + discharge_calibration_factor (v37, nahrazeno v39)
**Problém:** LP bilance SoC při výboji klesala o **1525 %** rychleji než BMS → méně `BATTERY_SELL` ve večerní špičce, energie zbytečně „na zítra“.
**Změna (v37):** `ems.fn_soc_tracking_bundle` + `_soc_tracking_bundle` v rolling replanu; `discharge_calibration_factor` násobí `(bd + ge_bat)` **jen v rovnici kontinuity SoC** (`solve_dispatch`). Konstanty: error práh 3200 Wh, min výboj 1000 Wh, factor clamp 0.51.2.
**Soubory:** `backend/services/planning_engine.py`, `db/routines/R__091_fn_soc_tracking_bundle.sql`, `docs/04-modules/planning.md`.
**Ověření:** `SocTrackingDischargeCalibrationTests`; MCP po večerním výboji: `solver_params->'inputs'->>'discharge_calibration_factor'`, `|plan_soc actual_soc| < 8 %` po ~2 h (cíl &lt; 5 % po doladění). Tag **v37**. **→ Root cause opraven v39; kalibrace zrušena.**
**Problém (v36f):** BA81 — `skip_pv_store` nestačil: `fixed_pv_b_export_cap` držel `ge_pv ≤ pv_b` → curtail pole A. home-01 rolling — prázdné `neg_evening_*` (D1 večer mimo horizont), SoC ~29 % místo ~20 % před `sell<0`.
@@ -19,6 +47,8 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
**Ověření:** `test_ba81_fixed_morning_exports_pv_a_not_curtail`, `test_rolling_horizon_drains_to_reserve_before_first_neg`; tag **v36g**.
**Deploy verified (2026-05-28, MCP `user-postgres-ems`):** Všechny aktivní rolling runy (`home-01`, `BA81`, `KV1`, `hulin-bess`) mají `planner_build_tag = 2026-05-28-neg-prep-window-v36g`. BA81 run 19604: před 1. `sell<0` (29.5. 10:15 Prague) u 19 slotů s PV přebytkem `pv_a_curtailed_w = 0`, `|grid_setpoint_w| = pv_surplus` (0 curtail/export mismatch). home-01 run 19560: `neg_evening_reserve_soc_anchors` délka 2 (kotvy 28.5. 23:45 a 29.5. 10:00 Prague, `target_reserve_soc_wh` 12800), večerní výboj k ~20% SoC před neg oknem.
## 2026-05-28 — Fixed tarif: export FVE před sell&lt;0 (v36f)
**Problém:** BA81 (fixed, sell&gt;3 Kč ráno): plán **curtail** PV A (~3 kW) + export jen **~600 W** (`ge_pv` jen přes pole B). Střídač reálně valí celou FVE — ekonomicky správně, ale plán nesedí. Příčina: `ge_pv=0` při `sell &lt; future_sell` (pv_store); `fixed_pv_b_export_cap` uvolní jen MI.