Files
ems/backend/app/routers/site_configuration.py
Dusan Vojacek 4e81a36371
Some checks failed
test / smoke-test (push) Has been cancelled
deploy / deploy (push) Has been cancelled
stranka configuration
2026-04-12 16:56:44 +02:00

249 lines
8.3 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
from datetime import datetime, timezone
from typing import Annotated, Any
import asyncpg
from fastapi import APIRouter, Depends, HTTPException
from app.db_json import record_to_dict
from app.deps import get_pg_pool
router = APIRouter(prefix="/sites/{site_id}", tags=["sites"])
_DEYE_KEYS = frozenset(
{
"deye_last_system_time_sync_at",
"deye_last_system_time_sync_minute",
"deye_last_tou_inactive_write_prague_date",
"deye_tou_inactive_signature",
}
)
def _mask_secret_reference(raw: str | None) -> str | None:
if raw is None:
return None
s = str(raw).strip()
if not s:
return None
if len(s) <= 4:
return "nastaveno"
return f"{s[-2:]}"
def _iso_utc(dt: datetime | None) -> str | None:
if dt is None:
return None
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc).isoformat()
@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:
site_row = await conn.fetchrow(
"""
SELECT id, code, name, timezone, latitude, longitude, active, notes, created_at
FROM ems.site
WHERE id = $1
""",
site_id,
)
if site_row is None:
raise HTTPException(status_code=404, detail="Site not found")
grid_row = await conn.fetchrow(
"SELECT * FROM ems.site_grid_connection WHERE site_id = $1",
site_id,
)
market_row = await conn.fetchrow(
"""
SELECT *
FROM ems.site_market_config
WHERE site_id = $1
AND valid_from <= now()
AND (valid_to IS NULL OR valid_to > now())
ORDER BY valid_from DESC
LIMIT 1
""",
site_id,
)
endpoint_rows = await conn.fetch(
"""
SELECT id, site_id, endpoint_type, host, port, protocol, unit_id,
auth_reference, enabled, notes
FROM ems.site_endpoint
WHERE site_id = $1
ORDER BY id
""",
site_id,
)
endpoints: list[dict[str, Any]] = []
for er in endpoint_rows:
d = record_to_dict(er)
d["auth_reference"] = _mask_secret_reference(er["auth_reference"])
endpoints.append(d)
inv_rows = await conn.fetch(
"""
SELECT ai.*,
(SELECT ep.host || CASE
WHEN ep.port IS NOT NULL THEN ':' || ep.port::text
ELSE ''
END
FROM ems.site_endpoint ep
WHERE ep.id = ai.endpoint_id) AS endpoint_connection
FROM ems.asset_inverter ai
WHERE ai.site_id = $1
ORDER BY ai.id
""",
site_id,
)
inverters: list[dict[str, Any]] = []
for ir in inv_rows:
full = record_to_dict(ir)
ep_label = full.pop("endpoint_connection", None)
core = {k: v for k, v in full.items() if k not in _DEYE_KEYS}
deye_meta = {k: full[k] for k in _DEYE_KEYS if full.get(k) is not None}
core["endpoint_connection"] = ep_label
core["deye_meta"] = deye_meta if deye_meta else None
inverters.append(core)
bat_rows = await conn.fetch(
"SELECT * FROM ems.asset_battery WHERE site_id = $1 ORDER BY id",
site_id,
)
pv_rows = await conn.fetch(
"SELECT * FROM ems.asset_pv_array WHERE site_id = $1 ORDER BY id",
site_id,
)
ev_rows = await conn.fetch(
"""
SELECT ec.*,
se.host || CASE
WHEN se.port IS NOT NULL THEN ':' || se.port::text
ELSE ''
END AS endpoint_connection
FROM ems.asset_ev_charger ec
LEFT JOIN ems.site_endpoint se ON se.id = ec.endpoint_id
WHERE ec.site_id = $1
ORDER BY ec.id
""",
site_id,
)
ev_chargers = [record_to_dict(r) for r in ev_rows]
veh_rows = await conn.fetch(
"""
SELECT id, site_id, code, name, make, model, battery_capacity_kwh,
max_charge_power_w, default_charger_id, api_type, api_reference,
default_target_soc_pct, default_deadline_hour, active
FROM ems.asset_vehicle
WHERE site_id = $1
ORDER BY code
""",
site_id,
)
vehicles: list[dict[str, Any]] = []
for vr in veh_rows:
d = record_to_dict(vr)
d["api_reference"] = _mask_secret_reference(vr["api_reference"])
vehicles.append(d)
hp_rows = await conn.fetch(
"""
SELECT hp.*,
se.host || CASE
WHEN se.port IS NOT NULL THEN ':' || se.port::text
ELSE ''
END AS endpoint_connection
FROM ems.asset_heat_pump hp
LEFT JOIN ems.site_endpoint se ON se.id = hp.endpoint_id
WHERE hp.site_id = $1
ORDER BY hp.id
""",
site_id,
)
heat_pumps = [record_to_dict(r) for r in hp_rows]
mode_row = await conn.fetchrow(
"""
SELECT m.mode_code, m.activated_at, m.activated_by, m.valid_until,
m.previous_mode, m.notes,
d.name AS mode_name, d.description AS mode_description,
d.loxone_mode_value, d.ev_enabled, d.heat_pump_enabled,
d.battery_mode, d.grid_mode, d.is_autonomous
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,
)
override_rows = await conn.fetch(
"""
SELECT id, override_type, value_json, valid_from, valid_to, reason, created_by, created_at
FROM ems.site_override
WHERE site_id = $1
AND valid_from <= now()
AND (valid_to IS NULL OR valid_to > now())
ORDER BY valid_from DESC
LIMIT 50
""",
site_id,
)
hb_row = await conn.fetchrow(
"SELECT last_seen, status FROM ems.site_heartbeat 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,
)
site = record_to_dict(site_row)
lat = site_row["latitude"]
lon = site_row["longitude"]
site["latitude"] = float(lat) if lat is not None else None
site["longitude"] = float(lon) if lon is not None else None
operating_mode = record_to_dict(mode_row) if mode_row else None
return {
"site": site,
"grid_connection": record_to_dict(grid_row) if grid_row else None,
"market_config": record_to_dict(market_row) if market_row else None,
"market_config_note": (
"Zelený bonus za výrobu je u FVE polí (asset_pv_array), ne v obchodní konfiguraci."
),
"endpoints": endpoints,
"inverters": inverters,
"batteries": [record_to_dict(r) for r in bat_rows],
"pv_arrays": [record_to_dict(r) for r in pv_rows],
"ev_chargers": ev_chargers,
"vehicles": vehicles,
"heat_pumps": heat_pumps,
"operating_mode": operating_mode,
"active_overrides": [record_to_dict(r) for r in override_rows],
"operational": {
"heartbeat_last_seen": _iso_utc(hb_row["last_seen"]) if hb_row else None,
"heartbeat_status": hb_row["status"] if hb_row else None,
"has_active_plan": run_row is not None,
"active_plan_created_at": _iso_utc(run_row["created_at"]) if run_row else None,
},
}