diff --git a/docs/onboarding-wallbox-tc-2026-06.md b/docs/onboarding-wallbox-tc-2026-06.md new file mode 100644 index 0000000..1e66b26 --- /dev/null +++ b/docs/onboarding-wallbox-tc-2026-06.md @@ -0,0 +1,35 @@ +# Oživení: nový Teltonika wallbox + tepelné čerpadlo (draft 2026-06-11) + +Stav: ČEKÁ NA VSTUPY od uživatele (sekce 1). Postup ověřen proti kódu +(telemetry_collector, control/outputs, views vw_asset_*_modbus_poll) a seedu +home-01 (V003). Plný checklist se vzorovými SQL: viz analýza agenta / tento dokument. + +## 1. Chybějící vstupy (dodá člověk) + +| Zařízení | Údaj | +|----------|------| +| Wallbox | **lokalita** (site code), IP Waveshare + unit ID, max proud (A), fáze | +| Wallbox | Modbus registry TeltoCharge: status / proud / energie (datasheet) | +| TČ | **lokalita**, model (Samsung EHS …?), IP + unit ID | +| TČ | jmenovitý výkon (W), COP@7°C, objem TUV (l), registry: stav/výkon/teploty/enable | +| Vozidlo | kapacita baterie (kWh), max AC výkon, default target SoC + deadline | + +## 2. Postup (po dodání vstupů) + +1. Flyway migrace `V085__seed__wallbox_tc.sql`: `site_endpoint` (modbus_tcp) + + `asset_ev_charger` (+ `asset_vehicle`) + `asset_heat_pump` — šablony dle seedu V003. +2. Deploy (push na main) → collector čte `vw_asset_ev_charger_modbus_poll` / + `vw_asset_heat_pump_modbus_poll` → po restartu backendu začne pollovat. +3. **Pozor:** v `telemetry_collector.py` a `control/outputs.py` jsou pro + Teltoniku/Samsung části registrové mapy jako TODO — po dodání datasheetů + doplnit čtení (status/proud/energie; teploty/stav) a zápis (proud limit / + enable) přes FC dle zařízení + journal `modbus_command`. +4. Ověření: `vw_latest_ev_charger` / `vw_latest_heat_pump` (data_age < 2 min); + příjezd EV → `ev_session` + `fn_update_ev_arrival_stats`; TČ → + `tuv_delta_stats` po pár dnech (job 00:45) → solver začne TUV plánovat. + +## 3. Co EMS po oživení umí hned + +- EV: detekce příjezdu/odjezdu, session s deadline, plánované nabíjení v levných + slotech (v2: EV deadline constraint s placeným deficitem). +- TČ: TUV look-ahead v plánu (musí-topit okna), enable řízení exporterem. diff --git a/scripts/harness/battery_upgrade_study.py b/scripts/harness/battery_upgrade_study.py index fafb32e..85fbe14 100644 --- a/scripts/harness/battery_upgrade_study.py +++ b/scripts/harness/battery_upgrade_study.py @@ -131,12 +131,34 @@ async def _load_slots(conn: asyncpg.Connection, site_id: int) -> list[Slot]: select a.interval_start, p.effective_buy_price_czk_kwh as buy, p.effective_sell_price_czk_kwh as sell, - greatest(0, coalesce(a.actual_pv_production_wh,0) - coalesce(a.pv_b_production_wh,0)) as pv_a, - coalesce(a.pv_b_production_wh,0) as pv_b, + -- POTENCIÁL: při sell<0 lokalita škrtí výrobu (reg 340 / GEN cutoff), + -- telemetrie ji nevidí → použij max(skutečnost, predikce) per pole. + case when p.effective_sell_price_czk_kwh < 0 + then greatest(coalesce(a.actual_pv_production_wh,0) - coalesce(a.pv_b_production_wh,0), + coalesce(fc.fc_a_wh, 0)) + else greatest(0, coalesce(a.actual_pv_production_wh,0) - coalesce(a.pv_b_production_wh,0)) + end as pv_a, + case when p.effective_sell_price_czk_kwh < 0 + then greatest(coalesce(a.pv_b_production_wh,0), coalesce(fc.fc_b_wh, 0)) + else coalesce(a.pv_b_production_wh,0) + end as pv_b, coalesce(a.actual_load_consumption_wh,0) as load from ems.audit_interval a join ems.vw_site_effective_price p on p.site_id = a.site_id and p.interval_start = a.interval_start + left join lateral ( + select + sum(power_w) filter (where pa.controllable) * 0.25 as fc_a_wh, + sum(power_w) filter (where not pa.controllable) * 0.25 as fc_b_wh + from ( + select distinct on (fpr.pv_array_id) fpi2.power_w, fpr.pv_array_id + from ems.forecast_pv_interval fpi2 + join ems.forecast_pv_run fpr on fpr.id = fpi2.run_id + where fpi2.interval_start = a.interval_start + order by fpr.pv_array_id, fpr.created_at desc + ) x + join ems.asset_pv_array pa on pa.id = x.pv_array_id and pa.site_id = a.site_id + ) fc on true where a.site_id = $1 and a.actual_load_consumption_wh is not null order by a.interval_start """, diff --git a/scripts/harness/hu1_bess_study.py b/scripts/harness/hu1_bess_study.py index f89c11b..ff705b0 100644 --- a/scripts/harness/hu1_bess_study.py +++ b/scripts/harness/hu1_bess_study.py @@ -60,15 +60,31 @@ async def _load(conn: asyncpg.Connection, price_site: int = 3) -> list[Slot]: else p.effective_buy_price_czk_kwh end as buy, case when $1 = 5 then p2.effective_sell_price_czk_kwh else p.effective_sell_price_czk_kwh end as sell, + -- POTENCIÁL: BA81 při sell<0 škrtí (81 % výroby v datech chybí) case when p.effective_sell_price_czk_kwh < 0 - then greatest(0, coalesce(a.actual_pv_production_wh,0) - - coalesce(a.actual_load_consumption_wh,0)) + then greatest(0, + greatest(coalesce(a.actual_pv_production_wh,0), + coalesce(fc.fc_a_wh,0) + coalesce(fc.fc_b_wh,0)) + - coalesce(a.actual_load_consumption_wh,0)) else 0 end as share_wh from ems.audit_interval a join ems.vw_site_effective_price p on p.site_id = a.site_id and p.interval_start = a.interval_start left join ems.vw_site_effective_price p2 on p2.site_id = 5 and p2.interval_start = a.interval_start + left join lateral ( + select + sum(power_w) filter (where pa.controllable) * 0.25 as fc_a_wh, + sum(power_w) filter (where not pa.controllable) * 0.25 as fc_b_wh + from ( + select distinct on (fpr.pv_array_id) fpi2.power_w, fpr.pv_array_id + from ems.forecast_pv_interval fpi2 + join ems.forecast_pv_run fpr on fpr.id = fpi2.run_id + where fpi2.interval_start = a.interval_start + order by fpr.pv_array_id, fpr.created_at desc + ) x + join ems.asset_pv_array pa on pa.id = x.pv_array_id and pa.site_id = a.site_id + ) fc on true where a.site_id = 3 and a.actual_load_consumption_wh is not null and p2.effective_buy_price_czk_kwh is not null order by a.interval_start diff --git a/scripts/harness/investment_study_results_2026-06-11.txt b/scripts/harness/investment_study_results_2026-06-11.txt new file mode 100644 index 0000000..b8408dd --- /dev/null +++ b/scripts/harness/investment_study_results_2026-06-11.txt @@ -0,0 +1,33 @@ +# Studie navýšení baterie — perfect-hindsight nad reálnými daty (audit_interval) +# Okna 7 dní s navazujícím SoC; Δ = horní mez ročního přínosu + +## BA81 (2026-04-26 … 2026-06-11; block_neg=False, pv_b_shed=True, export cap 16 kW) + current 12.5 kWh / 6.25 kW -4579 Kč / 45 dní + upgrade 32 kWh / 6.25 kW (výkon beze změny) -6144 Kč Δ +1565 Kč (+34.77 Kč/den; rok ~7615–12692 Kč) + upgrade 32 kWh / 12.00 kW (0.5C, cap AC stridace) -6657 Kč Δ +2078 Kč (+46.19 Kč/den; rok ~10115–16858 Kč) + +## KV1 (2026-04-30 … 2026-06-11; block_neg=True, pv_b_shed=False, export cap 8 kW) + current 12.5 kWh / 6.25 kW -2149 Kč / 41 dní + upgrade 25 kWh / 6.25 kW (výkon beze změny) -2952 Kč Δ +803 Kč (+19.58 Kč/den; rok ~4289–7148 Kč) + upgrade 25 kWh / 12.00 kW (0.5C, cap AC stridace) -3056 Kč Δ +907 Kč (+22.12 Kč/den; rok ~4844–8073 Kč) + +Pozn.: rok = Kč/den × 365; dolní odhad ×0.6 (zima: méně PV, menší spready). +Horní mez (dokonalá předpověď) — reálný plánovač zachytí typicky 70–90 %. +# HU1 BESS studie — 128 kWh / 36 kW / AC 40 kW; ceny site 3 (fixní nákup BA81) +# Období: 18 dní (BA81 audit); sdílitelný přebytek BA81 při sell<0: 882 kWh + + bez EDC sdílení výnos 1351 Kč = 75.06 Kč/den (rok ~16438–27396 Kč) + EDC sdílení, distribuce 2.0 Kč/kWh výnos 2251 Kč = 125.05 Kč/den (rok ~27386–45643 Kč) + EDC sdílení, distribuce 1.5 Kč/kWh výnos 2689 Kč = 149.37 Kč/den (rok ~32712–54520 Kč) + EDC sdílení, distribuce 1.0 Kč/kWh výnos 3130 Kč = 173.86 Kč/den (rok ~38076–63460 Kč) + +Pozn.: horní mez (perfect hindsight); jaro = nejsilnější sezóna pro spot spready. +# HU1 BESS studie — 128 kWh / 36 kW / AC 40 kW; ceny site 5 (SPOT nákup i prodej (site 5)) +# Období: 18 dní (BA81 audit); sdílitelný přebytek BA81 při sell<0: 882 kWh + + bez EDC sdílení výnos 6699 Kč = 372.17 Kč/den (rok ~81505–135842 Kč) + EDC sdílení, distribuce 2.0 Kč/kWh výnos 6699 Kč = 372.17 Kč/den (rok ~81505–135842 Kč) + EDC sdílení, distribuce 1.5 Kč/kWh výnos 6699 Kč = 372.17 Kč/den (rok ~81505–135842 Kč) + EDC sdílení, distribuce 1.0 Kč/kWh výnos 6699 Kč = 372.17 Kč/den (rok ~81505–135842 Kč) + +Pozn.: horní mez (perfect hindsight); jaro = nejsilnější sezóna pro spot spready.