uprava adutiu - nacitani dalsich registru, uprava ekonomiky
This commit is contained in:
@@ -27,11 +27,13 @@ class DailyEconomics(BaseModel):
|
|||||||
export_kwh: float
|
export_kwh: float
|
||||||
pv_kwh: float
|
pv_kwh: float
|
||||||
load_kwh: float
|
load_kwh: float
|
||||||
self_consumption_kwh: float
|
pv_self_consumption_kwh: float
|
||||||
ev_kwh: float
|
ev_kwh: float
|
||||||
hp_kwh: float
|
hp_kwh: float
|
||||||
import_cost_czk: float
|
import_cost_czk: float
|
||||||
export_revenue_czk: float
|
export_revenue_czk: float
|
||||||
|
grid_import_cashflow_czk: float
|
||||||
|
grid_export_revenue_czk: float
|
||||||
net_cost_czk: float
|
net_cost_czk: float
|
||||||
green_bonus_czk: float
|
green_bonus_czk: float
|
||||||
total_balance_czk: float
|
total_balance_czk: float
|
||||||
@@ -50,6 +52,8 @@ class IntervalEconomics(BaseModel):
|
|||||||
import_kwh: float
|
import_kwh: float
|
||||||
export_kwh: float
|
export_kwh: float
|
||||||
dynamic_cost_czk: float | None
|
dynamic_cost_czk: float | None
|
||||||
|
grid_import_cashflow_czk: float | None
|
||||||
|
grid_export_revenue_czk: float | None
|
||||||
stored_cost_czk: float | None
|
stored_cost_czk: float | None
|
||||||
green_bonus_czk: float | None
|
green_bonus_czk: float | None
|
||||||
planned_cost_czk: float | None
|
planned_cost_czk: float | None
|
||||||
@@ -68,7 +72,12 @@ class IntervalEconomics(BaseModel):
|
|||||||
class ChartDayPoint(BaseModel):
|
class ChartDayPoint(BaseModel):
|
||||||
day: date
|
day: date
|
||||||
daily_balance_czk: float
|
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_balance_czk: float
|
||||||
|
cumulative_grid_balance_czk: float
|
||||||
|
|
||||||
|
|
||||||
class LockResponse(BaseModel):
|
class LockResponse(BaseModel):
|
||||||
@@ -82,6 +91,12 @@ def _num(val: Any) -> float:
|
|||||||
return float(val)
|
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:
|
async def _check_site(conn: asyncpg.Connection, site_id: int) -> None:
|
||||||
ok = await conn.fetchval(
|
ok = await conn.fetchval(
|
||||||
"SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id
|
"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)
|
@router.get("/daily", response_model=DailyEconomicsResponse)
|
||||||
async def get_economics_daily(
|
async def get_economics_daily(
|
||||||
site_id: int,
|
site_id: int,
|
||||||
@@ -159,50 +211,7 @@ async def get_economics_daily(
|
|||||||
for r in dyn_rows:
|
for r in dyn_rows:
|
||||||
d = r["day_local"]
|
d = r["day_local"]
|
||||||
lock = locks.get(d)
|
lock = locks.get(d)
|
||||||
if lock:
|
days.append(_daily_from_row(r, lock, is_locked=lock is not None))
|
||||||
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,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return DailyEconomicsResponse(days=days, has_green_bonus=has_bonus)
|
return DailyEconomicsResponse(days=days, has_green_bonus=has_bonus)
|
||||||
|
|
||||||
@@ -232,20 +241,22 @@ async def get_economics_intervals(
|
|||||||
interval_start=r["interval_start"].isoformat(),
|
interval_start=r["interval_start"].isoformat(),
|
||||||
import_kwh=_num(r["import_kwh"]),
|
import_kwh=_num(r["import_kwh"]),
|
||||||
export_kwh=_num(r["export_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,
|
dynamic_cost_czk=_opt(r["dynamic_cost_czk"]),
|
||||||
stored_cost_czk=float(r["stored_cost_czk"]) if r["stored_cost_czk"] is not None else None,
|
grid_import_cashflow_czk=_opt(r["grid_import_cashflow_czk"]),
|
||||||
green_bonus_czk=float(r["green_bonus_czk"]) if r["green_bonus_czk"] is not None else None,
|
grid_export_revenue_czk=_opt(r["grid_export_revenue_czk"]),
|
||||||
planned_cost_czk=float(r["planned_cost_czk"]) if r["planned_cost_czk"] is not None else None,
|
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,
|
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,
|
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_buy_price=_opt(r["effective_buy_price_czk_kwh"]),
|
||||||
effective_sell_price=float(r["effective_sell_price_czk_kwh"]) if r["effective_sell_price_czk_kwh"] is not None else None,
|
effective_sell_price=_opt(r["effective_sell_price_czk_kwh"]),
|
||||||
planned_buy_price=float(r["planned_buy_price"]) if r["planned_buy_price"] is not None else None,
|
planned_buy_price=_opt(r["planned_buy_price"]),
|
||||||
planned_sell_price=float(r["planned_sell_price"]) if r["planned_sell_price"] is not None else None,
|
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_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_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_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
|
for r in rows
|
||||||
]
|
]
|
||||||
@@ -263,7 +274,8 @@ async def lock_day(
|
|||||||
row = await conn.fetchrow(
|
row = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
SELECT import_cost_czk, export_revenue_czk, net_cost_czk,
|
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
|
FROM ems.vw_economics_daily
|
||||||
WHERE site_id = $1 AND day_local = $2
|
WHERE site_id = $1 AND day_local = $2
|
||||||
""",
|
""",
|
||||||
@@ -280,14 +292,17 @@ async def lock_day(
|
|||||||
"""
|
"""
|
||||||
INSERT INTO ems.audit_day_lock
|
INSERT INTO ems.audit_day_lock
|
||||||
(site_id, day_local, import_cost_czk, export_revenue_czk,
|
(site_id, day_local, import_cost_czk, export_revenue_czk,
|
||||||
net_cost_czk, green_bonus_czk, total_balance_czk)
|
net_cost_czk, green_bonus_czk, total_balance_czk,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
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
|
ON CONFLICT (site_id, day_local) DO UPDATE SET
|
||||||
import_cost_czk = EXCLUDED.import_cost_czk,
|
import_cost_czk = EXCLUDED.import_cost_czk,
|
||||||
export_revenue_czk = EXCLUDED.export_revenue_czk,
|
export_revenue_czk = EXCLUDED.export_revenue_czk,
|
||||||
net_cost_czk = EXCLUDED.net_cost_czk,
|
net_cost_czk = EXCLUDED.net_cost_czk,
|
||||||
green_bonus_czk = EXCLUDED.green_bonus_czk,
|
green_bonus_czk = EXCLUDED.green_bonus_czk,
|
||||||
total_balance_czk = EXCLUDED.total_balance_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()
|
locked_at = now()
|
||||||
""",
|
""",
|
||||||
site_id,
|
site_id,
|
||||||
@@ -297,6 +312,8 @@ async def lock_day(
|
|||||||
row["net_cost_czk"],
|
row["net_cost_czk"],
|
||||||
row["green_bonus_czk"],
|
row["green_bonus_czk"],
|
||||||
row["total_balance_czk"],
|
row["total_balance_czk"],
|
||||||
|
row["grid_import_cashflow_czk"],
|
||||||
|
row["grid_export_revenue_czk"],
|
||||||
)
|
)
|
||||||
|
|
||||||
return LockResponse(locked=True, day=day)
|
return LockResponse(locked=True, day=day)
|
||||||
@@ -343,7 +360,8 @@ async def get_monthly_chart(
|
|||||||
|
|
||||||
rows = await conn.fetch(
|
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
|
FROM ems.vw_economics_daily
|
||||||
WHERE site_id = $1
|
WHERE site_id = $1
|
||||||
AND day_local >= $2
|
AND day_local >= $2
|
||||||
@@ -357,7 +375,8 @@ async def get_monthly_chart(
|
|||||||
|
|
||||||
lock_rows = await conn.fetch(
|
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
|
FROM ems.audit_day_lock
|
||||||
WHERE site_id = $1
|
WHERE site_id = $1
|
||||||
AND day_local >= $2
|
AND day_local >= $2
|
||||||
@@ -367,19 +386,31 @@ async def get_monthly_chart(
|
|||||||
month_start,
|
month_start,
|
||||||
month_end,
|
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] = []
|
points: list[ChartDayPoint] = []
|
||||||
cumulative = 0.0
|
cum_balance = 0.0
|
||||||
|
cum_grid = 0.0
|
||||||
for r in rows:
|
for r in rows:
|
||||||
d = r["day_local"]
|
d = r["day_local"]
|
||||||
balance = locks.get(d, _num(r["total_balance_czk"]))
|
src = locks.get(d, r)
|
||||||
cumulative += balance
|
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(
|
points.append(
|
||||||
ChartDayPoint(
|
ChartDayPoint(
|
||||||
day=d,
|
day=d,
|
||||||
daily_balance_czk=round(balance, 2),
|
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_GRID_TOTAL_POWER = 625
|
||||||
DEYE_REG_GEN_PORT_POWER = 667
|
DEYE_REG_GEN_PORT_POWER = 667
|
||||||
DEYE_REG_LOAD_TOTAL_POWER = 653
|
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_PV1_POWER = 672
|
||||||
DEYE_REG_PV2_POWER = 673
|
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)
|
pv1_power = await mb.read_register_signed(DEYE_REG_PV1_POWER)
|
||||||
pv2_power = await mb.read_register_signed(DEYE_REG_PV2_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)
|
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)
|
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)
|
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,
|
battery_soc_percent, battery_power_w,
|
||||||
batt_charge_today_wh, batt_discharge_today_wh,
|
batt_charge_today_wh, batt_discharge_today_wh,
|
||||||
grid_power_w, load_power_w,
|
grid_power_w, load_power_w,
|
||||||
|
grid_import_total_wh, grid_export_total_wh,
|
||||||
run_state
|
run_state
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
@@ -87,7 +97,8 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
|||||||
$8, $9,
|
$8, $9,
|
||||||
$10, $11,
|
$10, $11,
|
||||||
$12, $13,
|
$12, $13,
|
||||||
$14
|
$14, $15,
|
||||||
|
$16
|
||||||
)
|
)
|
||||||
ON CONFLICT (inverter_id, measured_at) DO NOTHING
|
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,
|
batt_discharge_today,
|
||||||
grid_power,
|
grid_power,
|
||||||
load_power,
|
load_power,
|
||||||
|
grid_import_total_wh,
|
||||||
|
grid_export_total_wh,
|
||||||
run_state,
|
run_state,
|
||||||
)
|
)
|
||||||
inv_temp: float | None = None
|
inv_temp: float | None = None
|
||||||
|
|||||||
38
db/migration/V040__energy_wh_columns.sql
Normal file
38
db/migration/V040__energy_wh_columns.sql
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- V040 – Energy Wh columns
|
||||||
|
-- Přidává kumulativní čítače grid energie do telemetrie
|
||||||
|
-- a per-slot Wh sloupce do audit_interval pro přesné
|
||||||
|
-- import/export měření (Deye reg 522-525 + per-minute fallback).
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- 1. telemetry_inverter: kumulativní Deye lifetime čítače
|
||||||
|
ALTER TABLE ems.telemetry_inverter
|
||||||
|
ADD COLUMN IF NOT EXISTS grid_import_total_wh BIGINT,
|
||||||
|
ADD COLUMN IF NOT EXISTS grid_export_total_wh BIGINT;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ems.telemetry_inverter.grid_import_total_wh IS
|
||||||
|
'Kumulativní import ze sítě (Wh) z Deye reg 522+523 (32-bit × 0.1 kWh). Lifetime čítač, monotónně rostoucí.';
|
||||||
|
COMMENT ON COLUMN ems.telemetry_inverter.grid_export_total_wh IS
|
||||||
|
'Kumulativní export do sítě (Wh) z Deye reg 524+525 (32-bit × 0.1 kWh). Lifetime čítač, monotónně rostoucí.';
|
||||||
|
|
||||||
|
-- 2. audit_interval: 6 základních energetických veličin (Wh za 15min slot)
|
||||||
|
ALTER TABLE ems.audit_interval
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_grid_import_wh NUMERIC(10,1),
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_grid_export_wh NUMERIC(10,1),
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_batt_charge_wh NUMERIC(10,1),
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_batt_discharge_wh NUMERIC(10,1),
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_pv_production_wh NUMERIC(10,1),
|
||||||
|
ADD COLUMN IF NOT EXISTS actual_load_consumption_wh NUMERIC(10,1);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_grid_import_wh IS
|
||||||
|
'Import ze sítě za 15min slot (Wh). Primárně z delta Deye total counterů (reg 522+523), fallback per-minutový split z grid_power_w.';
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_grid_export_wh IS
|
||||||
|
'Export do sítě za 15min slot (Wh). Primárně z delta Deye total counterů (reg 524+525), fallback per-minutový split z grid_power_w.';
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_batt_charge_wh IS
|
||||||
|
'Nabití baterie za 15min slot (Wh). Per-minutový split z battery_power_w (záporné = nabíjení).';
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_batt_discharge_wh IS
|
||||||
|
'Vybití baterie za 15min slot (Wh). Per-minutový split z battery_power_w (kladné = vybíjení).';
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_pv_production_wh IS
|
||||||
|
'FVE výroba za 15min slot (Wh). SUM(pv_power_w) / 60 z minutových vzorků.';
|
||||||
|
COMMENT ON COLUMN ems.audit_interval.actual_load_consumption_wh IS
|
||||||
|
'Celková spotřeba za 15min slot (Wh). SUM(load_power_w) / 60 z minutových vzorků.';
|
||||||
13
db/migration/V041__audit_day_lock_grid_direction.sql
Normal file
13
db/migration/V041__audit_day_lock_grid_direction.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- V041 – audit_day_lock: směrové cashflow sloupce
|
||||||
|
-- Snapshot pro zamknuté dny rozšířen o cashflow podle směru energie.
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
ALTER TABLE ems.audit_day_lock
|
||||||
|
ADD COLUMN IF NOT EXISTS grid_import_cashflow_czk NUMERIC(12,2),
|
||||||
|
ADD COLUMN IF NOT EXISTS grid_export_revenue_czk NUMERIC(12,2);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ems.audit_day_lock.grid_import_cashflow_czk IS
|
||||||
|
'Snapshot: celková cena za import ze sítě v Kč (může být záporná při záporné spotové ceně).';
|
||||||
|
COMMENT ON COLUMN ems.audit_day_lock.grid_export_revenue_czk IS
|
||||||
|
'Snapshot: celkový příjem z exportu do sítě v Kč.';
|
||||||
@@ -29,6 +29,22 @@ DECLARE
|
|||||||
v_pv_b_production_wh NUMERIC;
|
v_pv_b_production_wh NUMERIC;
|
||||||
v_array_prod_wh NUMERIC;
|
v_array_prod_wh NUMERIC;
|
||||||
r_bonus RECORD;
|
r_bonus RECORD;
|
||||||
|
|
||||||
|
-- per-minute Wh veličiny
|
||||||
|
v_grid_import_wh NUMERIC;
|
||||||
|
v_grid_export_wh NUMERIC;
|
||||||
|
v_batt_charge_wh NUMERIC;
|
||||||
|
v_batt_discharge_wh NUMERIC;
|
||||||
|
v_pv_production_wh NUMERIC;
|
||||||
|
v_load_consumption_wh NUMERIC;
|
||||||
|
|
||||||
|
-- Deye counter delta
|
||||||
|
v_counter_import_first BIGINT;
|
||||||
|
v_counter_import_last BIGINT;
|
||||||
|
v_counter_export_first BIGINT;
|
||||||
|
v_counter_export_last BIGINT;
|
||||||
|
v_delta_import NUMERIC;
|
||||||
|
v_delta_export NUMERIC;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Najít aktivní plán pro tento interval
|
-- Najít aktivní plán pro tento interval
|
||||||
SELECT pi.* INTO v_plan
|
SELECT pi.* INTO v_plan
|
||||||
@@ -42,24 +58,58 @@ BEGIN
|
|||||||
|
|
||||||
v_run_id := v_plan.run_id;
|
v_run_id := v_plan.run_id;
|
||||||
|
|
||||||
-- Agregovat telemetrii střídače (průměr za 15min; agregace bez GROUP BY vrací vždy 1 řádek)
|
-- Agregovat telemetrii střídače: průměry (pro zpětnou kompatibilitu) + per-minute split pro Wh
|
||||||
SELECT
|
SELECT
|
||||||
AVG(pv_power_w)::INT,
|
AVG(pv_power_w)::INT,
|
||||||
AVG(battery_power_w)::INT,
|
AVG(battery_power_w)::INT,
|
||||||
AVG(grid_power_w)::INT,
|
AVG(grid_power_w)::INT,
|
||||||
AVG(load_power_w)::INT,
|
AVG(load_power_w)::INT,
|
||||||
LAST(battery_soc_percent, measured_at)
|
LAST(battery_soc_percent, measured_at),
|
||||||
|
-- Per-minute split: každý vzorek × 1/60 h = Wh
|
||||||
|
ROUND(SUM(GREATEST(grid_power_w, 0))::NUMERIC / 60, 1),
|
||||||
|
ROUND(SUM(ABS(LEAST(grid_power_w, 0)))::NUMERIC / 60, 1),
|
||||||
|
ROUND(SUM(ABS(LEAST(battery_power_w, 0)))::NUMERIC / 60, 1),
|
||||||
|
ROUND(SUM(GREATEST(battery_power_w, 0))::NUMERIC / 60, 1),
|
||||||
|
ROUND(SUM(GREATEST(pv_power_w, 0))::NUMERIC / 60, 1),
|
||||||
|
ROUND(SUM(GREATEST(load_power_w, 0))::NUMERIC / 60, 1),
|
||||||
|
-- Deye total energy counter delta
|
||||||
|
FIRST(grid_import_total_wh, measured_at),
|
||||||
|
LAST(grid_import_total_wh, measured_at),
|
||||||
|
FIRST(grid_export_total_wh, measured_at),
|
||||||
|
LAST(grid_export_total_wh, measured_at)
|
||||||
INTO
|
INTO
|
||||||
v_avg_pv_power_w,
|
v_avg_pv_power_w,
|
||||||
v_avg_battery_power_w,
|
v_avg_battery_power_w,
|
||||||
v_avg_grid_power_w,
|
v_avg_grid_power_w,
|
||||||
v_avg_load_power_w,
|
v_avg_load_power_w,
|
||||||
v_last_soc
|
v_last_soc,
|
||||||
|
v_grid_import_wh,
|
||||||
|
v_grid_export_wh,
|
||||||
|
v_batt_charge_wh,
|
||||||
|
v_batt_discharge_wh,
|
||||||
|
v_pv_production_wh,
|
||||||
|
v_load_consumption_wh,
|
||||||
|
v_counter_import_first,
|
||||||
|
v_counter_import_last,
|
||||||
|
v_counter_export_first,
|
||||||
|
v_counter_export_last
|
||||||
FROM ems.telemetry_inverter
|
FROM ems.telemetry_inverter
|
||||||
WHERE site_id = p_site_id
|
WHERE site_id = p_site_id
|
||||||
AND measured_at >= p_interval_start
|
AND measured_at >= p_interval_start
|
||||||
AND measured_at < v_interval_end;
|
AND measured_at < v_interval_end;
|
||||||
|
|
||||||
|
-- Deye counter delta (primární zdroj pro grid import/export, pokud jsou čítače dostupné)
|
||||||
|
IF v_counter_import_first IS NOT NULL AND v_counter_import_last IS NOT NULL
|
||||||
|
AND v_counter_import_last >= v_counter_import_first THEN
|
||||||
|
v_delta_import := v_counter_import_last - v_counter_import_first;
|
||||||
|
v_grid_import_wh := v_delta_import;
|
||||||
|
END IF;
|
||||||
|
IF v_counter_export_first IS NOT NULL AND v_counter_export_last IS NOT NULL
|
||||||
|
AND v_counter_export_last >= v_counter_export_first THEN
|
||||||
|
v_delta_export := v_counter_export_last - v_counter_export_first;
|
||||||
|
v_grid_export_wh := v_delta_export;
|
||||||
|
END IF;
|
||||||
|
|
||||||
-- Agregovat EV nabíječky (součet průměrů po charger_id)
|
-- Agregovat EV nabíječky (součet průměrů po charger_id)
|
||||||
SELECT COALESCE(SUM(avg_power), 0)::INT
|
SELECT COALESCE(SUM(avg_power), 0)::INT
|
||||||
INTO v_sum_ev_power_w
|
INTO v_sum_ev_power_w
|
||||||
@@ -84,12 +134,10 @@ BEGIN
|
|||||||
v_buy_price := ems.fn_effective_buy_price(p_site_id, p_interval_start);
|
v_buy_price := ems.fn_effective_buy_price(p_site_id, p_interval_start);
|
||||||
v_sell_price := ems.fn_effective_sell_price(p_site_id, p_interval_start);
|
v_sell_price := ems.fn_effective_sell_price(p_site_id, p_interval_start);
|
||||||
|
|
||||||
-- Skutečné náklady (kladný grid = nákup, záporný = prodej)
|
-- Skutečné náklady per-direction (import × buy - export × sell)
|
||||||
IF v_avg_grid_power_w IS NOT NULL THEN
|
IF v_grid_import_wh IS NOT NULL OR v_grid_export_wh IS NOT NULL THEN
|
||||||
v_actual_cost := (v_avg_grid_power_w::NUMERIC / 1000.0 / 4.0)
|
v_actual_cost := COALESCE(v_grid_import_wh, 0) / 1000.0 * COALESCE(v_buy_price, 0)
|
||||||
* CASE WHEN v_avg_grid_power_w >= 0
|
- COALESCE(v_grid_export_wh, 0) / 1000.0 * COALESCE(v_sell_price, 0);
|
||||||
THEN COALESCE(v_buy_price, 0)
|
|
||||||
ELSE COALESCE(v_sell_price, 0) END;
|
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
-- Zelený bonus: výroba bonusových polí z reálné telemetrie (Wh = průměr W × 0,25 h)
|
-- Zelený bonus: výroba bonusových polí z reálné telemetrie (Wh = průměr W × 0,25 h)
|
||||||
@@ -122,7 +170,6 @@ BEGIN
|
|||||||
AND ti.measured_at < v_interval_end;
|
AND ti.measured_at < v_interval_end;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
-- Fallback na forecast pokud telemetrie není k dispozici
|
|
||||||
IF v_array_prod_wh IS NULL THEN
|
IF v_array_prod_wh IS NULL THEN
|
||||||
SELECT fpi.power_w * 0.25
|
SELECT fpi.power_w * 0.25
|
||||||
INTO v_array_prod_wh
|
INTO v_array_prod_wh
|
||||||
@@ -160,7 +207,13 @@ BEGIN
|
|||||||
pv_b_production_wh,
|
pv_b_production_wh,
|
||||||
green_bonus_czk,
|
green_bonus_czk,
|
||||||
deviation_grid_w,
|
deviation_grid_w,
|
||||||
deviation_cost_czk
|
deviation_cost_czk,
|
||||||
|
actual_grid_import_wh,
|
||||||
|
actual_grid_export_wh,
|
||||||
|
actual_batt_charge_wh,
|
||||||
|
actual_batt_discharge_wh,
|
||||||
|
actual_pv_production_wh,
|
||||||
|
actual_load_consumption_wh
|
||||||
) VALUES (
|
) VALUES (
|
||||||
p_site_id, p_interval_start, v_run_id,
|
p_site_id, p_interval_start, v_run_id,
|
||||||
v_avg_pv_power_w,
|
v_avg_pv_power_w,
|
||||||
@@ -178,7 +231,13 @@ BEGIN
|
|||||||
ELSE NULL END,
|
ELSE NULL END,
|
||||||
CASE WHEN v_plan.run_id IS NOT NULL
|
CASE WHEN v_plan.run_id IS NOT NULL
|
||||||
THEN ROUND(v_actual_cost - COALESCE(v_plan.expected_cost_czk, 0), 4)
|
THEN ROUND(v_actual_cost - COALESCE(v_plan.expected_cost_czk, 0), 4)
|
||||||
ELSE NULL END
|
ELSE NULL END,
|
||||||
|
v_grid_import_wh,
|
||||||
|
v_grid_export_wh,
|
||||||
|
v_batt_charge_wh,
|
||||||
|
v_batt_discharge_wh,
|
||||||
|
v_pv_production_wh,
|
||||||
|
v_load_consumption_wh
|
||||||
)
|
)
|
||||||
ON CONFLICT (site_id, interval_start) DO UPDATE SET
|
ON CONFLICT (site_id, interval_start) DO UPDATE SET
|
||||||
planning_run_id = EXCLUDED.planning_run_id,
|
planning_run_id = EXCLUDED.planning_run_id,
|
||||||
@@ -193,15 +252,23 @@ BEGIN
|
|||||||
pv_b_production_wh = EXCLUDED.pv_b_production_wh,
|
pv_b_production_wh = EXCLUDED.pv_b_production_wh,
|
||||||
green_bonus_czk = EXCLUDED.green_bonus_czk,
|
green_bonus_czk = EXCLUDED.green_bonus_czk,
|
||||||
deviation_grid_w = EXCLUDED.deviation_grid_w,
|
deviation_grid_w = EXCLUDED.deviation_grid_w,
|
||||||
deviation_cost_czk = EXCLUDED.deviation_cost_czk;
|
deviation_cost_czk = EXCLUDED.deviation_cost_czk,
|
||||||
|
actual_grid_import_wh = EXCLUDED.actual_grid_import_wh,
|
||||||
|
actual_grid_export_wh = EXCLUDED.actual_grid_export_wh,
|
||||||
|
actual_batt_charge_wh = EXCLUDED.actual_batt_charge_wh,
|
||||||
|
actual_batt_discharge_wh = EXCLUDED.actual_batt_discharge_wh,
|
||||||
|
actual_pv_production_wh = EXCLUDED.actual_pv_production_wh,
|
||||||
|
actual_load_consumption_wh = EXCLUDED.actual_load_consumption_wh;
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
COMMENT ON FUNCTION ems.fn_fill_audit_interval(INT, TIMESTAMPTZ) IS
|
COMMENT ON FUNCTION ems.fn_fill_audit_interval(INT, TIMESTAMPTZ) IS
|
||||||
'Naplní nebo aktualizuje jeden řádek v audit_interval pro danou lokalitu a 15min interval.
|
'Naplní nebo aktualizuje jeden řádek v audit_interval pro danou lokalitu a 15min interval.
|
||||||
Agreguje průměry z telemetrie (střídač, EV, TČ), porovná se skutečným plánem a spočítá odchylky.
|
Agreguje průměry z telemetrie (střídač, EV, TČ), porovná se skutečným plánem a spočítá odchylky.
|
||||||
Zelený bonus: součet přes pole s green_bonus_czk_kwh; výroba primárně z reálné telemetrie
|
Nově: per-minutový split pro 6 energetických veličin (import/export/batt/PV/load Wh);
|
||||||
(dle asset_pv_array.telemetry_source), fallback na forecast_pv_interval pokud telemetrie chybí.
|
grid import/export primárně z delta Deye total counterů (reg 522-525), fallback per-minute.
|
||||||
|
actual_cost_czk = per-direction (import_wh × buy - export_wh × sell).
|
||||||
|
Zelený bonus: součet přes pole s green_bonus_czk_kwh.
|
||||||
Volat každých 15 minut pro interval který právě skončil.';
|
Volat každých 15 minut pro interval který právě skončil.';
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|||||||
@@ -9,14 +9,27 @@ CREATE OR REPLACE VIEW ems.vw_economics_interval AS
|
|||||||
SELECT
|
SELECT
|
||||||
ai.site_id,
|
ai.site_id,
|
||||||
ai.interval_start,
|
ai.interval_start,
|
||||||
ROUND(GREATEST(ai.actual_grid_power_w, 0)::NUMERIC / 4000, 4) AS import_kwh,
|
-- Wh-based kWh (per-direction, zachytí bidirectional flow)
|
||||||
ROUND(ABS(LEAST(ai.actual_grid_power_w, 0))::NUMERIC / 4000, 4) AS export_kwh,
|
ROUND(COALESCE(ai.actual_grid_import_wh, GREATEST(ai.actual_grid_power_w, 0)::NUMERIC / 4) / 1000, 4)
|
||||||
CASE WHEN ai.actual_grid_power_w >= 0
|
AS import_kwh,
|
||||||
THEN ROUND((ai.actual_grid_power_w::NUMERIC / 4000)
|
ROUND(COALESCE(ai.actual_grid_export_wh, ABS(LEAST(ai.actual_grid_power_w, 0))::NUMERIC / 4) / 1000, 4)
|
||||||
* COALESCE(ep.effective_buy_price_czk_kwh, 0), 4)
|
AS export_kwh,
|
||||||
ELSE ROUND((ai.actual_grid_power_w::NUMERIC / 4000)
|
-- Směrové cashflow: kolik Kč za import ze sítě / kolik Kč za export do sítě
|
||||||
* COALESCE(ep.effective_sell_price_czk_kwh, 0), 4)
|
ROUND(
|
||||||
END AS dynamic_cost_czk,
|
COALESCE(ai.actual_grid_import_wh, GREATEST(ai.actual_grid_power_w, 0)::NUMERIC / 4)
|
||||||
|
/ 1000.0 * COALESCE(ep.effective_buy_price_czk_kwh, 0), 4
|
||||||
|
) AS grid_import_cashflow_czk,
|
||||||
|
ROUND(
|
||||||
|
COALESCE(ai.actual_grid_export_wh, ABS(LEAST(ai.actual_grid_power_w, 0))::NUMERIC / 4)
|
||||||
|
/ 1000.0 * COALESCE(ep.effective_sell_price_czk_kwh, 0), 4
|
||||||
|
) AS grid_export_revenue_czk,
|
||||||
|
-- Net cost (zpětná kompatibilita): import_cashflow - export_revenue
|
||||||
|
ROUND(
|
||||||
|
COALESCE(ai.actual_grid_import_wh, GREATEST(ai.actual_grid_power_w, 0)::NUMERIC / 4)
|
||||||
|
/ 1000.0 * COALESCE(ep.effective_buy_price_czk_kwh, 0)
|
||||||
|
- COALESCE(ai.actual_grid_export_wh, ABS(LEAST(ai.actual_grid_power_w, 0))::NUMERIC / 4)
|
||||||
|
/ 1000.0 * COALESCE(ep.effective_sell_price_czk_kwh, 0), 4
|
||||||
|
) AS dynamic_cost_czk,
|
||||||
ai.actual_cost_czk AS stored_cost_czk,
|
ai.actual_cost_czk AS stored_cost_czk,
|
||||||
ai.green_bonus_czk,
|
ai.green_bonus_czk,
|
||||||
pi.expected_cost_czk AS planned_cost_czk,
|
pi.expected_cost_czk AS planned_cost_czk,
|
||||||
@@ -31,7 +44,13 @@ SELECT
|
|||||||
ai.actual_ev_power_w,
|
ai.actual_ev_power_w,
|
||||||
ai.actual_heat_pump_power_w,
|
ai.actual_heat_pump_power_w,
|
||||||
ai.actual_battery_power_w,
|
ai.actual_battery_power_w,
|
||||||
ai.actual_battery_soc_pct
|
ai.actual_battery_soc_pct,
|
||||||
|
ai.actual_grid_import_wh,
|
||||||
|
ai.actual_grid_export_wh,
|
||||||
|
ai.actual_batt_charge_wh,
|
||||||
|
ai.actual_batt_discharge_wh,
|
||||||
|
ai.actual_pv_production_wh,
|
||||||
|
ai.actual_load_consumption_wh
|
||||||
FROM ems.audit_interval ai
|
FROM ems.audit_interval ai
|
||||||
LEFT JOIN ems.vw_site_effective_price ep
|
LEFT JOIN ems.vw_site_effective_price ep
|
||||||
ON ep.site_id = ai.site_id AND ep.interval_start = ai.interval_start
|
ON ep.site_id = ai.site_id AND ep.interval_start = ai.interval_start
|
||||||
@@ -39,7 +58,10 @@ LEFT JOIN ems.planning_interval pi
|
|||||||
ON pi.run_id = ai.planning_run_id AND pi.interval_start = ai.interval_start;
|
ON pi.run_id = ai.planning_run_id AND pi.interval_start = ai.interval_start;
|
||||||
|
|
||||||
COMMENT ON VIEW ems.vw_economics_interval IS
|
COMMENT ON VIEW ems.vw_economics_interval IS
|
||||||
'Dynamické ekonomické vyhodnocení per 15min slot (závisí na vw_site_effective_price).';
|
'Dynamické ekonomické vyhodnocení per 15min slot.
|
||||||
|
import/export kWh primárně z per-direction Wh sloupců audit_interval (Deye counter / per-minute split),
|
||||||
|
fallback na průměrný výkon pro zpětnou kompatibilitu se starými daty.
|
||||||
|
grid_import_cashflow_czk / grid_export_revenue_czk = směrové cashflow podle skutečného toku energie.';
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ems.vw_economics_daily AS
|
CREATE OR REPLACE VIEW ems.vw_economics_daily AS
|
||||||
SELECT
|
SELECT
|
||||||
@@ -53,7 +75,11 @@ SELECT
|
|||||||
ROUND(SUM(GREATEST(actual_ev_power_w, 0)::NUMERIC / 4000), 3) AS ev_kwh,
|
ROUND(SUM(GREATEST(actual_ev_power_w, 0)::NUMERIC / 4000), 3) AS ev_kwh,
|
||||||
ROUND(SUM(GREATEST(actual_heat_pump_power_w, 0)::NUMERIC / 4000), 3) AS hp_kwh,
|
ROUND(SUM(GREATEST(actual_heat_pump_power_w, 0)::NUMERIC / 4000), 3) AS hp_kwh,
|
||||||
ROUND(SUM(GREATEST(actual_pv_power_w, 0)::NUMERIC / 4000)
|
ROUND(SUM(GREATEST(actual_pv_power_w, 0)::NUMERIC / 4000)
|
||||||
- SUM(export_kwh), 3) AS self_consumption_kwh,
|
- SUM(export_kwh), 3) AS pv_self_consumption_kwh,
|
||||||
|
-- Směrové cashflow (podle směru energie, ne znaménka peněz)
|
||||||
|
ROUND(SUM(grid_import_cashflow_czk), 2) AS grid_import_cashflow_czk,
|
||||||
|
ROUND(SUM(grid_export_revenue_czk), 2) AS grid_export_revenue_czk,
|
||||||
|
-- Staré sloupce (podle znaménka peněz – zpětná kompatibilita)
|
||||||
ROUND(SUM(CASE WHEN dynamic_cost_czk > 0
|
ROUND(SUM(CASE WHEN dynamic_cost_czk > 0
|
||||||
THEN dynamic_cost_czk ELSE 0 END), 2) AS import_cost_czk,
|
THEN dynamic_cost_czk ELSE 0 END), 2) AS import_cost_czk,
|
||||||
ROUND(SUM(CASE WHEN dynamic_cost_czk < 0
|
ROUND(SUM(CASE WHEN dynamic_cost_czk < 0
|
||||||
@@ -63,8 +89,7 @@ SELECT
|
|||||||
ROUND(-SUM(dynamic_cost_czk)
|
ROUND(-SUM(dynamic_cost_czk)
|
||||||
+ COALESCE(SUM(green_bonus_czk), 0), 2) AS total_balance_czk,
|
+ COALESCE(SUM(green_bonus_czk), 0), 2) AS total_balance_czk,
|
||||||
ROUND(SUM(planned_cost_czk), 2) AS planned_net_cost_czk,
|
ROUND(SUM(planned_cost_czk), 2) AS planned_net_cost_czk,
|
||||||
ROUND(-COALESCE(SUM(planned_cost_czk), 0)
|
ROUND(-COALESCE(SUM(planned_cost_czk), 0), 2) AS planned_balance_czk,
|
||||||
+ COALESCE(SUM(green_bonus_czk), 0), 2) AS planned_balance_czk,
|
|
||||||
ROUND(SUM(dynamic_cost_czk)
|
ROUND(SUM(dynamic_cost_czk)
|
||||||
- COALESCE(SUM(planned_cost_czk), 0), 2) AS deviation_cost_czk
|
- COALESCE(SUM(planned_cost_czk), 0), 2) AS deviation_cost_czk
|
||||||
FROM ems.vw_economics_interval
|
FROM ems.vw_economics_interval
|
||||||
@@ -72,4 +97,5 @@ GROUP BY site_id,
|
|||||||
date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague')::date;
|
date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague')::date;
|
||||||
|
|
||||||
COMMENT ON VIEW ems.vw_economics_daily IS
|
COMMENT ON VIEW ems.vw_economics_daily IS
|
||||||
'Denní souhrn ekonomiky (závisí na vw_economics_interval).';
|
'Denní souhrn ekonomiky. planned_balance_czk = jen síťové náklady (bez zeleného bonusu).
|
||||||
|
grid_import_cashflow_czk / grid_export_revenue_czk = směrové cashflow podle skutečného toku energie.';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
Cell,
|
Cell,
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
|
Legend,
|
||||||
Line,
|
Line,
|
||||||
ReferenceLine,
|
ReferenceLine,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -14,11 +15,14 @@ import type { ChartDayPoint } from '../../types/economics'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
points: ChartDayPoint[]
|
points: ChartDayPoint[]
|
||||||
|
hasGreenBonus: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const GREEN = '#22c55e'
|
const GREEN = '#22c55e'
|
||||||
const RED = '#ef4444'
|
const RED = '#ef4444'
|
||||||
const BLUE = '#3b82f6'
|
const BLUE = '#3b82f6'
|
||||||
|
const AMBER = '#f59e0b'
|
||||||
|
const SLATE = '#64748b'
|
||||||
|
|
||||||
function formatDay(iso: string): string {
|
function formatDay(iso: string): string {
|
||||||
const d = new Date(iso + 'T00:00:00')
|
const d = new Date(iso + 'T00:00:00')
|
||||||
@@ -29,40 +33,66 @@ type PayloadEntry = {
|
|||||||
name?: string
|
name?: string
|
||||||
value?: number
|
value?: number
|
||||||
color?: string
|
color?: string
|
||||||
|
dataKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function CustomTooltip({
|
function CustomTooltip({
|
||||||
active,
|
active,
|
||||||
payload,
|
payload,
|
||||||
label,
|
label,
|
||||||
|
hasGreenBonus,
|
||||||
}: {
|
}: {
|
||||||
active?: boolean
|
active?: boolean
|
||||||
payload?: PayloadEntry[]
|
payload?: PayloadEntry[]
|
||||||
label?: string
|
label?: string
|
||||||
|
hasGreenBonus: boolean
|
||||||
}) {
|
}) {
|
||||||
if (!active || !payload?.length || !label) return null
|
if (!active || !payload?.length || !label) return null
|
||||||
const balance = payload.find((p) => p.name === 'daily_balance_czk')
|
|
||||||
const cumulative = payload.find((p) => p.name === 'cumulative_balance_czk')
|
const gridBalance = payload.find((p) => p.dataKey === 'daily_grid_balance_czk')
|
||||||
|
const bonus = payload.find((p) => p.dataKey === 'daily_green_bonus_czk')
|
||||||
|
const cumBalance = payload.find((p) => p.dataKey === 'cumulative_balance_czk')
|
||||||
|
const cumGrid = payload.find((p) => p.dataKey === 'cumulative_grid_balance_czk')
|
||||||
|
const importCost = payload.find((p) => p.dataKey === 'daily_import_cost_czk')
|
||||||
|
const exportRev = payload.find((p) => p.dataKey === 'daily_export_revenue_czk')
|
||||||
|
|
||||||
|
const gridVal = gridBalance?.value ?? 0
|
||||||
|
const bonusVal = bonus?.value ?? 0
|
||||||
|
const total = gridVal + bonusVal
|
||||||
|
|
||||||
|
const fmtCzk = (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(2)} Kč`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs shadow-lg">
|
<div className="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs shadow-lg">
|
||||||
<p className="mb-1 font-medium text-slate-200">{label}</p>
|
<p className="mb-1 font-medium text-slate-200">{label}</p>
|
||||||
{balance && (
|
<p style={{ color: gridVal >= 0 ? GREEN : RED }}>Síť: {fmtCzk(gridVal)}</p>
|
||||||
<p style={{ color: (balance.value ?? 0) >= 0 ? GREEN : RED }}>
|
{hasGreenBonus && <p style={{ color: AMBER }}>Bonus: {fmtCzk(bonusVal)}</p>}
|
||||||
Den: {(balance.value ?? 0) >= 0 ? '+' : ''}
|
<p className="mt-1 border-t border-slate-700 pt-1 font-semibold" style={{ color: total >= 0 ? GREEN : RED }}>
|
||||||
{(balance.value ?? 0).toFixed(2)} Kč
|
Celkem: {fmtCzk(total)}
|
||||||
|
</p>
|
||||||
|
{importCost && (
|
||||||
|
<p className="mt-1 text-slate-400">
|
||||||
|
Nákup ze sítě: {(importCost.value ?? 0).toFixed(2)} Kč
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{cumulative && (
|
{exportRev && (
|
||||||
<p style={{ color: BLUE }}>
|
<p className="text-slate-400">Prodej do sítě: {(exportRev.value ?? 0).toFixed(2)} Kč</p>
|
||||||
Kumulativ: {(cumulative.value ?? 0) >= 0 ? '+' : ''}
|
)}
|
||||||
{(cumulative.value ?? 0).toFixed(2)} Kč
|
{cumBalance && (
|
||||||
|
<p className="mt-1 text-slate-400" style={{ color: BLUE }}>
|
||||||
|
Kumulativ: {fmtCzk(cumBalance.value ?? 0)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{cumGrid && (
|
||||||
|
<p className="text-slate-400" style={{ color: SLATE }}>
|
||||||
|
Kumulativ síť: {fmtCzk(cumGrid.value ?? 0)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EconomicsChart({ points }: Props) {
|
export function EconomicsChart({ points, hasGreenBonus }: Props) {
|
||||||
if (points.length === 0) {
|
if (points.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-64 items-center justify-center text-sm text-slate-500">
|
<div className="flex h-64 items-center justify-center text-sm text-slate-500">
|
||||||
@@ -77,7 +107,7 @@ export function EconomicsChart({ points }: Props) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveContainer width="100%" height={320}>
|
<ResponsiveContainer width="100%" height={380}>
|
||||||
<ComposedChart data={data} margin={{ top: 8, right: 16, bottom: 4, left: 0 }}>
|
<ComposedChart data={data} margin={{ top: 8, right: 16, bottom: 4, left: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||||
<XAxis dataKey="label" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
<XAxis dataKey="label" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||||
@@ -102,13 +132,72 @@ export function EconomicsChart({ points }: Props) {
|
|||||||
style: { fontSize: 11, fill: BLUE },
|
style: { fontSize: 11, fill: BLUE },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip hasGreenBonus={hasGreenBonus} />} />
|
||||||
|
<Legend
|
||||||
|
wrapperStyle={{ fontSize: 11 }}
|
||||||
|
formatter={(value: string) => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
daily_grid_balance_czk: 'Bilance síť',
|
||||||
|
daily_green_bonus_czk: 'Zelený bonus',
|
||||||
|
daily_import_cost_czk: 'Nákup ze sítě',
|
||||||
|
daily_export_revenue_czk: 'Prodej do sítě',
|
||||||
|
cumulative_balance_czk: 'Kumulativ vč. bonusu',
|
||||||
|
cumulative_grid_balance_czk: 'Kumulativ síť',
|
||||||
|
}
|
||||||
|
return labels[value] ?? value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ReferenceLine yAxisId="left" y={0} stroke="#475569" strokeDasharray="2 2" />
|
<ReferenceLine yAxisId="left" y={0} stroke="#475569" strokeDasharray="2 2" />
|
||||||
<Bar yAxisId="left" dataKey="daily_balance_czk" radius={[3, 3, 0, 0]} maxBarSize={32}>
|
|
||||||
|
{/* Stacked bars: bottom = grid balance, top = green bonus */}
|
||||||
|
<Bar
|
||||||
|
yAxisId="left"
|
||||||
|
dataKey="daily_grid_balance_czk"
|
||||||
|
stackId="balance"
|
||||||
|
maxBarSize={28}
|
||||||
|
radius={hasGreenBonus ? undefined : [3, 3, 0, 0]}
|
||||||
|
>
|
||||||
{data.map((entry, idx) => (
|
{data.map((entry, idx) => (
|
||||||
<Cell key={idx} fill={entry.daily_balance_czk >= 0 ? GREEN : RED} fillOpacity={0.8} />
|
<Cell
|
||||||
|
key={idx}
|
||||||
|
fill={entry.daily_grid_balance_czk >= 0 ? GREEN : RED}
|
||||||
|
fillOpacity={0.7}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Bar>
|
</Bar>
|
||||||
|
{hasGreenBonus && (
|
||||||
|
<Bar
|
||||||
|
yAxisId="left"
|
||||||
|
dataKey="daily_green_bonus_czk"
|
||||||
|
stackId="balance"
|
||||||
|
fill={AMBER}
|
||||||
|
fillOpacity={0.8}
|
||||||
|
maxBarSize={28}
|
||||||
|
radius={[3, 3, 0, 0]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Lines: import cost / export revenue */}
|
||||||
|
<Line
|
||||||
|
yAxisId="left"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="daily_import_cost_czk"
|
||||||
|
stroke={RED}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeDasharray="4 2"
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
yAxisId="left"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="daily_export_revenue_czk"
|
||||||
|
stroke={GREEN}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeDasharray="4 2"
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Cumulative lines (right axis) */}
|
||||||
<Line
|
<Line
|
||||||
yAxisId="right"
|
yAxisId="right"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
@@ -117,6 +206,15 @@ export function EconomicsChart({ points }: Props) {
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={{ r: 3, fill: BLUE }}
|
dot={{ r: 3, fill: BLUE }}
|
||||||
/>
|
/>
|
||||||
|
<Line
|
||||||
|
yAxisId="right"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="cumulative_grid_balance_czk"
|
||||||
|
stroke={SLATE}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeDasharray="5 3"
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ function DailyRow({
|
|||||||
onToggle: () => void
|
onToggle: () => void
|
||||||
onLockToggle: () => void
|
onLockToggle: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const colCount = hasGreenBonus ? 13 : 12
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr
|
<tr
|
||||||
@@ -157,9 +158,17 @@ function DailyRow({
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-sm">{kwh(row.import_kwh)}</td>
|
<td className="px-3 py-2 text-right text-sm">{kwh(row.import_kwh)}</td>
|
||||||
<td className="px-3 py-2 text-right text-sm">{kwh(row.export_kwh)}</td>
|
<td className="px-3 py-2 text-right text-sm">{kwh(row.export_kwh)}</td>
|
||||||
<td className="px-3 py-2 text-right text-sm text-slate-400">{kwh(row.self_consumption_kwh)}</td>
|
<td className="px-3 py-2 text-right text-sm text-slate-400">{kwh(row.pv_self_consumption_kwh)}</td>
|
||||||
<td className="px-3 py-2 text-right text-sm text-red-400">{row.import_cost_czk.toFixed(2)}</td>
|
<td className="px-3 py-2 text-right text-sm text-red-400">{row.grid_import_cashflow_czk.toFixed(2)}</td>
|
||||||
<td className="px-3 py-2 text-right text-sm text-green-400">{row.export_revenue_czk.toFixed(2)}</td>
|
<td className="px-3 py-2 text-right text-sm text-green-400">{row.grid_export_revenue_czk.toFixed(2)}</td>
|
||||||
|
<td className="px-3 py-2 text-right text-sm text-red-300">{row.import_cost_czk.toFixed(2)}</td>
|
||||||
|
<td className="px-3 py-2 text-right text-sm text-green-300">{row.export_revenue_czk.toFixed(2)}</td>
|
||||||
|
<td className="px-3 py-2 text-right text-sm text-slate-400">
|
||||||
|
{row.planned_balance_czk != null ? czk(row.planned_balance_czk) : '–'}
|
||||||
|
</td>
|
||||||
|
<td className={`px-3 py-2 text-right text-sm ${row.deviation_cost_czk != null ? balanceColor(-row.deviation_cost_czk) : ''}`}>
|
||||||
|
{row.deviation_cost_czk != null ? czk(row.deviation_cost_czk) : '–'}
|
||||||
|
</td>
|
||||||
{hasGreenBonus && (
|
{hasGreenBonus && (
|
||||||
<td className="px-3 py-2 text-right text-sm text-amber-400">
|
<td className="px-3 py-2 text-right text-sm text-amber-400">
|
||||||
{row.green_bonus_czk > 0 ? row.green_bonus_czk.toFixed(2) : '–'}
|
{row.green_bonus_czk > 0 ? row.green_bonus_czk.toFixed(2) : '–'}
|
||||||
@@ -168,12 +177,6 @@ function DailyRow({
|
|||||||
<td className={`px-3 py-2 text-right text-sm font-semibold ${balanceColor(row.total_balance_czk)}`}>
|
<td className={`px-3 py-2 text-right text-sm font-semibold ${balanceColor(row.total_balance_czk)}`}>
|
||||||
{czk(row.total_balance_czk)}
|
{czk(row.total_balance_czk)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-sm text-slate-400">
|
|
||||||
{row.planned_balance_czk != null ? czk(row.planned_balance_czk) : '–'}
|
|
||||||
</td>
|
|
||||||
<td className={`px-3 py-2 text-right text-sm ${row.deviation_cost_czk != null ? balanceColor(-row.deviation_cost_czk) : ''}`}>
|
|
||||||
{row.deviation_cost_czk != null ? czk(row.deviation_cost_czk) : '–'}
|
|
||||||
</td>
|
|
||||||
<td className="px-2 py-2 text-center">
|
<td className="px-2 py-2 text-center">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -193,7 +196,7 @@ function DailyRow({
|
|||||||
</tr>
|
</tr>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={hasGreenBonus ? 11 : 10} className="bg-slate-900/50 px-4 py-2">
|
<td colSpan={colCount} className="bg-slate-900/50 px-4 py-2">
|
||||||
<IntervalDetail siteId={siteId} day={row.day} hasGreenBonus={hasGreenBonus} />
|
<IntervalDetail siteId={siteId} day={row.day} hasGreenBonus={hasGreenBonus} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -217,6 +220,8 @@ export default function Economics() {
|
|||||||
return {
|
return {
|
||||||
import_cost: days.reduce((s, d) => s + d.import_cost_czk, 0),
|
import_cost: days.reduce((s, d) => s + d.import_cost_czk, 0),
|
||||||
export_revenue: days.reduce((s, d) => s + d.export_revenue_czk, 0),
|
export_revenue: days.reduce((s, d) => s + d.export_revenue_czk, 0),
|
||||||
|
grid_import_cashflow: days.reduce((s, d) => s + d.grid_import_cashflow_czk, 0),
|
||||||
|
grid_export_revenue: days.reduce((s, d) => s + d.grid_export_revenue_czk, 0),
|
||||||
green_bonus: days.reduce((s, d) => s + d.green_bonus_czk, 0),
|
green_bonus: days.reduce((s, d) => s + d.green_bonus_czk, 0),
|
||||||
total_balance: days.reduce((s, d) => s + d.total_balance_czk, 0),
|
total_balance: days.reduce((s, d) => s + d.total_balance_czk, 0),
|
||||||
}
|
}
|
||||||
@@ -277,14 +282,22 @@ export default function Economics() {
|
|||||||
|
|
||||||
{/* Summary cards */}
|
{/* Summary cards */}
|
||||||
{summary && (
|
{summary && (
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
<p className="text-xs text-slate-400">Nákup celkem</p>
|
<p className="text-xs text-slate-400">Nákup ze sítě</p>
|
||||||
<p className="mt-1 text-lg font-semibold text-red-400">{summary.import_cost.toFixed(2)} Kč</p>
|
<p className="mt-1 text-lg font-semibold text-red-400">{summary.grid_import_cashflow.toFixed(2)} Kč</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
<p className="text-xs text-slate-400">Prodej celkem</p>
|
<p className="text-xs text-slate-400">Prodej do sítě</p>
|
||||||
<p className="mt-1 text-lg font-semibold text-green-400">{summary.export_revenue.toFixed(2)} Kč</p>
|
<p className="mt-1 text-lg font-semibold text-green-400">{summary.grid_export_revenue.toFixed(2)} Kč</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
|
<p className="text-xs text-slate-400">Náklad celkem</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-red-300">{summary.import_cost.toFixed(2)} Kč</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
|
<p className="text-xs text-slate-400">Příjem celkem</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-green-300">{summary.export_revenue.toFixed(2)} Kč</p>
|
||||||
</div>
|
</div>
|
||||||
{hasGreenBonus && (
|
{hasGreenBonus && (
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
@@ -304,9 +317,9 @@ export default function Economics() {
|
|||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
<h2 className="mb-3 text-sm font-medium text-slate-300">
|
<h2 className="mb-3 text-sm font-medium text-slate-300">
|
||||||
Denní bilance + kumulativ od 1. v měsíci
|
Denní bilance (síť + bonus) a kumulativ
|
||||||
</h2>
|
</h2>
|
||||||
<EconomicsChart points={points} />
|
<EconomicsChart points={points} hasGreenBonus={hasGreenBonus} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Daily table */}
|
{/* Daily table */}
|
||||||
@@ -348,13 +361,15 @@ export default function Economics() {
|
|||||||
<th className="px-3 py-2 text-left">Den</th>
|
<th className="px-3 py-2 text-left">Den</th>
|
||||||
<th className="px-3 py-2 text-right">Import kWh</th>
|
<th className="px-3 py-2 text-right">Import kWh</th>
|
||||||
<th className="px-3 py-2 text-right">Export kWh</th>
|
<th className="px-3 py-2 text-right">Export kWh</th>
|
||||||
<th className="px-3 py-2 text-right">Vl. spotřeba</th>
|
<th className="px-3 py-2 text-right">FVE vl. spot.</th>
|
||||||
|
<th className="px-3 py-2 text-right">Nákup ze sítě</th>
|
||||||
|
<th className="px-3 py-2 text-right">Prodej do sítě</th>
|
||||||
<th className="px-3 py-2 text-right">Náklad Kč</th>
|
<th className="px-3 py-2 text-right">Náklad Kč</th>
|
||||||
<th className="px-3 py-2 text-right">Příjem Kč</th>
|
<th className="px-3 py-2 text-right">Příjem Kč</th>
|
||||||
{hasGreenBonus && <th className="px-3 py-2 text-right">Bonus Kč</th>}
|
|
||||||
<th className="px-3 py-2 text-right">Bilance Kč</th>
|
|
||||||
<th className="px-3 py-2 text-right">Plán Kč</th>
|
<th className="px-3 py-2 text-right">Plán Kč</th>
|
||||||
<th className="px-3 py-2 text-right">Odchylka Kč</th>
|
<th className="px-3 py-2 text-right">Odchylka Kč</th>
|
||||||
|
{hasGreenBonus && <th className="px-3 py-2 text-right">Bonus Kč</th>}
|
||||||
|
<th className="px-3 py-2 text-right">Bilance Kč</th>
|
||||||
<th className="w-10 px-2 py-2 text-center" />
|
<th className="w-10 px-2 py-2 text-center" />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ export type DailyEconomics = {
|
|||||||
export_kwh: number
|
export_kwh: number
|
||||||
pv_kwh: number
|
pv_kwh: number
|
||||||
load_kwh: number
|
load_kwh: number
|
||||||
self_consumption_kwh: number
|
pv_self_consumption_kwh: number
|
||||||
ev_kwh: number
|
ev_kwh: number
|
||||||
hp_kwh: number
|
hp_kwh: number
|
||||||
import_cost_czk: number
|
import_cost_czk: number
|
||||||
export_revenue_czk: number
|
export_revenue_czk: number
|
||||||
|
grid_import_cashflow_czk: number
|
||||||
|
grid_export_revenue_czk: number
|
||||||
net_cost_czk: number
|
net_cost_czk: number
|
||||||
green_bonus_czk: number
|
green_bonus_czk: number
|
||||||
total_balance_czk: number
|
total_balance_czk: number
|
||||||
@@ -28,6 +30,8 @@ export type IntervalEconomics = {
|
|||||||
import_kwh: number
|
import_kwh: number
|
||||||
export_kwh: number
|
export_kwh: number
|
||||||
dynamic_cost_czk: number | null
|
dynamic_cost_czk: number | null
|
||||||
|
grid_import_cashflow_czk: number | null
|
||||||
|
grid_export_revenue_czk: number | null
|
||||||
stored_cost_czk: number | null
|
stored_cost_czk: number | null
|
||||||
green_bonus_czk: number | null
|
green_bonus_czk: number | null
|
||||||
planned_cost_czk: number | null
|
planned_cost_czk: number | null
|
||||||
@@ -46,7 +50,12 @@ export type IntervalEconomics = {
|
|||||||
export type ChartDayPoint = {
|
export type ChartDayPoint = {
|
||||||
day: string
|
day: string
|
||||||
daily_balance_czk: number
|
daily_balance_czk: number
|
||||||
|
daily_grid_balance_czk: number
|
||||||
|
daily_green_bonus_czk: number
|
||||||
|
daily_import_cost_czk: number
|
||||||
|
daily_export_revenue_czk: number
|
||||||
cumulative_balance_czk: number
|
cumulative_balance_czk: number
|
||||||
|
cumulative_grid_balance_czk: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LockResponse = {
|
export type LockResponse = {
|
||||||
|
|||||||
Reference in New Issue
Block a user