sql first refactor
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from typing import Annotated, Any
|
||||
|
||||
@@ -9,7 +10,7 @@ import asyncpg
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
from app.db_json import record_to_dict
|
||||
from app.db_json import fetch_json
|
||||
from app.deps import get_pg_pool
|
||||
|
||||
router = APIRouter(prefix="/sites/{site_id}/ev", tags=["ev"])
|
||||
@@ -38,30 +39,19 @@ async def get_active_ev_sessions(
|
||||
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
) -> list[dict[str, Any]]:
|
||||
async with pool.acquire() as conn:
|
||||
site_ok = await conn.fetchval("SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id)
|
||||
site_ok = await conn.fetchval(
|
||||
"SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id
|
||||
)
|
||||
if not site_ok:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT es.id, es.charger_id, es.vehicle_id,
|
||||
es.session_start, es.energy_delivered_wh,
|
||||
es.target_soc_pct, es.target_deadline,
|
||||
av.make, av.model, av.battery_capacity_kwh,
|
||||
av.default_target_soc_pct, av.default_deadline_hour,
|
||||
ac.code AS charger_code,
|
||||
COALESCE(
|
||||
NULLIF(TRIM(CONCAT_WS(' ', ac.manufacturer, ac.model)), ''),
|
||||
ac.code
|
||||
) AS charger_name
|
||||
FROM ems.ev_session es
|
||||
LEFT JOIN ems.asset_vehicle av ON av.id = es.vehicle_id
|
||||
JOIN ems.asset_ev_charger ac ON ac.id = es.charger_id
|
||||
WHERE es.site_id = $1 AND es.session_end IS NULL
|
||||
ORDER BY es.session_start DESC
|
||||
""",
|
||||
rows = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_ev_sessions_active($1::int)",
|
||||
site_id,
|
||||
)
|
||||
return [record_to_dict(r) for r in rows]
|
||||
if not isinstance(rows, list):
|
||||
rows = json.loads(rows) if isinstance(rows, str) else []
|
||||
return [r for r in rows if isinstance(r, dict)]
|
||||
|
||||
|
||||
@router.patch("/sessions/{session_id}", response_model=EvSessionPatchResponse)
|
||||
@@ -72,25 +62,25 @@ async def patch_ev_session(
|
||||
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
) -> EvSessionPatchResponse:
|
||||
async with pool.acquire() as conn:
|
||||
site_ok = await conn.fetchval("SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id)
|
||||
site_ok = await conn.fetchval(
|
||||
"SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id
|
||||
)
|
||||
if not site_ok:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
UPDATE ems.ev_session
|
||||
SET target_soc_pct = $1, target_deadline = $2
|
||||
WHERE id = $3 AND site_id = $4
|
||||
RETURNING id
|
||||
""",
|
||||
body.target_soc_pct,
|
||||
body.target_deadline,
|
||||
session_id,
|
||||
patch = body.model_dump(exclude_unset=True)
|
||||
raw = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_ev_session_apply_patch($1::int, $2::int, $3::jsonb)",
|
||||
site_id,
|
||||
session_id,
|
||||
json.dumps(patch),
|
||||
)
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
return EvSessionPatchResponse(success=True, session_id=int(row["id"]))
|
||||
if not isinstance(raw, dict):
|
||||
raw = json.loads(raw)
|
||||
if not raw.get("success"):
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
return EvSessionPatchResponse(success=True, session_id=int(raw["session_id"]))
|
||||
|
||||
|
||||
class ArrivalHourItem(BaseModel):
|
||||
@@ -114,65 +104,48 @@ async def get_ev_arrival_prediction(
|
||||
site_id: int,
|
||||
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
) -> EvArrivalPredictionResponse:
|
||||
"""Top hodiny příjezdu z ems.fn_ev_expected_arrival; při <5 session celkem insufficient_data."""
|
||||
async with pool.acquire() as conn:
|
||||
site_ok = await conn.fetchval("SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id)
|
||||
if not site_ok:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
n_sessions = int(
|
||||
await conn.fetchval(
|
||||
"SELECT COUNT(*)::int FROM ems.ev_session WHERE site_id = $1",
|
||||
site_id,
|
||||
)
|
||||
or 0
|
||||
)
|
||||
insufficient = n_sessions < 5
|
||||
|
||||
tomorrow = await conn.fetchval(
|
||||
"""
|
||||
SELECT (
|
||||
CURRENT_TIMESTAMP AT TIME ZONE COALESCE(
|
||||
NULLIF(TRIM(timezone), ''),
|
||||
'Europe/Prague'
|
||||
)
|
||||
)::date + 1
|
||||
FROM ems.site
|
||||
WHERE id = $1
|
||||
""",
|
||||
raw = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_ev_arrival_prediction_bundle($1::int)",
|
||||
site_id,
|
||||
)
|
||||
if tomorrow is None:
|
||||
raise HTTPException(status_code=500, detail="Site date resolution failed")
|
||||
tomorrow_d: date = tomorrow
|
||||
if not isinstance(raw, dict):
|
||||
raw = json.loads(raw)
|
||||
if raw.get("error") == "site_not_found":
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
chargers_rows = await conn.fetch(
|
||||
"SELECT id, code FROM ems.asset_ev_charger WHERE site_id = $1 ORDER BY id",
|
||||
site_id,
|
||||
)
|
||||
|
||||
chargers: dict[str, ChargerTomorrowArrival] = {}
|
||||
for ch in chargers_rows:
|
||||
code = str(ch["code"])
|
||||
preds = await conn.fetch(
|
||||
"SELECT * FROM ems.fn_ev_expected_arrival($1, $2, $3::date)",
|
||||
site_id,
|
||||
ch["id"],
|
||||
tomorrow_d,
|
||||
)
|
||||
chargers[code] = ChargerTomorrowArrival(
|
||||
tomorrow=[
|
||||
ArrivalHourItem(
|
||||
hour=int(r["expected_hour"]),
|
||||
confidence_pct=int(r["confidence_pct"]),
|
||||
samples=int(r["sample_count"]),
|
||||
chargers: dict[str, ChargerTomorrowArrival] = {}
|
||||
ch_raw = raw.get("chargers") or {}
|
||||
if isinstance(ch_raw, dict):
|
||||
for code, v in ch_raw.items():
|
||||
if not isinstance(v, dict):
|
||||
continue
|
||||
tlist = v.get("tomorrow") or []
|
||||
items: list[ArrivalHourItem] = []
|
||||
if isinstance(tlist, list):
|
||||
for it in tlist:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
items.append(
|
||||
ArrivalHourItem(
|
||||
hour=int(it.get("hour") or 0),
|
||||
confidence_pct=int(it.get("confidence_pct") or 0),
|
||||
samples=int(it.get("samples") or 0),
|
||||
)
|
||||
)
|
||||
for r in preds
|
||||
]
|
||||
)
|
||||
chargers[str(code)] = ChargerTomorrowArrival(tomorrow=items)
|
||||
|
||||
td = raw.get("tomorrow_date")
|
||||
if isinstance(td, date):
|
||||
td_s = td.isoformat()
|
||||
elif isinstance(td, datetime):
|
||||
td_s = td.date().isoformat()
|
||||
else:
|
||||
td_s = str(td or "")
|
||||
|
||||
return EvArrivalPredictionResponse(
|
||||
insufficient_data=insufficient,
|
||||
tomorrow_date=tomorrow_d.isoformat(),
|
||||
insufficient_data=bool(raw.get("insufficient_data")),
|
||||
tomorrow_date=td_s,
|
||||
chargers=chargers,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user