do flow pridana ekonomika
This commit is contained in:
@@ -32,6 +32,10 @@ class DailyEnergyFlows(BaseModel):
|
|||||||
batt_to_grid_kwh: float
|
batt_to_grid_kwh: float
|
||||||
grid_to_load_kwh: float
|
grid_to_load_kwh: float
|
||||||
grid_to_batt_kwh: float
|
grid_to_batt_kwh: float
|
||||||
|
grid_import_cashflow_czk: float
|
||||||
|
grid_export_revenue_czk: float
|
||||||
|
grid_to_load_cost_czk: float
|
||||||
|
grid_to_batt_cost_czk: float
|
||||||
|
|
||||||
|
|
||||||
class DailyEnergyFlowsResponse(BaseModel):
|
class DailyEnergyFlowsResponse(BaseModel):
|
||||||
@@ -92,6 +96,10 @@ def _row_to_daily(r: Any) -> DailyEnergyFlows:
|
|||||||
batt_to_grid_kwh=_num(r["batt_to_grid_kwh"]),
|
batt_to_grid_kwh=_num(r["batt_to_grid_kwh"]),
|
||||||
grid_to_load_kwh=_num(r["grid_to_load_kwh"]),
|
grid_to_load_kwh=_num(r["grid_to_load_kwh"]),
|
||||||
grid_to_batt_kwh=_num(r["grid_to_batt_kwh"]),
|
grid_to_batt_kwh=_num(r["grid_to_batt_kwh"]),
|
||||||
|
grid_import_cashflow_czk=_num(r["grid_import_cashflow_czk"]),
|
||||||
|
grid_export_revenue_czk=_num(r["grid_export_revenue_czk"]),
|
||||||
|
grid_to_load_cost_czk=_num(r["grid_to_load_cost_czk"]),
|
||||||
|
grid_to_batt_cost_czk=_num(r["grid_to_batt_cost_czk"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -148,8 +156,39 @@ async def get_energy_flows_daily(
|
|||||||
ROUND(SUM(COALESCE(ai.flow_grid_to_load_wh, 0)) / 1000, 3)
|
ROUND(SUM(COALESCE(ai.flow_grid_to_load_wh, 0)) / 1000, 3)
|
||||||
AS grid_to_load_kwh,
|
AS grid_to_load_kwh,
|
||||||
ROUND(SUM(COALESCE(ai.flow_grid_to_batt_wh, 0)) / 1000, 3)
|
ROUND(SUM(COALESCE(ai.flow_grid_to_batt_wh, 0)) / 1000, 3)
|
||||||
AS grid_to_batt_kwh
|
AS grid_to_batt_kwh,
|
||||||
|
ROUND(
|
||||||
|
SUM(
|
||||||
|
COALESCE(ai.actual_grid_import_wh, 0) / 1000.0
|
||||||
|
* COALESCE(ep.effective_buy_price_czk_kwh, 0)
|
||||||
|
),
|
||||||
|
2
|
||||||
|
) AS grid_import_cashflow_czk,
|
||||||
|
ROUND(
|
||||||
|
SUM(
|
||||||
|
COALESCE(ai.actual_grid_export_wh, 0) / 1000.0
|
||||||
|
* COALESCE(ep.effective_sell_price_czk_kwh, 0)
|
||||||
|
),
|
||||||
|
2
|
||||||
|
) AS grid_export_revenue_czk,
|
||||||
|
ROUND(
|
||||||
|
SUM(
|
||||||
|
COALESCE(ai.flow_grid_to_load_wh, 0) / 1000.0
|
||||||
|
* COALESCE(ep.effective_buy_price_czk_kwh, 0)
|
||||||
|
),
|
||||||
|
2
|
||||||
|
) AS grid_to_load_cost_czk,
|
||||||
|
ROUND(
|
||||||
|
SUM(
|
||||||
|
COALESCE(ai.flow_grid_to_batt_wh, 0) / 1000.0
|
||||||
|
* COALESCE(ep.effective_buy_price_czk_kwh, 0)
|
||||||
|
),
|
||||||
|
2
|
||||||
|
) AS grid_to_batt_cost_czk
|
||||||
FROM ems.audit_interval ai
|
FROM ems.audit_interval ai
|
||||||
|
LEFT JOIN ems.vw_site_effective_price ep
|
||||||
|
ON ep.site_id = ai.site_id
|
||||||
|
AND ep.interval_start = ai.interval_start
|
||||||
WHERE ai.site_id = $1
|
WHERE ai.site_id = $1
|
||||||
AND (date_trunc('day', ai.interval_start AT TIME ZONE 'Europe/Prague'))::date
|
AND (date_trunc('day', ai.interval_start AT TIME ZONE 'Europe/Prague'))::date
|
||||||
>= $2
|
>= $2
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Základní 6 Wh veličin (import/export, PV, baterie, load) zůstává ve Fázi
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
- `GET /api/v1/sites/{site_id}/energy-flows/daily?month=YYYY-MM`
|
- `GET /api/v1/sites/{site_id}/energy-flows/daily?month=YYYY-MM` — kromě toků vrací denní součty **financí sítě**: `grid_import_cashflow_czk`, `grid_export_revenue_czk` (jako `vw_economics_interval`, join na `vw_site_effective_price`) a nákladový rozpad importu `grid_to_load_cost_czk` / `grid_to_batt_cost_czk` (efektivní nákupní cena × modelovaný tok).
|
||||||
- `GET /api/v1/sites/{site_id}/energy-flows/daily/{day}/intervals`
|
- `GET /api/v1/sites/{site_id}/energy-flows/daily/{day}/intervals`
|
||||||
|
|
||||||
## SQL
|
## SQL
|
||||||
@@ -29,7 +29,7 @@ Základní 6 Wh veličin (import/export, PV, baterie, load) zůstává ve Fázi
|
|||||||
## UI
|
## UI
|
||||||
|
|
||||||
- Sankey (`@nivo/sankey`) – součet toků za **celý měsíc** nebo za **jeden vybraný den** (rozbalovací pole „Graf a karty“; klik na název dne v tabulce také přepne den). Síť je ve vizualizaci rozdělena na **Import ze sítě** a **Export do sítě** (jinak by vznikl cyklus síť↔baterie a knihovna hlásí „circular link“).
|
- Sankey (`@nivo/sankey`) – součet toků za **celý měsíc** nebo za **jeden vybraný den** (rozbalovací pole „Graf a karty“; klik na název dne v tabulce také přepne den). Síť je ve vizualizaci rozdělena na **Import ze sítě** a **Export do sítě** (jinak by vznikl cyklus síť↔baterie a knihovna hlásí „circular link“).
|
||||||
- Čtyři perspektivní karty (FVE / síť / baterie / spotřeba — odkud šla energie do odběru).
|
- Pět perspektivních karet: FVE, síť, baterie, spotřeba a **financí** (nákup/prodej v Kč, průměrná cena Kč/kWh, rozpad nákladů importu do spotřeby vs. baterie; stejný měsíční/denní rozsah jako Sankey).
|
||||||
- Tabulka dnů s rozbalením na 15min intervaly.
|
- Tabulka dnů s rozbalením na 15min intervaly.
|
||||||
|
|
||||||
## Backfill
|
## Backfill
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ function kwh(v: number | null | undefined, d = 2): string {
|
|||||||
return v.toFixed(d)
|
return v.toFixed(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function czk(v: number | null | undefined): string {
|
||||||
|
if (v == null) return '–'
|
||||||
|
return v.toLocaleString('cs-CZ', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
function aggregateFlows(days: DailyEnergyFlows[]): FlowTotals & {
|
function aggregateFlows(days: DailyEnergyFlows[]): FlowTotals & {
|
||||||
pv_production_kwh: number
|
pv_production_kwh: number
|
||||||
grid_import_kwh: number
|
grid_import_kwh: number
|
||||||
@@ -48,6 +53,10 @@ function aggregateFlows(days: DailyEnergyFlows[]): FlowTotals & {
|
|||||||
batt_charge_kwh: number
|
batt_charge_kwh: number
|
||||||
batt_discharge_kwh: number
|
batt_discharge_kwh: number
|
||||||
load_kwh: number
|
load_kwh: number
|
||||||
|
grid_import_cashflow_czk: number
|
||||||
|
grid_export_revenue_czk: number
|
||||||
|
grid_to_load_cost_czk: number
|
||||||
|
grid_to_batt_cost_czk: number
|
||||||
} {
|
} {
|
||||||
const z = {
|
const z = {
|
||||||
pv_production_kwh: 0,
|
pv_production_kwh: 0,
|
||||||
@@ -63,6 +72,10 @@ function aggregateFlows(days: DailyEnergyFlows[]): FlowTotals & {
|
|||||||
batt_to_grid_kwh: 0,
|
batt_to_grid_kwh: 0,
|
||||||
grid_to_load_kwh: 0,
|
grid_to_load_kwh: 0,
|
||||||
grid_to_batt_kwh: 0,
|
grid_to_batt_kwh: 0,
|
||||||
|
grid_import_cashflow_czk: 0,
|
||||||
|
grid_export_revenue_czk: 0,
|
||||||
|
grid_to_load_cost_czk: 0,
|
||||||
|
grid_to_batt_cost_czk: 0,
|
||||||
}
|
}
|
||||||
for (const d of days) {
|
for (const d of days) {
|
||||||
z.pv_production_kwh += d.pv_production_kwh
|
z.pv_production_kwh += d.pv_production_kwh
|
||||||
@@ -78,6 +91,10 @@ function aggregateFlows(days: DailyEnergyFlows[]): FlowTotals & {
|
|||||||
z.batt_to_grid_kwh += d.batt_to_grid_kwh
|
z.batt_to_grid_kwh += d.batt_to_grid_kwh
|
||||||
z.grid_to_load_kwh += d.grid_to_load_kwh
|
z.grid_to_load_kwh += d.grid_to_load_kwh
|
||||||
z.grid_to_batt_kwh += d.grid_to_batt_kwh
|
z.grid_to_batt_kwh += d.grid_to_batt_kwh
|
||||||
|
z.grid_import_cashflow_czk += d.grid_import_cashflow_czk ?? 0
|
||||||
|
z.grid_export_revenue_czk += d.grid_export_revenue_czk ?? 0
|
||||||
|
z.grid_to_load_cost_czk += d.grid_to_load_cost_czk ?? 0
|
||||||
|
z.grid_to_batt_cost_czk += d.grid_to_batt_cost_czk ?? 0
|
||||||
}
|
}
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
@@ -167,6 +184,17 @@ export default function EnergyFlows() {
|
|||||||
? Math.min(100, (totals.batt_discharge_kwh / totals.batt_charge_kwh) * 100)
|
? Math.min(100, (totals.batt_discharge_kwh / totals.batt_charge_kwh) * 100)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
const avgImportKcPerKwh =
|
||||||
|
totals && totals.grid_import_kwh > 0.001
|
||||||
|
? totals.grid_import_cashflow_czk / totals.grid_import_kwh
|
||||||
|
: null
|
||||||
|
const avgExportKcPerKwh =
|
||||||
|
totals && totals.grid_export_kwh > 0.001
|
||||||
|
? totals.grid_export_revenue_czk / totals.grid_export_kwh
|
||||||
|
: null
|
||||||
|
const gridNetCzk =
|
||||||
|
totals != null ? totals.grid_import_cashflow_czk - totals.grid_export_revenue_czk : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl space-y-6 px-4 py-6 md:px-8">
|
<main className="mx-auto max-w-7xl space-y-6 px-4 py-6 md:px-8">
|
||||||
{siteReady && siteRow && (
|
{siteReady && siteRow && (
|
||||||
@@ -239,7 +267,7 @@ export default function EnergyFlows() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{totals && (
|
{totals && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||||
<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 font-medium uppercase tracking-wide text-amber-400">Perspektiva FVE</p>
|
<p className="text-xs font-medium uppercase tracking-wide text-amber-400">Perspektiva FVE</p>
|
||||||
<p className="mt-2 text-2xl font-semibold text-white">{totals.pv_production_kwh.toFixed(1)} kWh</p>
|
<p className="mt-2 text-2xl font-semibold text-white">{totals.pv_production_kwh.toFixed(1)} kWh</p>
|
||||||
@@ -296,6 +324,50 @@ export default function EnergyFlows() {
|
|||||||
zaokrouhlení po 15min intervalech.
|
zaokrouhlení po 15min intervalech.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
|
<p className="text-xs font-medium uppercase tracking-wide text-rose-400">Perspektiva financí</p>
|
||||||
|
<p className="mt-2 text-sm text-slate-400">
|
||||||
|
Nákup ze sítě:{' '}
|
||||||
|
<span className="font-semibold text-red-200">{czk(totals.grid_import_cashflow_czk)}</span> Kč
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-slate-400">
|
||||||
|
Prodej do sítě:{' '}
|
||||||
|
<span className="font-semibold text-green-200">{czk(totals.grid_export_revenue_czk)}</span> Kč
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-slate-300">
|
||||||
|
Bilance sítě (nákup − prodej):{' '}
|
||||||
|
<span className="font-semibold text-white">{czk(gridNetCzk)}</span> Kč
|
||||||
|
</p>
|
||||||
|
<ul className="mt-2 space-y-1 border-t border-slate-800 pt-2 text-xs text-slate-400">
|
||||||
|
<li>
|
||||||
|
Prům. cena nákupu:{' '}
|
||||||
|
{avgImportKcPerKwh != null ? (
|
||||||
|
<span className="text-rose-200/90">{avgImportKcPerKwh.toFixed(3)} Kč/kWh</span>
|
||||||
|
) : (
|
||||||
|
<span>–</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Prům. cena prodeje:{' '}
|
||||||
|
{avgExportKcPerKwh != null ? (
|
||||||
|
<span className="text-emerald-200/90">{avgExportKcPerKwh.toFixed(3)} Kč/kWh</span>
|
||||||
|
) : (
|
||||||
|
<span>–</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-2 text-[11px] font-medium uppercase tracking-wide text-slate-500">
|
||||||
|
Rozpad nákladů importu (efektivní cena × modelovaný tok)
|
||||||
|
</p>
|
||||||
|
<ul className="mt-1 space-y-1 text-xs text-slate-400">
|
||||||
|
<li>Do spotřeby: {czk(totals.grid_to_load_cost_czk)} Kč</li>
|
||||||
|
<li>Do baterie: {czk(totals.grid_to_batt_cost_czk)} Kč</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-2 border-t border-slate-800 pt-2 text-[11px] leading-snug text-slate-500">
|
||||||
|
Stejná jednotková cena v každém 15min slotu; součet rozpadu se může mírně lišit od celkového nákupu kvůli
|
||||||
|
zaokrouhlení a odchylce modelu toků od měřeného importu.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export type DailyEnergyFlows = {
|
|||||||
batt_to_grid_kwh: number
|
batt_to_grid_kwh: number
|
||||||
grid_to_load_kwh: number
|
grid_to_load_kwh: number
|
||||||
grid_to_batt_kwh: number
|
grid_to_batt_kwh: number
|
||||||
|
grid_import_cashflow_czk: number
|
||||||
|
grid_export_revenue_czk: number
|
||||||
|
grid_to_load_cost_czk: number
|
||||||
|
grid_to_batt_cost_czk: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DailyEnergyFlowsResponse = {
|
export type DailyEnergyFlowsResponse = {
|
||||||
|
|||||||
Reference in New Issue
Block a user