sql first refactor
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from typing import Annotated, Any, Literal
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -10,7 +11,7 @@ import asyncpg
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.db_json import record_to_dict
|
||||
from app.db_json import fetch_json
|
||||
from app.deps import get_pg_pool
|
||||
from app.notifications_logic import (
|
||||
EvSessionRow,
|
||||
@@ -47,6 +48,16 @@ def _iso_utc(dt: datetime | None) -> str | None:
|
||||
return dt.astimezone(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _parse_ts(val: Any) -> datetime | None:
|
||||
if val is None:
|
||||
return None
|
||||
if isinstance(val, datetime):
|
||||
return val
|
||||
if isinstance(val, str):
|
||||
return datetime.fromisoformat(val.replace("Z", "+00:00"))
|
||||
return None
|
||||
|
||||
|
||||
def _age_seconds(at: datetime | None) -> int | None:
|
||||
if at is None:
|
||||
return None
|
||||
@@ -81,174 +92,105 @@ async def get_site_status_full(
|
||||
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
) -> dict[str, Any]:
|
||||
async with pool.acquire() as conn:
|
||||
site = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id, code, name, timezone
|
||||
FROM ems.site
|
||||
WHERE id = $1
|
||||
""",
|
||||
bundle = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_site_full_status($1::int)",
|
||||
site_id,
|
||||
)
|
||||
if site is None:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
if not isinstance(bundle, dict):
|
||||
bundle = json.loads(bundle)
|
||||
if bundle.get("error") == "not_found":
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
tz = site["timezone"] or "Europe/Prague"
|
||||
site = bundle.get("site") or {}
|
||||
mode_row = bundle.get("operating_mode") or {}
|
||||
hb_row = bundle.get("heartbeat") or {}
|
||||
inv_row = bundle.get("inverter_latest")
|
||||
if not isinstance(inv_row, dict):
|
||||
inv_row = None
|
||||
ev_rows = bundle.get("ev_chargers") or []
|
||||
if not isinstance(ev_rows, list):
|
||||
ev_rows = []
|
||||
hp_row = bundle.get("heat_pump_latest")
|
||||
if not isinstance(hp_row, dict):
|
||||
hp_row = None
|
||||
reserve_row = bundle.get("battery_limits") or {}
|
||||
run_row = bundle.get("active_plan")
|
||||
if not isinstance(run_row, dict):
|
||||
run_row = None
|
||||
intervals: list[dict[str, Any]] = []
|
||||
raw_iv = bundle.get("planning_intervals") or []
|
||||
if isinstance(raw_iv, list):
|
||||
intervals = [x for x in raw_iv if isinstance(x, dict)]
|
||||
|
||||
mode_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT m.mode_code, d.name AS mode_name, m.activated_at, m.activated_by
|
||||
FROM ems.site_operating_mode m
|
||||
JOIN ems.operating_mode_def d ON d.code = m.mode_code
|
||||
WHERE m.site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
hb_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT last_seen, status
|
||||
FROM ems.site_heartbeat
|
||||
WHERE site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
inv_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT pv_power_w, battery_soc_percent, grid_power_w, measured_at
|
||||
FROM ems.vw_latest_inverter
|
||||
WHERE site_id = $1
|
||||
ORDER BY measured_at DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
ev_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT DISTINCT ON (charger_id)
|
||||
charger_code AS code,
|
||||
status,
|
||||
power_w,
|
||||
measured_at
|
||||
FROM ems.vw_latest_ev_charger
|
||||
WHERE site_id = $1
|
||||
ORDER BY charger_id, measured_at DESC NULLS LAST
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
hp_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT power_w, tuv_tank_temp_c, measured_at
|
||||
FROM ems.vw_latest_heat_pump
|
||||
WHERE site_id = $1
|
||||
ORDER BY measured_at DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
reserve_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT MIN(reserve_soc_percent)::float AS reserve_soc,
|
||||
MIN(min_soc_percent)::float AS min_soc
|
||||
FROM ems.asset_battery
|
||||
WHERE site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
run_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id, created_at
|
||||
FROM ems.planning_run
|
||||
WHERE site_id = $1 AND status = 'active'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
intervals: list[dict[str, Any]] = []
|
||||
if run_row:
|
||||
int_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT interval_start, battery_setpoint_w,
|
||||
load_baseline_w,
|
||||
pv_a_forecast_raw_w, pv_b_forecast_raw_w,
|
||||
pv_a_forecast_solver_w, pv_b_forecast_solver_w
|
||||
FROM ems.planning_interval
|
||||
WHERE run_id = $1
|
||||
ORDER BY interval_start
|
||||
""",
|
||||
run_row["id"],
|
||||
)
|
||||
intervals = [record_to_dict(r) for r in int_rows]
|
||||
|
||||
tomorrow_slots = await conn.fetchval(
|
||||
"""
|
||||
SELECT COUNT(*)::int
|
||||
FROM ems.vw_site_effective_price v
|
||||
WHERE v.site_id = $1
|
||||
AND (v.interval_start AT TIME ZONE $2)::date =
|
||||
((CURRENT_TIMESTAMP AT TIME ZONE $2)::date + INTERVAL '1 day')::date
|
||||
""",
|
||||
site_id,
|
||||
tz,
|
||||
)
|
||||
tomorrow_slots = int(tomorrow_slots or 0)
|
||||
tomorrow_slots = int(bundle.get("tomorrow_price_slot_count") or 0)
|
||||
|
||||
now_utc = datetime.now(timezone.utc)
|
||||
hb_last = hb_row["last_seen"] if hb_row else None
|
||||
hb_last = hb_row.get("last_seen") if hb_row else None
|
||||
hb_age = _age_seconds(hb_last)
|
||||
inv_measured = inv_row["measured_at"] if inv_row else None
|
||||
inv_measured = inv_row.get("measured_at") if inv_row else None
|
||||
inv_age = _age_seconds(inv_measured)
|
||||
|
||||
next_start, next_bat = _next_plan_interval(intervals, now_utc)
|
||||
|
||||
ev_list: list[dict[str, Any]] = []
|
||||
for r in ev_rows:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
ev_list.append(
|
||||
{
|
||||
"code": r["code"],
|
||||
"status": r["status"],
|
||||
"power_w": int(r["power_w"]) if r["power_w"] is not None else None,
|
||||
"code": r.get("code"),
|
||||
"status": r.get("status"),
|
||||
"power_w": int(r["power_w"]) if r.get("power_w") is not None else None,
|
||||
}
|
||||
)
|
||||
|
||||
telemetry: dict[str, Any] = {
|
||||
"inverter": {
|
||||
"pv_power_w": int(inv_row["pv_power_w"]) if inv_row and inv_row["pv_power_w"] is not None else None,
|
||||
"battery_soc_pct": float(inv_row["battery_soc_percent"])
|
||||
if inv_row and inv_row["battery_soc_percent"] is not None
|
||||
"pv_power_w": int(inv_row["pv_power_w"])
|
||||
if inv_row and inv_row.get("pv_power_w") is not None
|
||||
else None,
|
||||
"battery_soc_pct": float(inv_row["battery_soc_percent"])
|
||||
if inv_row and inv_row.get("battery_soc_percent") is not None
|
||||
else None,
|
||||
"grid_power_w": int(inv_row["grid_power_w"])
|
||||
if inv_row and inv_row.get("grid_power_w") is not None
|
||||
else None,
|
||||
"grid_power_w": int(inv_row["grid_power_w"]) if inv_row and inv_row["grid_power_w"] is not None else None,
|
||||
"measured_at": _iso_utc(inv_measured),
|
||||
"age_seconds": inv_age,
|
||||
},
|
||||
"ev_chargers": ev_list,
|
||||
"heat_pump": {
|
||||
"power_w": int(hp_row["power_w"]) if hp_row and hp_row["power_w"] is not None else None,
|
||||
"power_w": int(hp_row["power_w"]) if hp_row and hp_row.get("power_w") is not None else None,
|
||||
"tank_temp_c": float(hp_row["tuv_tank_temp_c"])
|
||||
if hp_row and hp_row["tuv_tank_temp_c"] is not None
|
||||
if hp_row and hp_row.get("tuv_tank_temp_c") is not None
|
||||
else None,
|
||||
"measured_at": _iso_utc(hp_row["measured_at"]) if hp_row else None,
|
||||
"measured_at": _iso_utc(hp_row.get("measured_at")) if hp_row else None,
|
||||
},
|
||||
}
|
||||
|
||||
has_plan = run_row is not None
|
||||
planning = {
|
||||
"has_active_plan": has_plan,
|
||||
"plan_created_at": _iso_utc(run_row["created_at"]) if run_row else None,
|
||||
"plan_created_at": _iso_utc(run_row.get("created_at")) if run_row else None,
|
||||
"next_interval_start": next_start,
|
||||
"next_battery_setpoint_w": next_bat,
|
||||
}
|
||||
|
||||
mode_code = (mode_row["mode_code"] if mode_row else None) or ""
|
||||
reserve_soc = float(reserve_row["reserve_soc"]) if reserve_row and reserve_row["reserve_soc"] is not None else None
|
||||
min_soc = float(reserve_row["min_soc"]) if reserve_row and reserve_row["min_soc"] is not None else None
|
||||
soc = float(inv_row["battery_soc_percent"]) if inv_row and inv_row["battery_soc_percent"] is not None else None
|
||||
mode_code = (mode_row.get("mode_code") if mode_row else None) or ""
|
||||
reserve_soc = (
|
||||
float(reserve_row["reserve_soc"])
|
||||
if reserve_row and reserve_row.get("reserve_soc") is not None
|
||||
else None
|
||||
)
|
||||
min_soc = (
|
||||
float(reserve_row["min_soc"]) if reserve_row and reserve_row.get("min_soc") is not None else None
|
||||
)
|
||||
soc = (
|
||||
float(inv_row["battery_soc_percent"])
|
||||
if inv_row and inv_row.get("battery_soc_percent") is not None
|
||||
else None
|
||||
)
|
||||
|
||||
alerts: list[dict[str, str]] = []
|
||||
|
||||
@@ -281,17 +223,17 @@ async def get_site_status_full(
|
||||
alerts.sort(key=lambda a: (0 if a["level"] == "error" else 1, a["message"]))
|
||||
|
||||
return {
|
||||
"site": {"id": site["id"], "code": site["code"], "name": site["name"]},
|
||||
"site": {"id": site.get("id"), "code": site.get("code"), "name": site.get("name")},
|
||||
"operating_mode": {
|
||||
"mode_code": mode_row["mode_code"] if mode_row else None,
|
||||
"mode_name": mode_row["mode_name"] if mode_row else None,
|
||||
"activated_at": _iso_utc(mode_row["activated_at"]) if mode_row else None,
|
||||
"activated_by": mode_row["activated_by"] if mode_row else None,
|
||||
"mode_code": mode_row.get("mode_code") if mode_row else None,
|
||||
"mode_name": mode_row.get("mode_name") if mode_row else None,
|
||||
"activated_at": _iso_utc(mode_row.get("activated_at")) if mode_row else None,
|
||||
"activated_by": mode_row.get("activated_by") if mode_row else None,
|
||||
},
|
||||
"heartbeat": {
|
||||
"last_seen": _iso_utc(hb_last),
|
||||
"age_seconds": hb_age,
|
||||
"status": hb_row["status"] if hb_row else None,
|
||||
"status": hb_row.get("status") if hb_row else None,
|
||||
},
|
||||
"telemetry": telemetry,
|
||||
"planning": planning,
|
||||
@@ -395,156 +337,39 @@ async def get_site_notifications(
|
||||
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
) -> SiteNotificationsResponse:
|
||||
async with pool.acquire() as conn:
|
||||
site = await conn.fetchrow(
|
||||
"SELECT id, timezone FROM ems.site WHERE id = $1",
|
||||
ctx = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_site_notifications_context($1::int)",
|
||||
site_id,
|
||||
)
|
||||
if site is None:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
tz = site["timezone"] or "Europe/Prague"
|
||||
if not isinstance(ctx, dict):
|
||||
ctx = json.loads(ctx)
|
||||
if ctx.get("error") == "not_found":
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
mode_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT m.mode_code
|
||||
FROM ems.site_operating_mode m
|
||||
WHERE m.site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
run_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id FROM ems.planning_run
|
||||
WHERE site_id = $1 AND status = 'active'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
reserve_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT MIN(reserve_soc_percent)::float AS reserve_soc,
|
||||
MIN(min_soc_percent)::float AS min_soc
|
||||
FROM ems.asset_battery
|
||||
WHERE site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
inv_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT battery_soc_percent, measured_at
|
||||
FROM ems.vw_latest_inverter
|
||||
WHERE site_id = $1
|
||||
ORDER BY measured_at DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
hb_row = await conn.fetchrow(
|
||||
"SELECT last_seen FROM ems.site_heartbeat WHERE site_id = $1",
|
||||
site_id,
|
||||
)
|
||||
tomorrow_slots = await conn.fetchval(
|
||||
"""
|
||||
SELECT COUNT(*)::int
|
||||
FROM ems.vw_site_effective_price v
|
||||
WHERE v.site_id = $1
|
||||
AND (v.interval_start AT TIME ZONE $2)::date =
|
||||
((CURRENT_TIMESTAMP AT TIME ZONE $2)::date + INTERVAL '1 day')::date
|
||||
""",
|
||||
site_id,
|
||||
tz,
|
||||
)
|
||||
has_plan = bool(ctx.get("has_plan"))
|
||||
mode_code = (ctx.get("mode_code") or "") or ""
|
||||
reserve_soc = _float_or_none(ctx.get("reserve_soc"))
|
||||
min_soc = _float_or_none(ctx.get("min_soc"))
|
||||
soc = _float_or_none(ctx.get("soc_pct"))
|
||||
inv_age = _age_seconds(_parse_ts(ctx.get("inv_measured_at")))
|
||||
hb_age = _age_seconds(_parse_ts(ctx.get("hb_last_seen")))
|
||||
tomorrow_slots = int(ctx.get("tomorrow_slots") or 0)
|
||||
|
||||
price_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT interval_start,
|
||||
effective_buy_price_czk_kwh,
|
||||
effective_sell_price_czk_kwh
|
||||
FROM ems.vw_site_effective_price
|
||||
WHERE site_id = $1
|
||||
AND interval_start >= now()
|
||||
AND interval_start < now() + INTERVAL '48 hours'
|
||||
ORDER BY interval_start
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
price_rows = ctx.get("price_slots") or []
|
||||
if not isinstance(price_rows, list):
|
||||
price_rows = []
|
||||
|
||||
avg_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT AVG(effective_buy_price_czk_kwh)::float AS avg_buy
|
||||
FROM ems.vw_site_effective_price
|
||||
WHERE site_id = $1
|
||||
AND interval_start::date IN (CURRENT_DATE, CURRENT_DATE + INTERVAL '1 day')
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
avg_buy = _float_or_none(ctx.get("avg_buy"))
|
||||
usable_wh = _float_or_none(ctx.get("usable_wh"))
|
||||
|
||||
bat_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT COALESCE(SUM(ab.usable_capacity_wh), 0)::float AS usable_wh
|
||||
FROM ems.asset_battery ab
|
||||
JOIN ems.asset_inverter ai ON ai.id = ab.inverter_id
|
||||
WHERE ai.site_id = $1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
ev_rows = ctx.get("ev_sessions") or []
|
||||
if not isinstance(ev_rows, list):
|
||||
ev_rows = []
|
||||
|
||||
ev_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT DISTINCT ON (es.id)
|
||||
es.id,
|
||||
es.charger_id,
|
||||
es.energy_delivered_wh,
|
||||
es.target_soc_pct,
|
||||
es.session_start,
|
||||
es.soc_at_connect_pct,
|
||||
COALESCE(av_id.battery_capacity_kwh, av_def.battery_capacity_kwh) AS battery_capacity_kwh,
|
||||
COALESCE(av_id.make, av_def.make) AS make,
|
||||
COALESCE(av_id.model, av_def.model) AS model,
|
||||
COALESCE(av_id.default_target_soc_pct, av_def.default_target_soc_pct) AS default_target_soc_pct,
|
||||
ac.code AS charger_code
|
||||
FROM ems.ev_session es
|
||||
JOIN ems.asset_ev_charger ac ON ac.id = es.charger_id
|
||||
LEFT JOIN ems.asset_vehicle av_id ON av_id.id = es.vehicle_id
|
||||
LEFT JOIN ems.asset_vehicle av_def
|
||||
ON av_def.default_charger_id = ac.id AND es.vehicle_id IS NULL
|
||||
WHERE es.site_id = $1 AND es.session_end IS NULL
|
||||
ORDER BY es.id, av_def.id NULLS LAST
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
neg_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT predicted_date, window_start_hour, window_end_hour, probability_pct
|
||||
FROM ems.predicted_negative_price_window
|
||||
WHERE site_id = $1
|
||||
AND predicted_date BETWEEN CURRENT_DATE AND CURRENT_DATE + 2
|
||||
AND probability_pct >= 50
|
||||
ORDER BY predicted_date, window_start_hour
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
|
||||
has_plan = run_row is not None
|
||||
mode_code = (mode_row["mode_code"] if mode_row else None) or ""
|
||||
reserve_soc = (
|
||||
float(reserve_row["reserve_soc"])
|
||||
if reserve_row and reserve_row["reserve_soc"] is not None
|
||||
else None
|
||||
)
|
||||
min_soc = (
|
||||
float(reserve_row["min_soc"])
|
||||
if reserve_row and reserve_row["min_soc"] is not None
|
||||
else None
|
||||
)
|
||||
soc = (
|
||||
float(inv_row["battery_soc_percent"])
|
||||
if inv_row and inv_row["battery_soc_percent"] is not None
|
||||
else None
|
||||
)
|
||||
inv_age = _age_seconds(inv_row["measured_at"] if inv_row else None)
|
||||
hb_age = _age_seconds(hb_row["last_seen"] if hb_row else None)
|
||||
neg_rows = ctx.get("neg_windows") or []
|
||||
if not isinstance(neg_rows, list):
|
||||
neg_rows = []
|
||||
|
||||
infra = _infrastructure_notification_items(
|
||||
has_plan=has_plan,
|
||||
@@ -559,11 +384,15 @@ async def get_site_notifications(
|
||||
|
||||
prices: list[PriceSlot] = []
|
||||
for r in price_rows:
|
||||
buy = _float_or_none(r["effective_buy_price_czk_kwh"])
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
buy = _float_or_none(r.get("effective_buy_price_czk_kwh"))
|
||||
if buy is None:
|
||||
continue
|
||||
sell_v = _float_or_none(r["effective_sell_price_czk_kwh"])
|
||||
istart = r["interval_start"]
|
||||
sell_v = _float_or_none(r.get("effective_sell_price_czk_kwh"))
|
||||
istart = r.get("interval_start")
|
||||
if isinstance(istart, str):
|
||||
istart = datetime.fromisoformat(istart.replace("Z", "+00:00"))
|
||||
prices.append(
|
||||
PriceSlot(
|
||||
interval_start=istart,
|
||||
@@ -572,43 +401,50 @@ async def get_site_notifications(
|
||||
)
|
||||
)
|
||||
|
||||
avg_buy = _float_or_none(avg_row["avg_buy"]) if avg_row else None
|
||||
usable_wh = _float_or_none(bat_row["usable_wh"]) if bat_row else None
|
||||
battery_kwh = (usable_wh / 1000.0) if usable_wh is not None else None
|
||||
|
||||
ev_sessions: list[EvSessionRow] = []
|
||||
for er in ev_rows:
|
||||
if not isinstance(er, dict):
|
||||
continue
|
||||
ss = er.get("session_start")
|
||||
if isinstance(ss, str):
|
||||
ss = datetime.fromisoformat(ss.replace("Z", "+00:00"))
|
||||
ev_sessions.append(
|
||||
EvSessionRow(
|
||||
id=int(er["id"]),
|
||||
charger_id=int(er["charger_id"]),
|
||||
energy_delivered_wh=float(er["energy_delivered_wh"] or 0),
|
||||
target_soc_pct=_float_or_none(er["target_soc_pct"]),
|
||||
session_start=er["session_start"],
|
||||
battery_capacity_kwh=_float_or_none(er["battery_capacity_kwh"]),
|
||||
make=er["make"],
|
||||
model=er["model"],
|
||||
default_target_soc_pct=_float_or_none(er["default_target_soc_pct"]),
|
||||
charger_code=str(er["charger_code"] or ""),
|
||||
soc_at_connect_pct=_float_or_none(er["soc_at_connect_pct"]),
|
||||
energy_delivered_wh=float(er.get("energy_delivered_wh") or 0),
|
||||
target_soc_pct=_float_or_none(er.get("target_soc_pct")),
|
||||
session_start=ss,
|
||||
battery_capacity_kwh=_float_or_none(er.get("battery_capacity_kwh")),
|
||||
make=er.get("make"),
|
||||
model=er.get("model"),
|
||||
default_target_soc_pct=_float_or_none(er.get("default_target_soc_pct")),
|
||||
charger_code=str(er.get("charger_code") or ""),
|
||||
soc_at_connect_pct=_float_or_none(er.get("soc_at_connect_pct")),
|
||||
)
|
||||
)
|
||||
|
||||
neg_windows: list[NegWindowRow] = []
|
||||
for nr in neg_rows:
|
||||
dr = nr["predicted_date"]
|
||||
if not isinstance(nr, dict):
|
||||
continue
|
||||
dr = nr.get("predicted_date")
|
||||
if isinstance(dr, datetime):
|
||||
d_conv = dr.date()
|
||||
elif isinstance(dr, date):
|
||||
d_conv = dr
|
||||
elif isinstance(dr, str):
|
||||
d_conv = date.fromisoformat(dr[:10])
|
||||
else:
|
||||
d_conv = date.today()
|
||||
neg_windows.append(
|
||||
NegWindowRow(
|
||||
predicted_date=d_conv,
|
||||
window_start_hour=int(nr["window_start_hour"]),
|
||||
window_end_hour=int(nr["window_end_hour"]),
|
||||
probability_pct=int(nr["probability_pct"]),
|
||||
window_start_hour=int(nr.get("window_start_hour") or 0),
|
||||
window_end_hour=int(nr.get("window_end_hour") or 0),
|
||||
probability_pct=int(nr.get("probability_pct") or 0),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user