sql first refactor
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user