dalsi uprava vypoctu delty (ignorujeme orezane vyroby)
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user