sql first refactor
Some checks failed
CI and deploy / migration-check (push) Successful in 5s
CI and deploy / deploy (push) Failing after 20s

This commit is contained in:
Dusan Vojacek
2026-04-19 20:02:20 +02:00
parent a02e11ee13
commit 93f883f5e0
74 changed files with 6022 additions and 4014 deletions

View File

@@ -1,10 +1,7 @@
"""`_select_charge_slots`: pre-selection nabíjecích slotů (anti-micro-cycling).
"""Pre-selection nabíjecích slotů (anti-micro-cycling) referenční Python.
Ověřuje novou logiku podle varianty B:
- PV-surplus sloty jsou vždy zahrnuty.
- Zbytek rozpočtu doplnit nejlevnějšími sloty podle `buy_price` (ne `sell_price`).
- Žádné sloty nesmí být vyloučeny kvůli tomu, že nemají PV-surplus, když
`charge_slot_buffer` > 0 a ještě chybí energie do `soc_max`.
Logika je v DB: ems.fn_load_planning_slots_full. Tento soubor drží kopii algoritmu
pro rychlé unit testy bez PostgreSQL.
"""
from __future__ import annotations
@@ -13,7 +10,50 @@ import unittest
from datetime import datetime, timezone
from types import SimpleNamespace
from services.planning_engine import INTERVAL_H, PlanningSlot, _select_charge_slots
from services.planning_engine import INTERVAL_H, PlanningSlot
def _select_charge_slots(
slots: list[PlanningSlot],
battery: SimpleNamespace,
current_soc_wh: float,
) -> set[int]:
"""Kopie logiky z ems.fn_load_planning_slots_full (charge mask)."""
charge_buf = float(getattr(battery, "charge_slot_buffer", 0) or 0)
if charge_buf <= 0:
return set(range(len(slots)))
energy_to_fill = float(battery.soc_max_wh) - float(current_soc_wh)
if energy_to_fill <= 0:
return set()
eta = float(getattr(battery, "charge_efficiency", 1.0) or 1.0)
max_p_w = float(getattr(battery, "max_charge_power_w", 0.0) or 0.0)
per_slot_full_wh = max_p_w * eta * INTERVAL_H
selected: set[int] = set()
for t, s in enumerate(slots):
pv_surplus_w = max(0, s.pv_a_forecast_w + s.pv_b_forecast_w - s.load_baseline_w)
if pv_surplus_w > 0:
selected.add(t)
grid_target_wh = energy_to_fill * charge_buf
if grid_target_wh <= 0 or per_slot_full_wh <= 0:
return selected
grid_candidates = [
(t, float(s.buy_price)) for t, s in enumerate(slots) if t not in selected
]
grid_candidates.sort(key=lambda x: x[1])
cumulative = 0.0
for t, _price in grid_candidates:
if cumulative >= grid_target_wh:
break
selected.add(t)
cumulative += per_slot_full_wh
return selected
def _slot(*, buy: float, sell: float = 1.0, pv: int = 0, load: int = 2_000) -> PlanningSlot: