Add export plan guard to block Deye export against plan.
Some checks failed
CI and deploy / migration-check (push) Failing after 39s
CI and deploy / deploy (push) Has been skipped

Force PASSIVE/no-export when sell is negative or export_mode is NONE,
and alert NEG_SELL_EXPORT in plan_actual_slot_guard when export still occurs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dusan Vojacek
2026-05-29 00:14:52 +02:00
parent 620a557a89
commit a7dff75e58
8 changed files with 230 additions and 4 deletions

View File

@@ -58,6 +58,21 @@ Ověření: logy backendu kolem pokusu **nebo** `select id,status,created_at fro
---
## Exekuční pojistky exportu (AUTO)
Po `_build_setpoints`, před zápisem Modbus (`orchestrator.export_setpoints`):
| Guard | Podmínka | Efekt |
|-------|----------|--------|
| **`_apply_export_plan_guard`** | `effective_sell_price < 0` **nebo** (`export_mode = NONE` a `grid_setpoint_w ≥ 0`) | PASSIVE, `export_ban`, `grid_export_limit = 0`, vybíjení baterie do sítě vynulováno (`battery_w = max(0, …)`), `deye_physical_mode = PASSIVE` |
| **`_apply_price_failsafe_guard`** | `is_predicted_price = true` | PASSIVE, všechny výkonové setpointy 0, žádný export |
Implementace: `backend/services/control/setpoints.py`. Ověření: `pytest backend/tests/test_control_export_plan_guard.py`.
**Poznámka:** PV B (nekontrolovatelné pole) může při záporné vykupní stále fyzicky exportovat — pojistka řídí Deye (baterie + řízené FVE A), ne mikroinvertory na GEN bez cut-off.
---
## Logika exportu
```python

View File

@@ -60,7 +60,7 @@ pro **reg 178** (spolu s peak shaving bity 45).
| Job | Frekvence | Popis |
|-----|-----------|--------|
| `verify_modbus` | každé **2 min** | Pro každou aktivní site vybere `written` příkazy s `written_at` v posledních **20 min** a zavolá `verify_modbus_commands`. |
| `plan_actual_slot_guard` | **:05, :20, :35, :50** (po `audit_filler`) | `ems.fn_plan_actual_slot_guard_all_active` (+ `plan_actual_slot_guard.py` jen Discord): poslední 2 uzavřené 15min sloty — fatální odchylka **plán vs. audit síť****Discord** (`critical`), dedup přes `ems.plan_fatal_deviation_sent`. |
| `plan_actual_slot_guard` | **:05, :20, :35, :50** (po `audit_filler`) | `ems.fn_plan_actual_slot_guard_all_active` (+ `plan_actual_slot_guard.py` jen Discord): poslední 2 uzavřené 15min sloty — fatální odchylka **plán vs. audit síť****Discord** (`critical`), dedup přes `ems.plan_fatal_deviation_sent`. Kódy: `GRID_SIGN_MISMATCH`, `GRID_EXPORT_SPIKE`, **`NEG_SELL_EXPORT`** (`sell < 0` a skutečný vývoz &lt; 4 kW), `GRID_LARGE_DEVIATION`, … Exekuční pojistka proti opakovanému vývozu: [`control.md`](control.md) — `_apply_export_plan_guard`. |
Plná tabulka jobů je v [`lifespan.py`](../../backend/app/lifespan.py).