sql first refactor
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
from typing import Annotated, Any
|
||||
@@ -10,6 +11,7 @@ import asyncpg
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.db_json import fetch_json
|
||||
from app.deps import get_pg_pool
|
||||
|
||||
router = APIRouter(
|
||||
@@ -105,56 +107,14 @@ async def _check_site(conn: asyncpg.Connection, site_id: int) -> None:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
|
||||
async def _has_green_bonus(conn: asyncpg.Connection, site_id: int) -> bool:
|
||||
return bool(
|
||||
await conn.fetchval(
|
||||
"""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM ems.asset_pv_array
|
||||
WHERE site_id = $1
|
||||
AND green_bonus_czk_kwh IS NOT NULL
|
||||
)
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _safe_get(record: Any, key: str, fallback: Any = None) -> Any:
|
||||
"""Safely get a key from asyncpg Record (which supports [] but not .get())."""
|
||||
try:
|
||||
return record[key]
|
||||
except (KeyError, TypeError):
|
||||
return fallback
|
||||
|
||||
|
||||
def _daily_from_row(r: Any, lock: Any | None, is_locked: bool) -> DailyEconomics:
|
||||
src = lock if (lock and is_locked) else r
|
||||
return DailyEconomics(
|
||||
day=r["day_local"],
|
||||
interval_count=r["interval_count"],
|
||||
import_kwh=_num(r["import_kwh"]),
|
||||
export_kwh=_num(r["export_kwh"]),
|
||||
pv_kwh=_num(r["pv_kwh"]),
|
||||
load_kwh=_num(r["load_kwh"]),
|
||||
pv_self_consumption_kwh=_num(r["pv_self_consumption_kwh"]),
|
||||
ev_kwh=_num(r["ev_kwh"]),
|
||||
hp_kwh=_num(r["hp_kwh"]),
|
||||
import_cost_czk=_num(src["import_cost_czk"]),
|
||||
export_revenue_czk=_num(src["export_revenue_czk"]),
|
||||
grid_import_cashflow_czk=_num(
|
||||
_safe_get(src, "grid_import_cashflow_czk", r["grid_import_cashflow_czk"])
|
||||
),
|
||||
grid_export_revenue_czk=_num(
|
||||
_safe_get(src, "grid_export_revenue_czk", r["grid_export_revenue_czk"])
|
||||
),
|
||||
net_cost_czk=_num(src["net_cost_czk"]),
|
||||
green_bonus_czk=_num(src["green_bonus_czk"]),
|
||||
total_balance_czk=_num(src["total_balance_czk"]),
|
||||
planned_balance_czk=_opt(r["planned_balance_czk"]),
|
||||
deviation_cost_czk=_opt(r["deviation_cost_czk"]),
|
||||
is_locked=is_locked,
|
||||
)
|
||||
def _parse_day(val: Any) -> date:
|
||||
if isinstance(val, datetime):
|
||||
return val.date()
|
||||
if isinstance(val, date):
|
||||
return val
|
||||
if isinstance(val, str):
|
||||
return date.fromisoformat(val[:10])
|
||||
raise ValueError(val)
|
||||
|
||||
|
||||
@router.get("/daily", response_model=DailyEconomicsResponse)
|
||||
@@ -179,41 +139,47 @@ async def get_economics_daily(
|
||||
|
||||
async with db.acquire() as conn:
|
||||
await _check_site(conn, site_id)
|
||||
has_bonus = await _has_green_bonus(conn, site_id)
|
||||
|
||||
dyn_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT * FROM ems.vw_economics_daily
|
||||
WHERE site_id = $1
|
||||
AND day_local >= $2
|
||||
AND day_local < $3
|
||||
ORDER BY day_local
|
||||
""",
|
||||
raw = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_economics_daily_month($1::int, $2::date, $3::date)",
|
||||
site_id,
|
||||
month_start,
|
||||
month_end,
|
||||
)
|
||||
|
||||
lock_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT * FROM ems.audit_day_lock
|
||||
WHERE site_id = $1
|
||||
AND day_local >= $2
|
||||
AND day_local < $3
|
||||
""",
|
||||
site_id,
|
||||
month_start,
|
||||
month_end,
|
||||
)
|
||||
locks = {r["day_local"]: r for r in lock_rows}
|
||||
|
||||
if not isinstance(raw, dict):
|
||||
raw = json.loads(raw)
|
||||
days_in: list[Any] = list(raw.get("days") or [])
|
||||
days: list[DailyEconomics] = []
|
||||
for r in dyn_rows:
|
||||
d = r["day_local"]
|
||||
lock = locks.get(d)
|
||||
days.append(_daily_from_row(r, lock, is_locked=lock is not None))
|
||||
|
||||
return DailyEconomicsResponse(days=days, has_green_bonus=has_bonus)
|
||||
for d in days_in:
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
days.append(
|
||||
DailyEconomics(
|
||||
day=_parse_day(d.get("day")),
|
||||
interval_count=int(d.get("interval_count") or 0),
|
||||
import_kwh=_num(d.get("import_kwh")),
|
||||
export_kwh=_num(d.get("export_kwh")),
|
||||
pv_kwh=_num(d.get("pv_kwh")),
|
||||
load_kwh=_num(d.get("load_kwh")),
|
||||
pv_self_consumption_kwh=_num(d.get("pv_self_consumption_kwh")),
|
||||
ev_kwh=_num(d.get("ev_kwh")),
|
||||
hp_kwh=_num(d.get("hp_kwh")),
|
||||
import_cost_czk=_num(d.get("import_cost_czk")),
|
||||
export_revenue_czk=_num(d.get("export_revenue_czk")),
|
||||
grid_import_cashflow_czk=_num(d.get("grid_import_cashflow_czk")),
|
||||
grid_export_revenue_czk=_num(d.get("grid_export_revenue_czk")),
|
||||
net_cost_czk=_num(d.get("net_cost_czk")),
|
||||
green_bonus_czk=_num(d.get("green_bonus_czk")),
|
||||
total_balance_czk=_num(d.get("total_balance_czk")),
|
||||
planned_balance_czk=_opt(d.get("planned_balance_czk")),
|
||||
deviation_cost_czk=_opt(d.get("deviation_cost_czk")),
|
||||
is_locked=bool(d.get("is_locked")),
|
||||
)
|
||||
)
|
||||
return DailyEconomicsResponse(
|
||||
days=days,
|
||||
has_green_bonus=bool(raw.get("has_green_bonus")),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/daily/{day}/intervals", response_model=list[IntervalEconomics])
|
||||
@@ -270,50 +236,18 @@ async def lock_day(
|
||||
) -> LockResponse:
|
||||
async with db.acquire() as conn:
|
||||
await _check_site(conn, site_id)
|
||||
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT import_cost_czk, export_revenue_czk, net_cost_czk,
|
||||
green_bonus_czk, total_balance_czk,
|
||||
grid_import_cashflow_czk, grid_export_revenue_czk
|
||||
FROM ems.vw_economics_daily
|
||||
WHERE site_id = $1 AND day_local = $2
|
||||
""",
|
||||
raw = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_economics_lock_day($1::int, $2::date)",
|
||||
site_id,
|
||||
day,
|
||||
)
|
||||
if row is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No economics data for {day.isoformat()}",
|
||||
)
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO ems.audit_day_lock
|
||||
(site_id, day_local, import_cost_czk, export_revenue_czk,
|
||||
net_cost_czk, green_bonus_czk, total_balance_czk,
|
||||
grid_import_cashflow_czk, grid_export_revenue_czk)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
ON CONFLICT (site_id, day_local) DO UPDATE SET
|
||||
import_cost_czk = EXCLUDED.import_cost_czk,
|
||||
export_revenue_czk = EXCLUDED.export_revenue_czk,
|
||||
net_cost_czk = EXCLUDED.net_cost_czk,
|
||||
green_bonus_czk = EXCLUDED.green_bonus_czk,
|
||||
total_balance_czk = EXCLUDED.total_balance_czk,
|
||||
grid_import_cashflow_czk = EXCLUDED.grid_import_cashflow_czk,
|
||||
grid_export_revenue_czk = EXCLUDED.grid_export_revenue_czk,
|
||||
locked_at = now()
|
||||
""",
|
||||
site_id,
|
||||
day,
|
||||
row["import_cost_czk"],
|
||||
row["export_revenue_czk"],
|
||||
row["net_cost_czk"],
|
||||
row["green_bonus_czk"],
|
||||
row["total_balance_czk"],
|
||||
row["grid_import_cashflow_czk"],
|
||||
row["grid_export_revenue_czk"],
|
||||
if not isinstance(raw, dict):
|
||||
raw = json.loads(raw)
|
||||
if raw.get("locked") is not True:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No economics data for {day.isoformat()}",
|
||||
)
|
||||
|
||||
return LockResponse(locked=True, day=day)
|
||||
@@ -327,8 +261,9 @@ async def unlock_day(
|
||||
) -> LockResponse:
|
||||
async with db.acquire() as conn:
|
||||
await _check_site(conn, site_id)
|
||||
await conn.execute(
|
||||
"DELETE FROM ems.audit_day_lock WHERE site_id = $1 AND day_local = $2",
|
||||
await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_economics_unlock_day($1::int, $2::date)",
|
||||
site_id,
|
||||
day,
|
||||
)
|
||||
@@ -357,61 +292,29 @@ async def get_monthly_chart(
|
||||
|
||||
async with db.acquire() as conn:
|
||||
await _check_site(conn, site_id)
|
||||
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT day_local, total_balance_czk, net_cost_czk,
|
||||
green_bonus_czk, grid_import_cashflow_czk, grid_export_revenue_czk
|
||||
FROM ems.vw_economics_daily
|
||||
WHERE site_id = $1
|
||||
AND day_local >= $2
|
||||
AND day_local < $3
|
||||
ORDER BY day_local
|
||||
""",
|
||||
arr = await fetch_json(
|
||||
conn,
|
||||
"select ems.fn_economics_monthly_chart($1::int, $2::date, $3::date)",
|
||||
site_id,
|
||||
month_start,
|
||||
month_end,
|
||||
)
|
||||
|
||||
lock_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT day_local, total_balance_czk, net_cost_czk,
|
||||
green_bonus_czk, grid_import_cashflow_czk, grid_export_revenue_czk
|
||||
FROM ems.audit_day_lock
|
||||
WHERE site_id = $1
|
||||
AND day_local >= $2
|
||||
AND day_local < $3
|
||||
""",
|
||||
site_id,
|
||||
month_start,
|
||||
month_end,
|
||||
)
|
||||
locks = {r["day_local"]: r for r in lock_rows}
|
||||
|
||||
if not isinstance(arr, list):
|
||||
arr = json.loads(arr) if isinstance(arr, str) else []
|
||||
points: list[ChartDayPoint] = []
|
||||
cum_balance = 0.0
|
||||
cum_grid = 0.0
|
||||
for r in rows:
|
||||
d = r["day_local"]
|
||||
src = locks.get(d, r)
|
||||
balance = _num(src["total_balance_czk"])
|
||||
grid_balance = -_num(src["net_cost_czk"])
|
||||
green_bonus = _num(src["green_bonus_czk"])
|
||||
import_cost = _num(_safe_get(src, "grid_import_cashflow_czk", r["grid_import_cashflow_czk"]))
|
||||
export_revenue = _num(_safe_get(src, "grid_export_revenue_czk", r["grid_export_revenue_czk"]))
|
||||
cum_balance += balance
|
||||
cum_grid += grid_balance
|
||||
for r in arr:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
points.append(
|
||||
ChartDayPoint(
|
||||
day=d,
|
||||
daily_balance_czk=round(balance, 2),
|
||||
daily_grid_balance_czk=round(grid_balance, 2),
|
||||
daily_green_bonus_czk=round(green_bonus, 2),
|
||||
daily_import_cost_czk=round(import_cost, 2),
|
||||
daily_export_revenue_czk=round(export_revenue, 2),
|
||||
cumulative_balance_czk=round(cum_balance, 2),
|
||||
cumulative_grid_balance_czk=round(cum_grid, 2),
|
||||
day=_parse_day(r.get("day")),
|
||||
daily_balance_czk=float(r.get("daily_balance_czk") or 0),
|
||||
daily_grid_balance_czk=float(r.get("daily_grid_balance_czk") or 0),
|
||||
daily_green_bonus_czk=float(r.get("daily_green_bonus_czk") or 0),
|
||||
daily_import_cost_czk=float(r.get("daily_import_cost_czk") or 0),
|
||||
daily_export_revenue_czk=float(r.get("daily_export_revenue_czk") or 0),
|
||||
cumulative_balance_czk=float(r.get("cumulative_balance_czk") or 0),
|
||||
cumulative_grid_balance_czk=float(r.get("cumulative_grid_balance_czk") or 0),
|
||||
)
|
||||
)
|
||||
|
||||
return points
|
||||
|
||||
Reference in New Issue
Block a user