This commit is contained in:
Dusan Vojacek
2026-03-20 14:30:03 +01:00
parent 2cc5ccfda7
commit 897b95f728
48 changed files with 4034 additions and 842 deletions

View File

@@ -349,8 +349,6 @@ async def run_daily_plan(site_id: int, db, triggered_by: str = "scheduler:daily"
logger.info(f"[site={site_id}] Daily plan: {horizon_from}{horizon_to}")
slots = await _load_slots(site_id, horizon_from, horizon_to, db)
if not slots:
raise RuntimeError(f"No planning slots for site_id={site_id} (prices/forecast horizon?)")
battery, hp, grid, vehicles, ev_sessions, soc_wh, tuv_temp = await _load_site_context(
site_id, db
@@ -430,9 +428,6 @@ async def run_rolling_replan(
correction_factor, correction_log = await compute_correction_factor(site_id, now, db)
slots = await _load_slots(site_id, replan_from, horizon_to, db)
if not slots:
logger.warning(f"[site={site_id}] Rolling replan: no slots, running daily plan")
return await run_daily_plan(site_id, db, triggered_by=triggered_by)
slots = apply_forecast_correction(slots, now, correction_factor)
@@ -477,7 +472,13 @@ async def run_rolling_replan(
return run_id, duration_ms
async def run_plan_api(site_id: int, db, plan_type: str, triggered_by: str = "api") -> tuple[int, int]:
async def run_plan_api(
site_id: int,
plan_type: str,
db,
*,
triggered_by: str = "api",
) -> tuple[int, int]:
"""Ruční / UI spuštění plánu. Vždy vrátí (run_id, solver_duration_ms)."""
pt = plan_type.lower().strip()
if pt == "daily":
@@ -671,10 +672,10 @@ async def _load_site_context(site_id: int, db):
site_id,
)
if soc_pct is None:
soc_wh = reserve_wh
soc_wh = uc * 0.5
else:
soc_wh = float(soc_pct) / 100.0 * uc
soc_wh = max(reserve_wh, min(soc_wh, soc_max_wh))
soc_wh = max(reserve_wh, min(soc_wh, soc_max_wh))
tuv = await db.fetchval(
"""
@@ -701,9 +702,9 @@ async def _load_slots(site_id, from_dt, to_dt, db) -> list[PlanningSlot]:
COALESCE(fpi_a.power_w, 0) AS pv_a_forecast_w,
COALESCE(fpi_b.power_w, 0) AS pv_b_forecast_w,
COALESCE(cbi.power_w, 500) AS load_baseline_w,
-- EV připojení z aktuálního stavu nabíječek
(ev1.status NOT IN ('available', 'unavailable')) AS ev1_connected,
(ev2.status NOT IN ('available', 'unavailable')) AS ev2_connected
-- EV připojení z poslední telemetrie nabíječek (bez řádku = nepřipojeno)
(COALESCE(ev1.status, 'available') NOT IN ('available', 'unavailable')) AS ev1_connected,
(COALESCE(ev2.status, 'available') NOT IN ('available', 'unavailable')) AS ev2_connected
FROM ems.vw_site_effective_price ep
-- FVE pole A forecast
LEFT JOIN LATERAL (
@@ -762,6 +763,10 @@ async def _load_slots(site_id, from_dt, to_dt, db) -> list[PlanningSlot]:
ev2_connected=bool(d["ev2_connected"]),
)
)
if not out:
raise RuntimeError(
"No planning slots available check market prices and horizon settings"
)
return out