new site BA81, tuyne forecast
This commit is contained in:
@@ -1009,7 +1009,7 @@ async def get_site_forecast_pv(
|
|||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT run_id, pv_array_id, interval_start, power_w,
|
SELECT run_id, pv_array_id, interval_start, power_w,
|
||||||
irradiance_wm2, temp_c, pv_array_code
|
irradiance_wm2, temp_c, pv_array_code, controllable
|
||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT ON (fpi.interval_start, fpr.pv_array_id)
|
SELECT DISTINCT ON (fpi.interval_start, fpr.pv_array_id)
|
||||||
fpi.run_id,
|
fpi.run_id,
|
||||||
@@ -1018,7 +1018,8 @@ async def get_site_forecast_pv(
|
|||||||
fpi.power_w,
|
fpi.power_w,
|
||||||
fpi.irradiance_wm2,
|
fpi.irradiance_wm2,
|
||||||
fpi.temp_c,
|
fpi.temp_c,
|
||||||
apa.code AS pv_array_code
|
apa.code AS pv_array_code,
|
||||||
|
apa.controllable
|
||||||
FROM ems.forecast_pv_interval fpi
|
FROM ems.forecast_pv_interval fpi
|
||||||
JOIN ems.forecast_pv_run fpr ON fpr.id = fpi.run_id
|
JOIN ems.forecast_pv_run fpr ON fpr.id = fpi.run_id
|
||||||
JOIN ems.asset_pv_array apa
|
JOIN ems.asset_pv_array apa
|
||||||
@@ -1028,20 +1029,21 @@ async def get_site_forecast_pv(
|
|||||||
AND fpr.status = 'ok'
|
AND fpr.status = 'ok'
|
||||||
ORDER BY fpi.interval_start, fpr.pv_array_id, fpr.created_at DESC
|
ORDER BY fpi.interval_start, fpr.pv_array_id, fpr.created_at DESC
|
||||||
) latest
|
) latest
|
||||||
ORDER BY pv_array_code, interval_start
|
ORDER BY controllable DESC, pv_array_code, interval_start
|
||||||
""",
|
""",
|
||||||
site_id,
|
site_id,
|
||||||
d,
|
d,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# pv_a = řiditelná pole (curtailment / Deye), pv_b = neřízená (GEN, …) — sloučí více orientací
|
||||||
pv_a: list[dict[str, Any]] = []
|
pv_a: list[dict[str, Any]] = []
|
||||||
pv_b: list[dict[str, Any]] = []
|
pv_b: list[dict[str, Any]] = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
item = record_to_dict(r)
|
item = record_to_dict(r)
|
||||||
code = item.get("pv_array_code")
|
item.pop("controllable", None)
|
||||||
if code == "pv-a":
|
if r["controllable"]:
|
||||||
pv_a.append(item)
|
pv_a.append(item)
|
||||||
elif code == "pv-b":
|
else:
|
||||||
pv_b.append(item)
|
pv_b.append(item)
|
||||||
return {"pv_a": pv_a, "pv_b": pv_b}
|
return {"pv_a": pv_a, "pv_b": pv_b}
|
||||||
|
|
||||||
|
|||||||
@@ -1052,20 +1052,42 @@ async def _load_slots(site_id, from_dt, to_dt, db) -> list[PlanningSlot]:
|
|||||||
LEFT JOIN ems.vw_site_effective_price ep
|
LEFT JOIN ems.vw_site_effective_price ep
|
||||||
ON ep.site_id = $1 AND ep.interval_start = s.interval_start
|
ON ep.site_id = $1 AND ep.interval_start = s.interval_start
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT fpi.power_w FROM ems.forecast_pv_interval fpi
|
SELECT COALESCE(SUM(u.power_w), 0)::INT AS power_w
|
||||||
JOIN ems.forecast_pv_run fpr ON fpr.id = fpi.run_id
|
FROM (
|
||||||
JOIN ems.asset_pv_array apa ON apa.id = fpi.pv_array_id AND apa.site_id = fpr.site_id
|
SELECT DISTINCT ON (apa.id)
|
||||||
WHERE fpr.site_id = $1 AND apa.code = 'pv-a'
|
fpi.power_w
|
||||||
AND fpi.interval_start = s.interval_start AND fpr.status = 'ok'
|
FROM ems.asset_pv_array apa
|
||||||
ORDER BY fpr.created_at DESC LIMIT 1
|
JOIN ems.forecast_pv_run fpr
|
||||||
|
ON fpr.pv_array_id = apa.id
|
||||||
|
AND fpr.site_id = apa.site_id
|
||||||
|
AND fpr.status = 'ok'
|
||||||
|
JOIN ems.forecast_pv_interval fpi
|
||||||
|
ON fpi.run_id = fpr.id
|
||||||
|
AND fpi.pv_array_id = apa.id
|
||||||
|
AND fpi.interval_start = s.interval_start
|
||||||
|
WHERE apa.site_id = $1
|
||||||
|
AND apa.controllable IS TRUE
|
||||||
|
ORDER BY apa.id, fpr.created_at DESC
|
||||||
|
) u
|
||||||
) fpi_a ON true
|
) fpi_a ON true
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT fpi.power_w FROM ems.forecast_pv_interval fpi
|
SELECT COALESCE(SUM(u.power_w), 0)::INT AS power_w
|
||||||
JOIN ems.forecast_pv_run fpr ON fpr.id = fpi.run_id
|
FROM (
|
||||||
JOIN ems.asset_pv_array apa ON apa.id = fpi.pv_array_id AND apa.site_id = fpr.site_id
|
SELECT DISTINCT ON (apa.id)
|
||||||
WHERE fpr.site_id = $1 AND apa.code = 'pv-b'
|
fpi.power_w
|
||||||
AND fpi.interval_start = s.interval_start AND fpr.status = 'ok'
|
FROM ems.asset_pv_array apa
|
||||||
ORDER BY fpr.created_at DESC LIMIT 1
|
JOIN ems.forecast_pv_run fpr
|
||||||
|
ON fpr.pv_array_id = apa.id
|
||||||
|
AND fpr.site_id = apa.site_id
|
||||||
|
AND fpr.status = 'ok'
|
||||||
|
JOIN ems.forecast_pv_interval fpi
|
||||||
|
ON fpi.run_id = fpr.id
|
||||||
|
AND fpi.pv_array_id = apa.id
|
||||||
|
AND fpi.interval_start = s.interval_start
|
||||||
|
WHERE apa.site_id = $1
|
||||||
|
AND apa.controllable IS FALSE
|
||||||
|
ORDER BY apa.id, fpr.created_at DESC
|
||||||
|
) u
|
||||||
) fpi_b ON true
|
) fpi_b ON true
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT t.status
|
SELECT t.status
|
||||||
|
|||||||
388
db/migration/V043__site_25a_fixed_buy_seed.sql
Normal file
388
db/migration/V043__site_25a_fixed_buy_seed.sql
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- V043__site_25a_fixed_buy_seed.sql
|
||||||
|
-- Sloupce pro fixní nákupní energii (NT + příplatek VT) a seed lokality site-25a.
|
||||||
|
--
|
||||||
|
-- Jedna verzovaná migrace: čtyři FVE pole (různá orientace), žádný mezikrok pv-a/pv-b.
|
||||||
|
--
|
||||||
|
-- Obnova / přepnutí checksum na DB, kde už běžela starší varianta V043 nebo V044:
|
||||||
|
-- DELETE FROM flyway_schema_history WHERE version IN ('043', '044');
|
||||||
|
-- Potom: flyway migrate
|
||||||
|
-- (Sloupce buy_fixed_* zůstanou díky ADD COLUMN IF NOT EXISTS; DO blok smaže legacy pv-a/pv-b
|
||||||
|
-- a doplní pv-str-*/pv-mi-* pokud chybí.)
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- Fixní složka nákupu bez DPH (k distribuci / poplatkům / marži / DPH dle fn_effective_buy_price)
|
||||||
|
ALTER TABLE ems.site_market_config
|
||||||
|
ADD COLUMN IF NOT EXISTS buy_fixed_energy_nt_czk_kwh NUMERIC(10,6),
|
||||||
|
ADD COLUMN IF NOT EXISTS buy_fixed_vt_surcharge_czk_kwh NUMERIC(10,6) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ems.site_market_config.buy_fixed_energy_nt_czk_kwh IS
|
||||||
|
'Při purchase_pricing_mode = fixed: základní nákupní cena energie Kč/kWh bez DPH v NT hodinách. VT = tato hodnota + buy_fixed_vt_surcharge_czk_kwh podle HDO oken.';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ems.site_market_config.buy_fixed_vt_surcharge_czk_kwh IS
|
||||||
|
'Při purchase_pricing_mode = fixed: příplatek Kč/kWh bez DPH k NT ceně ve VT oknech dle hdo_code_id.';
|
||||||
|
|
||||||
|
-- =============================================================
|
||||||
|
-- Seed lokality (idempotentní DO blok)
|
||||||
|
-- Viz docs/new-site-setup-template.md – ev-charger-1 pro planner/telemetrii.
|
||||||
|
-- FVE: čtyři záznamy asset_pv_array (forecast service běží per pole; planner sčítá controllable / !controllable).
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
v_site_code TEXT := 'BA81';
|
||||||
|
|
||||||
|
v_host_modbus TEXT := '109.164.83.155';
|
||||||
|
v_port_modbus INT := 502;
|
||||||
|
v_host_loxone TEXT := '109.164.83.155';
|
||||||
|
v_port_loxone INT := 8080;
|
||||||
|
|
||||||
|
v_site_id INT;
|
||||||
|
v_ep_deye INT;
|
||||||
|
v_ep_ev INT;
|
||||||
|
v_ep_loxone INT;
|
||||||
|
v_inv_main INT;
|
||||||
|
v_inv_gen INT;
|
||||||
|
v_hdo_id INT;
|
||||||
|
v_ch_id INT;
|
||||||
|
BEGIN
|
||||||
|
SELECT hc.id INTO v_hdo_id
|
||||||
|
FROM ems.hdo_code hc
|
||||||
|
WHERE hc.distributor = 'EGD' AND hc.code = 'custom_fve_home01'
|
||||||
|
ORDER BY hc.valid_from DESC NULLS LAST
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
INSERT INTO ems.site (code, name, timezone, latitude, longitude, active, notes)
|
||||||
|
VALUES (
|
||||||
|
v_site_code,
|
||||||
|
'Lokalita 25A / 17 kW příkon',
|
||||||
|
'Europe/Prague',
|
||||||
|
49.24368977130069,
|
||||||
|
17.425553019721196,
|
||||||
|
true,
|
||||||
|
'Připojení 3×25 A → import max 17 kW, export max 16 kW. '
|
||||||
|
'Při omezení exportu do DS nastavit v Deye SmartLoad: „MI export to Grid cutoff“ = enable; '
|
||||||
|
'po uvolnění exportu znovu disable. Veřejná IP tunelovaná z EMS serveru.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (code) DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
timezone = EXCLUDED.timezone,
|
||||||
|
latitude = EXCLUDED.latitude,
|
||||||
|
longitude = EXCLUDED.longitude,
|
||||||
|
active = EXCLUDED.active,
|
||||||
|
notes = EXCLUDED.notes
|
||||||
|
RETURNING id INTO v_site_id;
|
||||||
|
|
||||||
|
SELECT se.id INTO v_ep_deye
|
||||||
|
FROM ems.site_endpoint se
|
||||||
|
WHERE se.site_id = v_site_id
|
||||||
|
AND se.endpoint_type = 'modbus_tcp'
|
||||||
|
AND se.notes ILIKE '%Deye%'
|
||||||
|
ORDER BY se.id
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_ep_deye IS NULL THEN
|
||||||
|
INSERT INTO ems.site_endpoint (
|
||||||
|
site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, 'modbus_tcp', v_host_modbus, v_port_modbus, 'modbus_tcp', 1, true,
|
||||||
|
'Deye 12kW LV – Modbus TCP (Waveshare).'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_ep_deye;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT se.id INTO v_ep_ev
|
||||||
|
FROM ems.site_endpoint se
|
||||||
|
WHERE se.site_id = v_site_id
|
||||||
|
AND se.endpoint_type = 'modbus_tcp'
|
||||||
|
AND se.notes ILIKE '%Teltonika%'
|
||||||
|
ORDER BY se.id
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_ep_ev IS NULL THEN
|
||||||
|
INSERT INTO ems.site_endpoint (
|
||||||
|
site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, 'modbus_tcp', v_host_modbus, v_port_modbus, 'modbus_tcp', 2, true,
|
||||||
|
'Teltonika TeltoCharge 22kW – stejná IP jako Deye, unit_id 2 (upřesni dle zapojení).'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_ep_ev;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT se.id INTO v_ep_loxone
|
||||||
|
FROM ems.site_endpoint se
|
||||||
|
WHERE se.site_id = v_site_id
|
||||||
|
AND se.endpoint_type = 'loxone_http'
|
||||||
|
ORDER BY se.id
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_ep_loxone IS NULL THEN
|
||||||
|
INSERT INTO ems.site_endpoint (
|
||||||
|
site_id, endpoint_type, host, port, protocol, unit_id, enabled, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, 'loxone_http', v_host_loxone, v_port_loxone, 'http', NULL, true,
|
||||||
|
'Loxone Miniserver (HTTP Virtual Inputs).'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_ep_loxone;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO ems.site_grid_connection (
|
||||||
|
site_id, max_import_power_w, max_export_power_w, no_export, reserved_capacity_w, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, 17000, 16000, false, 0,
|
||||||
|
'Max 25 A přívod → cca 17 kW import; přetok / export povolen 16 kW.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (site_id) DO UPDATE SET
|
||||||
|
max_import_power_w = EXCLUDED.max_import_power_w,
|
||||||
|
max_export_power_w = EXCLUDED.max_export_power_w,
|
||||||
|
no_export = EXCLUDED.no_export,
|
||||||
|
reserved_capacity_w = EXCLUDED.reserved_capacity_w,
|
||||||
|
notes = EXCLUDED.notes;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.site_market_config smc
|
||||||
|
WHERE smc.site_id = v_site_id AND smc.valid_to IS NULL
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.site_market_config (
|
||||||
|
site_id,
|
||||||
|
purchase_pricing_mode, sale_pricing_mode,
|
||||||
|
buy_margin_fixed_czk, buy_margin_percent,
|
||||||
|
sell_margin_fixed_czk, sell_margin_percent,
|
||||||
|
currency, valid_from, valid_to, notes,
|
||||||
|
tariff_id, hdo_code_id, system_services_czk_kwh, ote_fee_czk_kwh,
|
||||||
|
buy_fixed_energy_nt_czk_kwh, buy_fixed_vt_surcharge_czk_kwh
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id,
|
||||||
|
'fixed', 'spot',
|
||||||
|
0, 0,
|
||||||
|
-0.020, 0,
|
||||||
|
'CZK', now(), NULL,
|
||||||
|
'Nákup fixní 3,67 Kč/kWh bez DPH (NT) + 0,52 Kč/kWh bez DPH ve VT (okna dle HDO jako home-01). '
|
||||||
|
'Prodej na spotu jako home-01. Distribuce v efektivní ceně 0 (tariff_id NULL) – energie jen fix + DPH dle vat_rate výchozí.',
|
||||||
|
NULL,
|
||||||
|
v_hdo_id,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
3.67,
|
||||||
|
0.52
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO ems.site_operating_mode (site_id, mode_code, activated_by, notes)
|
||||||
|
VALUES (
|
||||||
|
v_site_id,
|
||||||
|
'MANUAL',
|
||||||
|
'migration:V043_site_25a',
|
||||||
|
'Start MANUAL; po ověření přepnout na AUTO.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (site_id) DO NOTHING;
|
||||||
|
|
||||||
|
SELECT ai.id INTO v_inv_main
|
||||||
|
FROM ems.asset_inverter ai
|
||||||
|
WHERE ai.site_id = v_site_id AND ai.code = 'deye-main'
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_inv_main IS NULL THEN
|
||||||
|
INSERT INTO ems.asset_inverter (
|
||||||
|
site_id, code, manufacturer, model, endpoint_id,
|
||||||
|
max_charge_power_w, max_discharge_power_w, max_export_power_w,
|
||||||
|
max_ac_output_w, max_dc_input_w, max_battery_charge_w, max_battery_discharge_w,
|
||||||
|
gen_port_max_power_w,
|
||||||
|
controllable, active, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id,
|
||||||
|
'deye-main',
|
||||||
|
'Deye',
|
||||||
|
NULL,
|
||||||
|
v_ep_deye,
|
||||||
|
6250, 6250, 12000,
|
||||||
|
12000, 24000, 6250, 6250,
|
||||||
|
5000,
|
||||||
|
true, true,
|
||||||
|
'12kW LV hybrid. Baterie limit 0,5C ≈ 6,25 kW (280 A teoreticky vyšší – plánování dle 6,25 kW). '
|
||||||
|
'GEN port max ~5 kW součet MI.'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_inv_main;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT ai.id INTO v_inv_gen
|
||||||
|
FROM ems.asset_inverter ai
|
||||||
|
WHERE ai.site_id = v_site_id AND ai.code = 'ongrid-gen'
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_inv_gen IS NULL THEN
|
||||||
|
INSERT INTO ems.asset_inverter (
|
||||||
|
site_id, code, manufacturer, model, endpoint_id,
|
||||||
|
max_export_power_w, controllable, active, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id,
|
||||||
|
'ongrid-gen',
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
5000, false, true,
|
||||||
|
'Mikroinvertory na GEN portu (2 skupiny panelů), EMS necurtailuje.'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_inv_gen;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_battery ab
|
||||||
|
WHERE ab.site_id = v_site_id AND ab.code = 'bat-main'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_battery (
|
||||||
|
site_id, inverter_id, code,
|
||||||
|
usable_capacity_wh, min_soc_percent, reserve_soc_percent, max_soc_percent,
|
||||||
|
charge_efficiency, discharge_efficiency, degradation_cost_czk_kwh,
|
||||||
|
max_charge_c_rate, max_discharge_c_rate, bms_max_charge_w, bms_max_discharge_w
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, v_inv_main, 'bat-main',
|
||||||
|
12500,
|
||||||
|
10, 15, 95,
|
||||||
|
0.95, 0.95,
|
||||||
|
0.50,
|
||||||
|
0.5, 0.5,
|
||||||
|
6250, 6250
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Odstranění starého agregovaného seedu (pv-a / pv-b), pokud na DB zůstal z dřívější verze.
|
||||||
|
DELETE FROM ems.forecast_accuracy fa
|
||||||
|
WHERE fa.pv_array_id IN (
|
||||||
|
SELECT id FROM ems.asset_pv_array
|
||||||
|
WHERE site_id = v_site_id AND code IN ('pv-a', 'pv-b')
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ems.forecast_pv_interval fpi
|
||||||
|
USING ems.asset_pv_array apa
|
||||||
|
WHERE apa.site_id = v_site_id
|
||||||
|
AND apa.code IN ('pv-a', 'pv-b')
|
||||||
|
AND fpi.pv_array_id = apa.id;
|
||||||
|
|
||||||
|
DELETE FROM ems.forecast_pv_run fpr
|
||||||
|
WHERE fpr.site_id = v_site_id
|
||||||
|
AND fpr.pv_array_id IN (
|
||||||
|
SELECT id FROM ems.asset_pv_array
|
||||||
|
WHERE site_id = v_site_id AND code IN ('pv-a', 'pv-b')
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ems.asset_pv_array
|
||||||
|
WHERE site_id = v_site_id AND code IN ('pv-a', 'pv-b');
|
||||||
|
|
||||||
|
-- String 1: 12×620 Wp @110° / 45° (Deye, řiditelné)
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_pv_array ap
|
||||||
|
WHERE ap.site_id = v_site_id AND ap.code = 'pv-str-1'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_pv_array (
|
||||||
|
site_id, inverter_id, code, name,
|
||||||
|
nominal_power_wp, azimuth_deg, tilt_deg, module_count, shading_factor,
|
||||||
|
controllable, telemetry_source, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, v_inv_main, 'pv-str-1', 'String 1 – 12×620 Wp',
|
||||||
|
7440, 110, 45, 12, 1.0, true, 'pv_strings',
|
||||||
|
'Hlavní telemetrie stringů Deye (pv1+pv2); druhý string má telemetry_source NULL.'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- String 2: 8×620 Wp @200° / 10° (Deye, řiditelné)
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_pv_array ap
|
||||||
|
WHERE ap.site_id = v_site_id AND ap.code = 'pv-str-2'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_pv_array (
|
||||||
|
site_id, inverter_id, code, name,
|
||||||
|
nominal_power_wp, azimuth_deg, tilt_deg, module_count, shading_factor,
|
||||||
|
controllable, telemetry_source, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, v_inv_main, 'pv-str-2', 'String 2 – 8×620 Wp',
|
||||||
|
4960, 200, 10, 8, 1.0, true, NULL,
|
||||||
|
'Vlastní predikce orientace; telemetrie sdílená se stringem 1.'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- MI 5×620 Wp @200° / 45° (GEN, neriditelné)
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_pv_array ap
|
||||||
|
WHERE ap.site_id = v_site_id AND ap.code = 'pv-mi-1'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_pv_array (
|
||||||
|
site_id, inverter_id, code, name,
|
||||||
|
nominal_power_wp, azimuth_deg, tilt_deg, module_count, shading_factor,
|
||||||
|
controllable, telemetry_source, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, v_inv_gen, 'pv-mi-1', 'Mikroinvertory 5×620 Wp',
|
||||||
|
3100, 200, 45, 5, 1.0, false, 'gen_port',
|
||||||
|
'Souhrnná telemetrie GEN portu; druhá MI skupina má telemetry NULL.'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- MI 3×620 Wp @110° / 10° (GEN, neriditelné)
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_pv_array ap
|
||||||
|
WHERE ap.site_id = v_site_id AND ap.code = 'pv-mi-2'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_pv_array (
|
||||||
|
site_id, inverter_id, code, name,
|
||||||
|
nominal_power_wp, azimuth_deg, tilt_deg, module_count, shading_factor,
|
||||||
|
controllable, telemetry_source, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, v_inv_gen, 'pv-mi-2', 'Mikroinvertory 3×620 Wp',
|
||||||
|
1860, 110, 10, 3, 1.0, false, NULL,
|
||||||
|
'Predikce samostatně; gen_port u pv-mi-1.'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM ems.asset_ev_charger c
|
||||||
|
WHERE c.site_id = v_site_id AND c.code = 'ev-charger-1'
|
||||||
|
) THEN
|
||||||
|
INSERT INTO ems.asset_ev_charger (
|
||||||
|
site_id, code, manufacturer, model, endpoint_id,
|
||||||
|
max_power_w, min_power_w, phases, connector_count, schedulable, notes
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id, 'ev-charger-1', 'Teltonika', 'TeltoCharge 22kW',
|
||||||
|
v_ep_ev,
|
||||||
|
22000, 1380, 3, 1, true,
|
||||||
|
'Jedna nabíječka; kód ev-charger-1 kvůli planneru / telemetrii.'
|
||||||
|
)
|
||||||
|
RETURNING id INTO v_ch_id;
|
||||||
|
ELSE
|
||||||
|
SELECT id INTO v_ch_id FROM ems.asset_ev_charger
|
||||||
|
WHERE site_id = v_site_id AND code = 'ev-charger-1'
|
||||||
|
LIMIT 1;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO ems.asset_vehicle (
|
||||||
|
site_id, code, name, make, model,
|
||||||
|
battery_capacity_kwh, max_charge_power_w, default_charger_id, api_type,
|
||||||
|
default_target_soc_pct, default_deadline_hour, active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
v_site_id,
|
||||||
|
'ev-default',
|
||||||
|
'EV (výchozí)',
|
||||||
|
NULL, NULL,
|
||||||
|
60.0,
|
||||||
|
11000,
|
||||||
|
v_ch_id,
|
||||||
|
'none',
|
||||||
|
80,
|
||||||
|
7,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (site_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
@@ -14,6 +14,7 @@ STABLE
|
|||||||
AS $$
|
AS $$
|
||||||
DECLARE
|
DECLARE
|
||||||
v_spot_price NUMERIC;
|
v_spot_price NUMERIC;
|
||||||
|
v_energy_czk NUMERIC;
|
||||||
v_dist_rate NUMERIC;
|
v_dist_rate NUMERIC;
|
||||||
v_system_services NUMERIC;
|
v_system_services NUMERIC;
|
||||||
v_ote_fee NUMERIC;
|
v_ote_fee NUMERIC;
|
||||||
@@ -27,8 +28,14 @@ DECLARE
|
|||||||
v_hdo_code_id INT;
|
v_hdo_code_id INT;
|
||||||
v_tariff_id INT;
|
v_tariff_id INT;
|
||||||
v_rate_type TEXT;
|
v_rate_type TEXT;
|
||||||
|
v_purchase_mode TEXT;
|
||||||
|
v_fixed_nt NUMERIC;
|
||||||
|
v_fixed_vt_sur NUMERIC;
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT
|
SELECT
|
||||||
|
smc.purchase_pricing_mode,
|
||||||
|
smc.buy_fixed_energy_nt_czk_kwh,
|
||||||
|
smc.buy_fixed_vt_surcharge_czk_kwh,
|
||||||
smc.buy_margin_fixed_czk,
|
smc.buy_margin_fixed_czk,
|
||||||
smc.buy_margin_percent,
|
smc.buy_margin_percent,
|
||||||
smc.system_services_czk_kwh,
|
smc.system_services_czk_kwh,
|
||||||
@@ -37,6 +44,9 @@ BEGIN
|
|||||||
smc.tariff_id,
|
smc.tariff_id,
|
||||||
dt.vat_rate
|
dt.vat_rate
|
||||||
INTO
|
INTO
|
||||||
|
v_purchase_mode,
|
||||||
|
v_fixed_nt,
|
||||||
|
v_fixed_vt_sur,
|
||||||
v_buy_margin_fixed,
|
v_buy_margin_fixed,
|
||||||
v_buy_margin_pct,
|
v_buy_margin_pct,
|
||||||
v_system_services,
|
v_system_services,
|
||||||
@@ -62,10 +72,6 @@ BEGIN
|
|||||||
AND interval_start = p_interval_start
|
AND interval_start = p_interval_start
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
IF v_spot_price IS NULL THEN
|
|
||||||
RETURN NULL;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
v_local_time := (p_interval_start AT TIME ZONE 'Europe/Prague')::TIME;
|
v_local_time := (p_interval_start AT TIME ZONE 'Europe/Prague')::TIME;
|
||||||
v_dow := EXTRACT(DOW FROM p_interval_start AT TIME ZONE 'Europe/Prague');
|
v_dow := EXTRACT(DOW FROM p_interval_start AT TIME ZONE 'Europe/Prague');
|
||||||
-- 0=neděle, 6=sobota
|
-- 0=neděle, 6=sobota
|
||||||
@@ -106,11 +112,23 @@ BEGIN
|
|||||||
v_ote_fee := COALESCE(v_ote_fee, 0);
|
v_ote_fee := COALESCE(v_ote_fee, 0);
|
||||||
v_buy_margin_fixed := COALESCE(v_buy_margin_fixed, 0);
|
v_buy_margin_fixed := COALESCE(v_buy_margin_fixed, 0);
|
||||||
v_buy_margin_pct := COALESCE(v_buy_margin_pct, 0);
|
v_buy_margin_pct := COALESCE(v_buy_margin_pct, 0);
|
||||||
v_buy_margin := v_buy_margin_fixed + (v_spot_price * v_buy_margin_pct / 100.0);
|
|
||||||
v_vat_rate := COALESCE(v_vat_rate, 0.21);
|
v_vat_rate := COALESCE(v_vat_rate, 0.21);
|
||||||
|
v_fixed_vt_sur := COALESCE(v_fixed_vt_sur, 0);
|
||||||
|
|
||||||
|
IF upper(trim(COALESCE(v_purchase_mode, ''))) = 'FIXED'
|
||||||
|
AND v_fixed_nt IS NOT NULL THEN
|
||||||
|
v_energy_czk := v_fixed_nt
|
||||||
|
+ CASE WHEN v_is_vt THEN v_fixed_vt_sur ELSE 0 END;
|
||||||
|
ELSIF v_spot_price IS NULL THEN
|
||||||
|
RETURN NULL;
|
||||||
|
ELSE
|
||||||
|
v_energy_czk := v_spot_price;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_buy_margin := v_buy_margin_fixed + (v_energy_czk * v_buy_margin_pct / 100.0);
|
||||||
|
|
||||||
RETURN ROUND(
|
RETURN ROUND(
|
||||||
(v_spot_price + v_dist_rate + v_system_services + v_ote_fee + v_buy_margin)
|
(v_energy_czk + v_dist_rate + v_system_services + v_ote_fee + v_buy_margin)
|
||||||
* (1 + v_vat_rate),
|
* (1 + v_vat_rate),
|
||||||
6
|
6
|
||||||
);
|
);
|
||||||
@@ -119,8 +137,9 @@ $$;
|
|||||||
|
|
||||||
COMMENT ON FUNCTION ems.fn_effective_buy_price(INT, TIMESTAMPTZ) IS
|
COMMENT ON FUNCTION ems.fn_effective_buy_price(INT, TIMESTAMPTZ) IS
|
||||||
'Efektivní nákupní cena elektřiny Kč/kWh včetně DPH.
|
'Efektivní nákupní cena elektřiny Kč/kWh včetně DPH.
|
||||||
Složky: spot OTE + distribuce NT/VT (dle HDO) + systémové služby + OTE poplatek + marže (fix + % ze spotu).
|
Režim spot: energie = OTE buy_raw + distribuce NT/VT (dle HDO) + systémové služby + OTE poplatek + marže (fix + % z energie).
|
||||||
DPH aplikováno na celou částku. Distribuce závisí na HDO kódu site.';
|
Režim fixed: energie = buy_fixed_energy_nt_czk_kwh (+ buy_fixed_vt_surcharge_czk_kwh ve VT oknech dle HDO), pak stejné příplatky a DPH.
|
||||||
|
DPH aplikováno na celou částku.';
|
||||||
|
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
- **Záporná nákupní cena:**
|
- **Záporná nákupní cena:**
|
||||||
- horní mez `grid_import` zahrnuje `load_baseline_w` + nabíjení/EV/TČ (bez nekonečného importu).
|
- horní mez `grid_import` zahrnuje `load_baseline_w` + nabíjení/EV/TČ (bez nekonečného importu).
|
||||||
- **Uložené vstupy plánu** (`planning_interval`): `load_baseline_w`, `pv_*_forecast_raw_w`, `pv_*_forecast_solver_w` pro UI a audit.
|
- **Uložené vstupy plánu** (`planning_interval`): `load_baseline_w`, `pv_*_forecast_raw_w`, `pv_*_forecast_solver_w` pro UI a audit.
|
||||||
|
- **Více FVE polí s různou orientací:** `planning_engine._load_slots` sčítá predikovaný výkon za 15min přes **všechna** `asset_pv_array` dané lokality — `pv_a_forecast_w` = součet řádků s `controllable = true`, `pv_b_forecast_w` = součet s `controllable = false`. Pro každé pole a slot se bere **nejnovější** `forecast_pv_run` (`ORDER BY created_at DESC`, `DISTINCT ON (pv_array_id)`). Curtailment v LP zůstává **jedno** agregované `pv_a` (součet řiditelných polí); per-string curtailment by vyžadovalo rozšíření modelu.
|
||||||
|
|
||||||
Solver optimalizuje celý horizont (typicky 36h) najednou, čímž přirozeně zvládá:
|
Solver optimalizuje celý horizont (typicky 36h) najednou, čímž přirozeně zvládá:
|
||||||
- pohled dopředu (ráno ví že přes poledne bude záporná cena → prodává z baterie)
|
- pohled dopředu (ráno ví že přes poledne bude záporná cena → prodává z baterie)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Použij jako checklist při přidávání dalšího objektu do EMS. Odkazy: dato
|
|||||||
|------|---------|----------|
|
|------|---------|----------|
|
||||||
| ☐ | `ems.asset_inverter` | Vazba na `endpoint_id` kde je potřeba; `controllable`, `active`; výkonové limity |
|
| ☐ | `ems.asset_inverter` | Vazba na `endpoint_id` kde je potřeba; `controllable`, `active`; výkonové limity |
|
||||||
| ☐ | `ems.asset_battery` | Vazba na střídač; SoC limity, účinnosti, degradace — **solver očekává baterii**, jinak denní/rolling plán padá |
|
| ☐ | `ems.asset_battery` | Vazba na střídač; SoC limity, účinnosti, degradace — **solver očekává baterii**, jinak denní/rolling plán padá |
|
||||||
| ☐ | `ems.asset_pv_array` | Min. jedno pole k FVE forecastu / solveru; `controllable` u pole A vs B dle [`CLAUDE.md`](../CLAUDE.md) |
|
| ☐ | `ems.asset_pv_array` | Jedno nebo více polí (různé orientace = vlastní forecast běh na `id`). Plánovač **sčítá** predikce: `pv_a` = všechna `controllable = true`, `pv_b` = všechna `false` (viz [`docs/04-modules/planning.md`](04-modules/planning.md)). Kódy `pv-a` / `pv-b` už nejsou nutné. |
|
||||||
| ☐ | `ems.asset_ev_charger` | Volitelné; `endpoint_id`, výkony |
|
| ☐ | `ems.asset_ev_charger` | Volitelné; `endpoint_id`, výkony |
|
||||||
| ☐ | `ems.asset_heat_pump` | Volitelné; TČ parametry, TUV |
|
| ☐ | `ems.asset_heat_pump` | Volitelné; TČ parametry, TUV |
|
||||||
| ☐ | `ems.asset_vehicle` | Až **dva** záznamy na site (EV1/EV2 sloty ve solveru), pokud řešíš nabíjení |
|
| ☐ | `ems.asset_vehicle` | Až **dva** záznamy na site (EV1/EV2 sloty ve solveru), pokud řešíš nabíjení |
|
||||||
|
|||||||
Reference in New Issue
Block a user