korkece fve predikce, grafy predikci
This commit is contained in:
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from typing import Annotated, Any
|
||||
|
||||
import asyncpg
|
||||
@@ -522,3 +522,159 @@ async def get_site_forecast_pv_slots_range(
|
||||
if not isinstance(slots, list):
|
||||
slots = []
|
||||
return {"slots": slots}
|
||||
|
||||
|
||||
@router.get("/{site_id}/forecast/pv-slots-corrected")
|
||||
async def get_site_forecast_pv_slots_range_corrected(
|
||||
site_id: int,
|
||||
db: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
from_ts: datetime = Query(
|
||||
...,
|
||||
alias="from",
|
||||
description="Začátek okna [from, to), typicky UTC zaokrouhlené na 15 min",
|
||||
),
|
||||
to_ts: datetime = Query(
|
||||
...,
|
||||
alias="to",
|
||||
description="Konec polouzavřeného intervalu (max. cca 120 h za from)",
|
||||
),
|
||||
delta_from_ts: datetime | None = Query(
|
||||
None,
|
||||
alias="delta_from",
|
||||
description="Začátek okna historie pro výpočet delta profilu (default: now-60d)",
|
||||
),
|
||||
delta_to_ts: datetime | None = Query(
|
||||
None,
|
||||
alias="delta_to",
|
||||
description="Konec okna historie pro výpočet delta profilu (default: now)",
|
||||
),
|
||||
half_life_days: float = Query(
|
||||
14,
|
||||
ge=1,
|
||||
le=90,
|
||||
description="Half-life vážení (dny) pro delta profil",
|
||||
),
|
||||
threshold_w: int = Query(
|
||||
150,
|
||||
ge=0,
|
||||
le=10_000,
|
||||
description="Ignorovat sloty s nízkou výrobou (W) při odhadu profilu",
|
||||
),
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
if to_ts <= from_ts:
|
||||
raise HTTPException(status_code=422, detail="'to' must be after 'from'")
|
||||
if to_ts - from_ts > timedelta(hours=120):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="Span between 'from' and 'to' must be at most 120 hours",
|
||||
)
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
delta_to = delta_to_ts or now
|
||||
delta_from = delta_from_ts or (delta_to - timedelta(days=60))
|
||||
async with db.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")
|
||||
raw = await fetch_json(
|
||||
conn,
|
||||
"""
|
||||
select ems.fn_forecast_pv_slots_range_corrected(
|
||||
$1::int,
|
||||
$2::timestamptz,
|
||||
$3::timestamptz,
|
||||
$4::timestamptz,
|
||||
$5::timestamptz,
|
||||
$6::numeric,
|
||||
$7::int
|
||||
)
|
||||
""",
|
||||
site_id,
|
||||
from_ts,
|
||||
to_ts,
|
||||
delta_from,
|
||||
delta_to,
|
||||
half_life_days,
|
||||
threshold_w,
|
||||
)
|
||||
slots = raw if isinstance(raw, list) else []
|
||||
if not isinstance(slots, list):
|
||||
slots = []
|
||||
return {"slots": [s for s in slots if isinstance(s, dict)]}
|
||||
|
||||
|
||||
@router.get("/{site_id}/timeseries/telemetry-15m")
|
||||
async def get_site_telemetry_15m_range(
|
||||
site_id: int,
|
||||
db: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
from_ts: datetime = Query(..., alias="from", description="Začátek okna [from, to)"),
|
||||
to_ts: datetime = Query(..., alias="to", description="Konec okna [from, to)"),
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
if to_ts <= from_ts:
|
||||
raise HTTPException(status_code=422, detail="'to' must be after 'from'")
|
||||
if to_ts - from_ts > timedelta(days=60):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="Span between 'from' and 'to' must be at most 60 days",
|
||||
)
|
||||
async with db.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")
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
select
|
||||
slot_start,
|
||||
site_id,
|
||||
avg_pv_w,
|
||||
avg_load_w,
|
||||
avg_grid_w,
|
||||
avg_battery_w,
|
||||
last_soc_pct,
|
||||
sample_count
|
||||
from ems.telemetry_inverter_15m
|
||||
where site_id = $1
|
||||
and slot_start >= $2::timestamptz
|
||||
and slot_start < $3::timestamptz
|
||||
order by slot_start asc
|
||||
""",
|
||||
site_id,
|
||||
from_ts,
|
||||
to_ts,
|
||||
)
|
||||
return {"slots": [record_to_dict(r) for r in rows]}
|
||||
|
||||
|
||||
@router.get("/{site_id}/forecast/load-baseline-slots")
|
||||
async def get_site_load_baseline_slots_range(
|
||||
site_id: int,
|
||||
db: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
|
||||
from_ts: datetime = Query(..., alias="from", description="Začátek okna [from, to)"),
|
||||
to_ts: datetime = Query(..., alias="to", description="Konec okna [from, to)"),
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
if to_ts <= from_ts:
|
||||
raise HTTPException(status_code=422, detail="'to' must be after 'from'")
|
||||
if to_ts - from_ts > timedelta(days=60):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="Span between 'from' and 'to' must be at most 60 days",
|
||||
)
|
||||
async with db.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")
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
select interval_start, forecast_w, confidence_w
|
||||
from ems.fn_get_baseline_forecast($1::int, $2::timestamptz, $3::timestamptz)
|
||||
""",
|
||||
site_id,
|
||||
from_ts,
|
||||
to_ts,
|
||||
)
|
||||
return {"slots": [record_to_dict(r) for r in rows]}
|
||||
|
||||
Reference in New Issue
Block a user