third version before modbus cleanup
This commit is contained in:
52
db/migration/V027__planning_inputs_battery_semantics.sql
Normal file
52
db/migration/V027__planning_inputs_battery_semantics.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
-- EMS: two-tier SoC semantics (DB comments), restore arbitrage floor after V026 tuning,
|
||||
-- planning_interval solver inputs, baseline load forecast accuracy tracking.
|
||||
|
||||
-- Semantics: min_soc = absolute LP floor; reserve_soc = economic / export discharge floor
|
||||
COMMENT ON COLUMN ems.asset_battery.min_soc_percent IS
|
||||
'Minimální SoC v % – tvrdá spodní mez v LP (ochrana packu / BMS).';
|
||||
COMMENT ON COLUMN ems.asset_battery.reserve_soc_percent IS
|
||||
'Ekonomická podlaha v %: pod ní solver neplánuje „nadbytečné“ vybíjení související s exportem (MILP); nadřazuje se min_soc_percent.';
|
||||
|
||||
-- Obnovit rezervu 20 % tam, kde V026 sladila ekonomiku na reserve=10 společně s degradací 0.15
|
||||
UPDATE ems.asset_battery
|
||||
SET reserve_soc_percent = 20.00
|
||||
WHERE reserve_soc_percent = 10.00
|
||||
AND degradation_cost_czk_kwh = 0.1500;
|
||||
|
||||
ALTER TABLE ems.planning_interval
|
||||
ADD COLUMN IF NOT EXISTS load_baseline_w INT,
|
||||
ADD COLUMN IF NOT EXISTS pv_a_forecast_raw_w INT,
|
||||
ADD COLUMN IF NOT EXISTS pv_b_forecast_raw_w INT,
|
||||
ADD COLUMN IF NOT EXISTS pv_a_forecast_solver_w INT,
|
||||
ADD COLUMN IF NOT EXISTS pv_b_forecast_solver_w INT;
|
||||
|
||||
COMMENT ON COLUMN ems.planning_interval.load_baseline_w IS
|
||||
'Bazální spotřeba (W) vstupující do LP pro tento slot (stats DOW+hodina / fallback).';
|
||||
COMMENT ON COLUMN ems.planning_interval.pv_a_forecast_raw_w IS
|
||||
'FVE pole A – výkon z DB před rolling korekcí forecastu.';
|
||||
COMMENT ON COLUMN ems.planning_interval.pv_b_forecast_raw_w IS
|
||||
'FVE pole B – výkon z DB před rolling korekcí forecastu.';
|
||||
COMMENT ON COLUMN ems.planning_interval.pv_a_forecast_solver_w IS
|
||||
'FVE pole A – výkon po korekci (vstup do solve_dispatch).';
|
||||
COMMENT ON COLUMN ems.planning_interval.pv_b_forecast_solver_w IS
|
||||
'FVE pole B – výkon po korekci (vstup do solve_dispatch).';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ems.baseline_load_forecast_accuracy (
|
||||
site_id INT NOT NULL REFERENCES ems.site(id),
|
||||
interval_start TIMESTAMPTZ NOT NULL,
|
||||
planning_run_id INT NOT NULL REFERENCES ems.planning_run(id),
|
||||
forecast_baseline_w INT,
|
||||
actual_baseline_w INT,
|
||||
error_w INT,
|
||||
error_pct NUMERIC(8,4),
|
||||
lead_time_hours NUMERIC(6,2),
|
||||
filled_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (site_id, interval_start)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE ems.baseline_load_forecast_accuracy IS
|
||||
'Zpětná kontrola: plánovaný bazální výkon vs. skutečnost (load - EV - TČ za 15min z auditu).';
|
||||
COMMENT ON COLUMN ems.baseline_load_forecast_accuracy.forecast_baseline_w IS 'Vstup z planning_interval při uložení plánu.';
|
||||
COMMENT ON COLUMN ems.baseline_load_forecast_accuracy.actual_baseline_w IS 'Skutečný bazál W (shodná definice jako fn_update_baseline_stats).';
|
||||
COMMENT ON COLUMN ems.baseline_load_forecast_accuracy.lead_time_hours IS 'Hodiny mezi created_at plánu a začátkem intervalu.';
|
||||
|
||||
106
db/routines/R__fn_fill_baseline_load_forecast_accuracy.sql
Normal file
106
db/routines/R__fn_fill_baseline_load_forecast_accuracy.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
-- Doplní jeden řádek baseline_load_forecast_accuracy po vyplnění auditu (stejný interval).
|
||||
|
||||
CREATE OR REPLACE FUNCTION ems.fn_fill_baseline_load_forecast_accuracy(
|
||||
p_site_id INT,
|
||||
p_interval_start TIMESTAMPTZ
|
||||
)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_run_id INT;
|
||||
v_forecast INT;
|
||||
v_actual_load INT;
|
||||
v_actual_ev INT;
|
||||
v_actual_hp INT;
|
||||
v_actual_baseline INT;
|
||||
v_err INT;
|
||||
v_pct NUMERIC(8,4);
|
||||
v_lead NUMERIC(6,2);
|
||||
v_created TIMESTAMPTZ;
|
||||
BEGIN
|
||||
SELECT
|
||||
ai.planning_run_id,
|
||||
ai.actual_load_power_w,
|
||||
ai.actual_ev_power_w,
|
||||
ai.actual_heat_pump_power_w
|
||||
INTO v_run_id, v_actual_load, v_actual_ev, v_actual_hp
|
||||
FROM ems.audit_interval ai
|
||||
WHERE ai.site_id = p_site_id
|
||||
AND ai.interval_start = p_interval_start;
|
||||
|
||||
IF v_run_id IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT pi.load_baseline_w
|
||||
INTO v_forecast
|
||||
FROM ems.planning_interval pi
|
||||
WHERE pi.run_id = v_run_id
|
||||
AND pi.interval_start = p_interval_start;
|
||||
|
||||
IF v_forecast IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_actual_load IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_actual_baseline := GREATEST(0,
|
||||
v_actual_load
|
||||
- COALESCE(v_actual_ev, 0)
|
||||
- COALESCE(v_actual_hp, 0)
|
||||
);
|
||||
|
||||
v_err := v_forecast - v_actual_baseline;
|
||||
IF v_actual_baseline > 0 THEN
|
||||
v_pct := (v_err::NUMERIC / v_actual_baseline) * 100.0;
|
||||
ELSE
|
||||
v_pct := NULL;
|
||||
END IF;
|
||||
|
||||
SELECT pr.created_at INTO v_created
|
||||
FROM ems.planning_run pr
|
||||
WHERE pr.id = v_run_id;
|
||||
|
||||
IF v_created IS NOT NULL THEN
|
||||
v_lead := EXTRACT(EPOCH FROM (p_interval_start - v_created)) / 3600.0;
|
||||
ELSE
|
||||
v_lead := NULL;
|
||||
END IF;
|
||||
|
||||
INSERT INTO ems.baseline_load_forecast_accuracy (
|
||||
site_id,
|
||||
interval_start,
|
||||
planning_run_id,
|
||||
forecast_baseline_w,
|
||||
actual_baseline_w,
|
||||
error_w,
|
||||
error_pct,
|
||||
lead_time_hours,
|
||||
filled_at
|
||||
) VALUES (
|
||||
p_site_id,
|
||||
p_interval_start,
|
||||
v_run_id,
|
||||
v_forecast,
|
||||
v_actual_baseline,
|
||||
v_err,
|
||||
v_pct,
|
||||
v_lead,
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (site_id, interval_start) DO UPDATE SET
|
||||
planning_run_id = EXCLUDED.planning_run_id,
|
||||
forecast_baseline_w = EXCLUDED.forecast_baseline_w,
|
||||
actual_baseline_w = EXCLUDED.actual_baseline_w,
|
||||
error_w = EXCLUDED.error_w,
|
||||
error_pct = EXCLUDED.error_pct,
|
||||
lead_time_hours = EXCLUDED.lead_time_hours,
|
||||
filled_at = EXCLUDED.filled_at;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION ems.fn_fill_baseline_load_forecast_accuracy IS
|
||||
'Po fn_fill_audit_interval: uloží odchylku plánovaného load_baseline vs. skutečný bazál z auditu.';
|
||||
12
db/views/R__vw_baseline_load_forecast_accuracy.sql
Normal file
12
db/views/R__vw_baseline_load_forecast_accuracy.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE OR REPLACE VIEW ems.vw_baseline_load_forecast_accuracy_daily AS
|
||||
SELECT
|
||||
site_id,
|
||||
date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague') AS day_prague,
|
||||
COUNT(*) AS slot_count,
|
||||
AVG(ABS(error_w))::NUMERIC(12,2) AS mae_w,
|
||||
AVG(error_pct) FILTER (WHERE error_pct IS NOT NULL)::NUMERIC(8,4) AS avg_error_pct
|
||||
FROM ems.baseline_load_forecast_accuracy
|
||||
GROUP BY site_id, date_trunc('day', interval_start AT TIME ZONE 'Europe/Prague');
|
||||
|
||||
COMMENT ON VIEW ems.vw_baseline_load_forecast_accuracy_daily IS
|
||||
'Denní souhrn přesnosti predikce bazální spotřeby (|chyba| v průměru W).';
|
||||
@@ -18,3 +18,5 @@ GRANT SELECT ON ems.vw_forecast_accuracy_daily TO ems_anon;
|
||||
GRANT SELECT ON ems.consumption_baseline_stats TO ems_anon;
|
||||
GRANT SELECT ON ems.market_price_stats TO ems_anon;
|
||||
GRANT SELECT ON ems.tuv_usage_stats TO ems_anon;
|
||||
GRANT SELECT ON ems.baseline_load_forecast_accuracy TO ems_anon;
|
||||
GRANT SELECT ON ems.vw_baseline_load_forecast_accuracy_daily TO ems_anon;
|
||||
|
||||
Reference in New Issue
Block a user