uprava adutiu - nacitani dalsich registru, uprava ekonomiky
This commit is contained in:
@@ -27,11 +27,13 @@ class DailyEconomics(BaseModel):
|
||||
export_kwh: float
|
||||
pv_kwh: float
|
||||
load_kwh: float
|
||||
self_consumption_kwh: float
|
||||
pv_self_consumption_kwh: float
|
||||
ev_kwh: float
|
||||
hp_kwh: float
|
||||
import_cost_czk: float
|
||||
export_revenue_czk: float
|
||||
grid_import_cashflow_czk: float
|
||||
grid_export_revenue_czk: float
|
||||
net_cost_czk: float
|
||||
green_bonus_czk: float
|
||||
total_balance_czk: float
|
||||
@@ -50,6 +52,8 @@ class IntervalEconomics(BaseModel):
|
||||
import_kwh: float
|
||||
export_kwh: float
|
||||
dynamic_cost_czk: float | None
|
||||
grid_import_cashflow_czk: float | None
|
||||
grid_export_revenue_czk: float | None
|
||||
stored_cost_czk: float | None
|
||||
green_bonus_czk: float | None
|
||||
planned_cost_czk: float | None
|
||||
@@ -68,7 +72,12 @@ class IntervalEconomics(BaseModel):
|
||||
class ChartDayPoint(BaseModel):
|
||||
day: date
|
||||
daily_balance_czk: float
|
||||
daily_grid_balance_czk: float
|
||||
daily_green_bonus_czk: float
|
||||
daily_import_cost_czk: float
|
||||
daily_export_revenue_czk: float
|
||||
cumulative_balance_czk: float
|
||||
cumulative_grid_balance_czk: float
|
||||
|
||||
|
||||
class LockResponse(BaseModel):
|
||||
@@ -82,6 +91,12 @@ def _num(val: Any) -> float:
|
||||
return float(val)
|
||||
|
||||
|
||||
def _opt(val: Any) -> float | None:
|
||||
if val is None:
|
||||
return None
|
||||
return float(val)
|
||||
|
||||
|
||||
async def _check_site(conn: asyncpg.Connection, site_id: int) -> None:
|
||||
ok = await conn.fetchval(
|
||||
"SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id
|
||||
@@ -105,6 +120,43 @@ async def _has_green_bonus(conn: asyncpg.Connection, site_id: int) -> bool:
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/daily", response_model=DailyEconomicsResponse)
|
||||
async def get_economics_daily(
|
||||
site_id: int,
|
||||
@@ -159,50 +211,7 @@ async def get_economics_daily(
|
||||
for r in dyn_rows:
|
||||
d = r["day_local"]
|
||||
lock = locks.get(d)
|
||||
if lock:
|
||||
days.append(
|
||||
DailyEconomics(
|
||||
day=d,
|
||||
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"]),
|
||||
self_consumption_kwh=_num(r["self_consumption_kwh"]),
|
||||
ev_kwh=_num(r["ev_kwh"]),
|
||||
hp_kwh=_num(r["hp_kwh"]),
|
||||
import_cost_czk=_num(lock["import_cost_czk"]),
|
||||
export_revenue_czk=_num(lock["export_revenue_czk"]),
|
||||
net_cost_czk=_num(lock["net_cost_czk"]),
|
||||
green_bonus_czk=_num(lock["green_bonus_czk"]),
|
||||
total_balance_czk=_num(lock["total_balance_czk"]),
|
||||
planned_balance_czk=_num(r["planned_balance_czk"]) if r["planned_balance_czk"] is not None else None,
|
||||
deviation_cost_czk=_num(r["deviation_cost_czk"]) if r["deviation_cost_czk"] is not None else None,
|
||||
is_locked=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
days.append(
|
||||
DailyEconomics(
|
||||
day=d,
|
||||
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"]),
|
||||
self_consumption_kwh=_num(r["self_consumption_kwh"]),
|
||||
ev_kwh=_num(r["ev_kwh"]),
|
||||
hp_kwh=_num(r["hp_kwh"]),
|
||||
import_cost_czk=_num(r["import_cost_czk"]),
|
||||
export_revenue_czk=_num(r["export_revenue_czk"]),
|
||||
net_cost_czk=_num(r["net_cost_czk"]),
|
||||
green_bonus_czk=_num(r["green_bonus_czk"]),
|
||||
total_balance_czk=_num(r["total_balance_czk"]),
|
||||
planned_balance_czk=_num(r["planned_balance_czk"]) if r["planned_balance_czk"] is not None else None,
|
||||
deviation_cost_czk=_num(r["deviation_cost_czk"]) if r["deviation_cost_czk"] is not None else None,
|
||||
is_locked=False,
|
||||
)
|
||||
)
|
||||
days.append(_daily_from_row(r, lock, is_locked=lock is not None))
|
||||
|
||||
return DailyEconomicsResponse(days=days, has_green_bonus=has_bonus)
|
||||
|
||||
@@ -232,20 +241,22 @@ async def get_economics_intervals(
|
||||
interval_start=r["interval_start"].isoformat(),
|
||||
import_kwh=_num(r["import_kwh"]),
|
||||
export_kwh=_num(r["export_kwh"]),
|
||||
dynamic_cost_czk=float(r["dynamic_cost_czk"]) if r["dynamic_cost_czk"] is not None else None,
|
||||
stored_cost_czk=float(r["stored_cost_czk"]) if r["stored_cost_czk"] is not None else None,
|
||||
green_bonus_czk=float(r["green_bonus_czk"]) if r["green_bonus_czk"] is not None else None,
|
||||
planned_cost_czk=float(r["planned_cost_czk"]) if r["planned_cost_czk"] is not None else None,
|
||||
dynamic_cost_czk=_opt(r["dynamic_cost_czk"]),
|
||||
grid_import_cashflow_czk=_opt(r["grid_import_cashflow_czk"]),
|
||||
grid_export_revenue_czk=_opt(r["grid_export_revenue_czk"]),
|
||||
stored_cost_czk=_opt(r["stored_cost_czk"]),
|
||||
green_bonus_czk=_opt(r["green_bonus_czk"]),
|
||||
planned_cost_czk=_opt(r["planned_cost_czk"]),
|
||||
planned_grid_w=int(r["planned_grid_w"]) if r["planned_grid_w"] is not None else None,
|
||||
actual_grid_power_w=int(r["actual_grid_power_w"]) if r["actual_grid_power_w"] is not None else None,
|
||||
effective_buy_price=float(r["effective_buy_price_czk_kwh"]) if r["effective_buy_price_czk_kwh"] is not None else None,
|
||||
effective_sell_price=float(r["effective_sell_price_czk_kwh"]) if r["effective_sell_price_czk_kwh"] is not None else None,
|
||||
planned_buy_price=float(r["planned_buy_price"]) if r["planned_buy_price"] is not None else None,
|
||||
planned_sell_price=float(r["planned_sell_price"]) if r["planned_sell_price"] is not None else None,
|
||||
effective_buy_price=_opt(r["effective_buy_price_czk_kwh"]),
|
||||
effective_sell_price=_opt(r["effective_sell_price_czk_kwh"]),
|
||||
planned_buy_price=_opt(r["planned_buy_price"]),
|
||||
planned_sell_price=_opt(r["planned_sell_price"]),
|
||||
actual_pv_power_w=int(r["actual_pv_power_w"]) if r["actual_pv_power_w"] is not None else None,
|
||||
actual_load_power_w=int(r["actual_load_power_w"]) if r["actual_load_power_w"] is not None else None,
|
||||
actual_battery_power_w=int(r["actual_battery_power_w"]) if r["actual_battery_power_w"] is not None else None,
|
||||
actual_battery_soc_pct=float(r["actual_battery_soc_pct"]) if r["actual_battery_soc_pct"] is not None else None,
|
||||
actual_battery_soc_pct=_opt(r["actual_battery_soc_pct"]),
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
@@ -263,7 +274,8 @@ async def lock_day(
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT import_cost_czk, export_revenue_czk, net_cost_czk,
|
||||
green_bonus_czk, total_balance_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
|
||||
""",
|
||||
@@ -280,14 +292,17 @@ async def lock_day(
|
||||
"""
|
||||
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)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
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,
|
||||
@@ -297,6 +312,8 @@ async def lock_day(
|
||||
row["net_cost_czk"],
|
||||
row["green_bonus_czk"],
|
||||
row["total_balance_czk"],
|
||||
row["grid_import_cashflow_czk"],
|
||||
row["grid_export_revenue_czk"],
|
||||
)
|
||||
|
||||
return LockResponse(locked=True, day=day)
|
||||
@@ -343,7 +360,8 @@ async def get_monthly_chart(
|
||||
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT day_local, total_balance_czk
|
||||
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
|
||||
@@ -357,7 +375,8 @@ async def get_monthly_chart(
|
||||
|
||||
lock_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT day_local, total_balance_czk
|
||||
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
|
||||
@@ -367,19 +386,31 @@ async def get_monthly_chart(
|
||||
month_start,
|
||||
month_end,
|
||||
)
|
||||
locks = {r["day_local"]: _num(r["total_balance_czk"]) for r in lock_rows}
|
||||
locks = {r["day_local"]: r for r in lock_rows}
|
||||
|
||||
points: list[ChartDayPoint] = []
|
||||
cumulative = 0.0
|
||||
cum_balance = 0.0
|
||||
cum_grid = 0.0
|
||||
for r in rows:
|
||||
d = r["day_local"]
|
||||
balance = locks.get(d, _num(r["total_balance_czk"]))
|
||||
cumulative += balance
|
||||
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
|
||||
points.append(
|
||||
ChartDayPoint(
|
||||
day=d,
|
||||
daily_balance_czk=round(balance, 2),
|
||||
cumulative_balance_czk=round(cumulative, 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),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ DEYE_REG_BATTERY_POWER_FLOW = 590
|
||||
DEYE_REG_GRID_TOTAL_POWER = 625
|
||||
DEYE_REG_GEN_PORT_POWER = 667
|
||||
DEYE_REG_LOAD_TOTAL_POWER = 653
|
||||
DEYE_REG_GRID_IMPORT_TOTAL_LO = 522
|
||||
DEYE_REG_GRID_IMPORT_TOTAL_HI = 523
|
||||
DEYE_REG_GRID_EXPORT_TOTAL_LO = 524
|
||||
DEYE_REG_GRID_EXPORT_TOTAL_HI = 525
|
||||
DEYE_REG_PV1_POWER = 672
|
||||
DEYE_REG_PV2_POWER = 673
|
||||
|
||||
@@ -67,7 +71,12 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
pv1_power = await mb.read_register_signed(DEYE_REG_PV1_POWER)
|
||||
pv2_power = await mb.read_register_signed(DEYE_REG_PV2_POWER)
|
||||
gen_port_power = await mb.read_register_signed(DEYE_REG_GEN_PORT_POWER)
|
||||
grid_energy_regs = await mb.read_holding_registers(
|
||||
DEYE_REG_GRID_IMPORT_TOTAL_LO, 4
|
||||
)
|
||||
pv_power_w = aggregate_pv_production_w(pv1_power, pv2_power, gen_port_power)
|
||||
grid_import_total_wh = (grid_energy_regs[1] << 16 | grid_energy_regs[0]) * 100
|
||||
grid_export_total_wh = (grid_energy_regs[3] << 16 | grid_energy_regs[2]) * 100
|
||||
|
||||
logger.debug("inverter:%s Deye run_state raw=%s", code, run_state)
|
||||
|
||||
@@ -79,6 +88,7 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
battery_soc_percent, battery_power_w,
|
||||
batt_charge_today_wh, batt_discharge_today_wh,
|
||||
grid_power_w, load_power_w,
|
||||
grid_import_total_wh, grid_export_total_wh,
|
||||
run_state
|
||||
)
|
||||
VALUES (
|
||||
@@ -87,7 +97,8 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
$8, $9,
|
||||
$10, $11,
|
||||
$12, $13,
|
||||
$14
|
||||
$14, $15,
|
||||
$16
|
||||
)
|
||||
ON CONFLICT (inverter_id, measured_at) DO NOTHING
|
||||
""",
|
||||
@@ -104,6 +115,8 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
batt_discharge_today,
|
||||
grid_power,
|
||||
load_power,
|
||||
grid_import_total_wh,
|
||||
grid_export_total_wh,
|
||||
run_state,
|
||||
)
|
||||
inv_temp: float | None = None
|
||||
|
||||
Reference in New Issue
Block a user