dalsi uprava vypoctu delty (ignorujeme orezane vyroby)
Some checks failed
CI and deploy / migration-check (push) Failing after 17s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-22 22:42:12 +02:00
parent 568b584748
commit 1dfab8c7a1
11 changed files with 174 additions and 12 deletions

View File

@@ -8,7 +8,7 @@ from typing import Annotated, Any
import asyncpg
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field
from app.db_json import fetch_json
from app.deps import get_pg_pool
@@ -16,6 +16,20 @@ from app.deps import get_pg_pool
router = APIRouter(prefix="/sites/{site_id}", tags=["sites"])
class PvForecastCalibrationPatch(BaseModel):
"""Částečná úprava `ems.site_pv_forecast_calibration`. Vynechané klíče = beze změny."""
model_config = ConfigDict(extra="forbid")
delta_learn_min_ts: datetime | None = None
pv_curtailment_policy_effective_from: datetime | None = None
top_n_days: int | None = Field(default=None, ge=0, le=31)
non_top_day_factor: float | None = Field(default=None, ge=0, le=1)
day_weight_gamma: float | None = Field(default=None, ge=0.25, le=8)
half_life_days: float | None = Field(default=None, ge=1, le=90)
threshold_w: int | None = Field(default=None, ge=0, le=10_000)
class InverterModbusCurrentCapsBody(BaseModel):
"""Tvrdý strop proudu pro zápis Deye reg 108/109 (A); NULL ve JSONu = smaž strop v DB."""
@@ -77,6 +91,76 @@ async def get_site_configuration(
return raw
@router.patch("/configuration/pv-forecast-calibration")
async def patch_pv_forecast_calibration(
site_id: int,
body: PvForecastCalibrationPatch,
pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
"""Aktualizace kalibrace PV delty (`ems.site_pv_forecast_calibration`)."""
updates = body.model_dump(exclude_unset=True)
if not updates:
raise HTTPException(status_code=400, detail="No fields to update")
if updates.get("delta_learn_min_ts") is None and "delta_learn_min_ts" in updates:
raise HTTPException(
status_code=422,
detail="delta_learn_min_ts cannot be null (column is NOT NULL)",
)
allowed = {
"delta_learn_min_ts",
"pv_curtailment_policy_effective_from",
"top_n_days",
"non_top_day_factor",
"day_weight_gamma",
"half_life_days",
"threshold_w",
}
bad = set(updates) - allowed
if bad:
raise HTTPException(status_code=400, detail=f"Unknown fields: {sorted(bad)}")
cols = list(updates.keys())
set_parts: list[str] = []
args: list[Any] = [site_id]
for i, col in enumerate(cols, start=2):
set_parts.append(f"{col} = ${i}")
args.append(updates[col])
set_sql = ", ".join(set_parts) + ", updated_at = now()"
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 = await conn.execute(
f"""
UPDATE ems.site_pv_forecast_calibration
SET {set_sql}
WHERE site_id = $1
""",
*args,
)
if n == "UPDATE 0":
raise HTTPException(
status_code=404,
detail="PV forecast calibration row missing; run migration V057",
)
row = await conn.fetchrow(
"""
SELECT to_jsonb(c.*) AS j
FROM ems.site_pv_forecast_calibration c
WHERE c.site_id = $1
""",
site_id,
)
raw = row["j"] if row else {}
if not isinstance(raw, dict):
raw = json.loads(raw)
return raw
@router.patch("/inverters/{inverter_id}/modbus-current-caps")
async def patch_inverter_modbus_current_caps(
site_id: int,