sql first refactor
This commit is contained in:
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import unittest
|
||||
from dataclasses import replace
|
||||
|
||||
from services.control_exporter import (
|
||||
from services.control.exporter_monolith import (
|
||||
ControlSetpoints,
|
||||
InverterConfig,
|
||||
_deye_reg178_verify_with_double_read,
|
||||
|
||||
28
backend/tests/test_db_json_fetch_json.py
Normal file
28
backend/tests/test_db_json_fetch_json.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Smoke: fetch_json toleruje dict z asyncpg (bez reálné DB)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from app.db_json import fetch_json
|
||||
|
||||
|
||||
def test_fetch_json_returns_dict() -> None:
|
||||
async def _run() -> None:
|
||||
conn = AsyncMock()
|
||||
conn.fetchval = AsyncMock(return_value={"a": 1})
|
||||
out = await fetch_json(conn, "select ems.fn_x()", 1)
|
||||
assert out == {"a": 1}
|
||||
|
||||
asyncio.run(_run())
|
||||
|
||||
|
||||
def test_fetch_json_parses_str() -> None:
|
||||
async def _run() -> None:
|
||||
conn = AsyncMock()
|
||||
conn.fetchval = AsyncMock(return_value='{"b": 2}')
|
||||
out = await fetch_json(conn, "select 1")
|
||||
assert out == {"b": 2}
|
||||
|
||||
asyncio.run(_run())
|
||||
@@ -6,7 +6,7 @@ import unittest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
from services.control_exporter import (
|
||||
from services.control.exporter_monolith import (
|
||||
DEYE_CLOCK_DRIFT_OK_SEC,
|
||||
DEYE_CLOCK_RESYNC_INTERVAL_HOURS,
|
||||
DEYE_CLOCK_VERIFY_MAX_DELTA_SEC,
|
||||
|
||||
@@ -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