Files
ems/backend/app/routers/site_configuration.py
Dusan Vojacek 93f883f5e0
Some checks failed
CI and deploy / migration-check (push) Successful in 5s
CI and deploy / deploy (push) Failing after 20s
sql first refactor
2026-04-19 20:02:20 +02:00

122 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""GET /sites/{site_id}/configuration read-only souhrn konfigurace lokality."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Annotated, Any
import asyncpg
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from app.db_json import fetch_json
from app.deps import get_pg_pool
router = APIRouter(prefix="/sites/{site_id}", tags=["sites"])
class InverterModbusCurrentCapsBody(BaseModel):
"""Tvrdý strop proudu pro zápis Deye reg 108/109 (A); NULL ve JSONu = smaž strop v DB."""
deye_register_max_charge_a: int | None = Field(
default=None,
ge=0,
le=640,
description="None při vynechání klíče = nezměnit; explicitní null = smazat strop",
)
deye_register_max_discharge_a: int | None = Field(
default=None,
ge=0,
le=640,
description="Jako u nabíjení",
)
def _iso_utc_from_cfg(val: Any) -> str | None:
if val is None:
return None
if isinstance(val, str):
return val
if isinstance(val, datetime):
dt = val
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc).isoformat()
return str(val)
@router.get("/configuration")
async def get_site_configuration(
site_id: int,
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
async with pool.acquire() as conn:
raw = await fetch_json(
conn,
"select ems.fn_site_configuration($1::int)",
site_id,
)
if raw is None:
raise HTTPException(status_code=404, detail="Site not found")
if not isinstance(raw, dict):
raw = json.loads(raw)
op = raw.get("operational")
if isinstance(op, dict):
op = dict(op)
op["heartbeat_last_seen"] = _iso_utc_from_cfg(op.get("heartbeat_last_seen"))
op["active_plan_created_at"] = _iso_utc_from_cfg(op.get("active_plan_created_at"))
raw["operational"] = op
lat = raw.get("site", {}).get("latitude") if isinstance(raw.get("site"), dict) else None
lon = raw.get("site", {}).get("longitude") if isinstance(raw.get("site"), dict) else None
if isinstance(raw.get("site"), dict):
site = dict(raw["site"])
site["latitude"] = float(lat) if lat is not None else None
site["longitude"] = float(lon) if lon is not None else None
raw["site"] = site
return raw
@router.patch("/inverters/{inverter_id}/modbus-current-caps")
async def patch_inverter_modbus_current_caps(
site_id: int,
inverter_id: int,
body: InverterModbusCurrentCapsBody,
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
"""
Nastavení `deye_register_max_charge_a` / `deye_register_max_discharge_a` na `ems.asset_inverter`.
"""
updates = body.model_dump(exclude_unset=True)
if not updates:
raise HTTPException(
status_code=400,
detail="Send at least one of: deye_register_max_charge_a, deye_register_max_discharge_a",
)
patch: dict[str, Any] = {}
if "deye_register_max_charge_a" in updates:
patch["deye_register_max_charge_a"] = updates["deye_register_max_charge_a"]
if "deye_register_max_discharge_a" in updates:
patch["deye_register_max_discharge_a"] = updates["deye_register_max_discharge_a"]
async with pool.acquire() as conn:
raw = await fetch_json(
conn,
"select ems.fn_inverter_modbus_caps_patch($1::int, $2::int, $3::jsonb)",
site_id,
inverter_id,
json.dumps(patch),
)
if not isinstance(raw, dict):
raw = json.loads(raw)
if not raw.get("ok"):
if raw.get("error") == "not_found":
raise HTTPException(status_code=404, detail="Inverter not found for this site")
raise HTTPException(status_code=400, detail=raw.get("error", "patch_failed"))
return {
"inverter_id": int(raw["inverter_id"]),
"code": raw["code"],
"deye_register_max_charge_a": raw.get("deye_register_max_charge_a"),
"deye_register_max_discharge_a": raw.get("deye_register_max_discharge_a"),
}