This commit is contained in:
Dusan Vojacek
2026-03-20 14:29:43 +01:00
parent 8b4af663d8
commit 2cc5ccfda7
4 changed files with 603 additions and 11 deletions

93
backend/app/routers/ev.py Normal file
View File

@@ -0,0 +1,93 @@
"""REST API aktivní EV session a úprava deadline / target SoC."""
from __future__ import annotations
from datetime import datetime
from typing import Annotated, Any
import asyncpg
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, field_validator
from app.db_json import record_to_dict
from app.deps import get_pg_pool
router = APIRouter(prefix="/sites/{site_id}/ev", tags=["ev"])
class EvSessionPatchBody(BaseModel):
target_soc_pct: float | None = None
target_deadline: datetime | None = None
@field_validator("target_soc_pct")
@classmethod
def _soc_range(cls, v: float | None) -> float | None:
if v is not None and not (10 <= v <= 100):
raise ValueError("target_soc_pct must be between 10 and 100")
return v
class EvSessionPatchResponse(BaseModel):
success: bool = True
session_id: int
@router.get("/sessions/active")
async def get_active_ev_sessions(
site_id: int,
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)
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
""",
site_id,
)
return [record_to_dict(r) for r in rows]
@router.patch("/sessions/{session_id}", response_model=EvSessionPatchResponse)
async def patch_ev_session(
site_id: int,
session_id: int,
body: EvSessionPatchBody,
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)
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,
site_id,
)
if row is None:
raise HTTPException(status_code=404, detail="Session not found")
return EvSessionPatchResponse(success=True, session_id=int(row["id"]))