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

@@ -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,

View 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())

View File

@@ -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,

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: