From ec13c2ad6e36684a655b2e015cb5e2a95e2dbb27 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Thu, 11 Jun 2026 14:02:17 +0200 Subject: [PATCH] =?UTF-8?q?F=C3=A1ze=202/3:=20roz=C5=A1=C3=AD=C5=99en?= =?UTF-8?q?=C3=BD=20penalty=20audit=20+=20prototyp=20=C4=8Dist=C3=A9ho=20j?= =?UTF-8?q?=C3=A1dra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Penalty audit (6 fixtures vč. evening_push a extreme_neg_buy): - stejných 16/26 penalt mrtvých i na rozšířeném pokrytí (vč. EVENING_PUSH_Z_EXPORT_BONUS=2500 na evening-push dni) - žádná penalta nezpůsobuje Infeasible 2026-05-01 (strukturální problém) - Σpenalty 7978 Kč vs cashflow −614 Kč clean_core_prototype.py: čistý ekonomický MILP (bez heuristických penalt) na IDENTICKÝCH vstupech fixtures vs golden snapshoty současného plánovače: - lepší na všech 5 řešitelných fixtures, celkem +266 Kč (+25 %) za horizonty - extrémní den 2026-05-01: current INFEASIBLE → clean OK (−713 Kč zisk) - férové: současné plány mají hp/ev setpointy 0, čistý dispatch srovnání Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/harness/clean_core_prototype.py | 115 ++++++++++++++++++ .../penalty_audit_baseline_2026-06-11.txt | 22 ++-- 2 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 scripts/harness/clean_core_prototype.py diff --git a/scripts/harness/clean_core_prototype.py b/scripts/harness/clean_core_prototype.py new file mode 100644 index 0000000..6b1a0ce --- /dev/null +++ b/scripts/harness/clean_core_prototype.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Fáze 3 (teaser) – prototyp čistého jádra plánovače nad golden fixtures. + +Vezme STEJNÉ vstupy jako produkční solver (golden fixtures: forecast PV, baseline +load, efektivní ceny, battery/grid context) a vyřeší je ČISTÝM ekonomickým MILP +(scripts/harness/economics_report.solve_oracle): cash + degradace + terminal SoC, +tvrdá pravidla (block_export_on_negative_sell, curtail jen pole A, výkonové stropy), +ŽÁDNÉ heuristické penalty. + +Porovná s výsledkem současného plánovače (golden snapshoty): + - cashflow current vs clean na identických vstupech (modelované Kč), + - feasibility (extrémní den 2026-05-01 je pro současný plánovač Infeasible). + +Není to produkční náhrada (chybí EV deadline, TČ/TUV, provozní režimy) — je to +měření, kolik ekonomiky stojí heuristická vrstva. Spouštět z backend/: + python3 ../scripts/harness/clean_core_prototype.py +""" + +from __future__ import annotations + +import importlib.util +import json +import sys +from datetime import datetime +from pathlib import Path + +HARNESS = Path(__file__).resolve().parent +BACKEND = HARNESS.parents[1] / "backend" +sys.path.insert(0, str(BACKEND)) + +_spec = importlib.util.spec_from_file_location("econ", HARNESS / "economics_report.py") +econ = importlib.util.module_from_spec(_spec) +sys.modules["econ"] = econ # dataclasses vyžadují modul v sys.modules +_spec.loader.exec_module(econ) + +FIXTURES = sorted((BACKEND / "tests" / "golden" / "fixtures").glob("*.json")) +SNAPSHOTS = BACKEND / "tests" / "golden" / "snapshots" + + +def _fixture_to_inputs(fx: dict): + ctx = fx["context_json"] + b = ctx["battery"] + bat = econ.BatteryParams( + usable_wh=float(b["usable_capacity_wh"]), + min_soc_wh=float(b["min_soc_wh"]), + soc_max_wh=float(b.get("planner_soc_max_wh", b["soc_max_wh"])), + charge_eff=float(b["charge_efficiency"]), + discharge_eff=float(b["discharge_efficiency"]), + max_charge_w=float(b["max_charge_power_w"]), + max_discharge_w=float(b["max_discharge_power_w"]), + degradation_czk_kwh=float(b["degradation_cost_czk_kwh"]), + ) + g = ctx["grid"] + grid = { + "max_import_w": float(g["max_import_power_w"]), + "max_export_w": float(g["max_export_power_w"]), + "block_export_on_negative_sell": bool(g.get("block_export_on_negative_sell") or False), + } + slots = [] + for r in fx["slot_rows"]: + # forecast (W) → energie slotu (Wh): ×0.25 h + slots.append( + econ.DaySlot( + interval_start=datetime.fromisoformat(r["interval_start"]), + buy=float(r["buy_price"]), + sell=float(r["sell_price"]), + pv_a_wh=float(r["pv_a_forecast_w"] or 0) * 0.25, + pv_b_wh=float(r["pv_b_forecast_w"] or 0) * 0.25, + load_wh=float(r["load_baseline_w"] or 0) * 0.25, + grid_import_wh=0.0, + grid_export_wh=0.0, + soc_pct=None, + ) + ) + soc0 = float(ctx["soc_wh"]) + return slots, bat, grid, soc0 + + +def main() -> None: + header = ( + f"{'fixture':<42} {'current':>9} {'clean':>9} {'Δ':>8} pozn." + ) + print("# Clean core prototyp vs současný plánovač (modelovaný cashflow, Kč/horizont)") + print("# Δ < 0 = čisté jádro vydělá víc na stejných vstupech (bez SoC adjustu — terminal value v objective obou)") + print() + print(header) + print("-" * len(header)) + total_cur = total_clean = 0.0 + for path in FIXTURES: + fx = json.loads(path.read_text(encoding="utf-8")) + slots, bat, grid, soc0 = _fixture_to_inputs(fx) + avg_buy = sum(s.buy for s in slots[: 96]) / min(96, len(slots)) + factor = float(fx["context_json"]["battery"].get("planner_terminal_soc_value_factor") or 1.0) + cash, soc_end = econ.solve_oracle(slots, bat, grid, soc0, avg_buy * factor) + # SoC-fér: ocenit koncový SoC stejně jako terminal value + clean_adj = cash - soc_end / 1000.0 * avg_buy * factor + + snap = json.loads((SNAPSHOTS / path.name).read_text(encoding="utf-8")) + if "solver_error" in snap: + print(f"{path.stem:<42} {'INFEAS':>9} {clean_adj:>9.1f} {'—':>8} current selhal, clean OK") + continue + cur_cash = snap["totals"]["cashflow_czk"] + cur_soc_end = snap["slots"][-1]["battery_soc_target"] / 100.0 * bat.usable_wh + cur_adj = cur_cash - cur_soc_end / 1000.0 * avg_buy * factor + d = clean_adj - cur_adj + total_cur += cur_adj + total_clean += clean_adj + print(f"{path.stem:<42} {cur_adj:>9.1f} {clean_adj:>9.1f} {d:>8.1f}") + print("-" * len(header)) + print(f"{'CELKEM (bez infeasible)':<42} {total_cur:>9.1f} {total_clean:>9.1f} {total_clean - total_cur:>8.1f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/harness/penalty_audit_baseline_2026-06-11.txt b/scripts/harness/penalty_audit_baseline_2026-06-11.txt index 36c6aa5..4937a04 100644 --- a/scripts/harness/penalty_audit_baseline_2026-06-11.txt +++ b/scripts/harness/penalty_audit_baseline_2026-06-11.txt @@ -1,35 +1,35 @@ # Δcashflow: záporné = plán bez penalty vydělá víc (modelované Kč za horizonty fixtures) -baseline: cashflow -440.4 Kč, penalty 2140.1 Kč +baseline: cashflow -613.6 Kč, penalty 7977.9 Kč; infeasible fixtures: ['home-01_2026-05-01_extreme_neg_buy'] konstanta hodnota Δcash Δpenalty Δsloty bind ------------------------------------------------------------------------------------------------- -CURTAILMENT_PENALTY 0.001 0.0 -134.4 0 NE (mrtvá?) +CURTAILMENT_PENALTY 0.001 0.0 -284.5 0 NE (mrtvá?) EVENING_PUSH_Z_EXPORT_BONUS_CZK 2500.0 0.0 0.0 0 NE (mrtvá?) LOAD_FIRST_INCENTIVE_CZK_KWH 0.05 0.0 0.0 0 NE (mrtvá?) NEG_BUY_CHARGE_SHORTFALL_PENALTY_CZK_KWH 100.0 0.0 0.0 0 NE (mrtvá?) NEG_EVENING_PREP_DISCHARGE_SHORTFALL_PENALTY_CZK_KWH 120.0 0.0 0.0 0 NE (mrtvá?) -NEG_EVENING_RESERVE_SOC_SLACK_PENALTY_CZK_PER_WH 55.0 1.0 -11.7 3 ano +NEG_EVENING_RESERVE_SOC_SLACK_PENALTY_CZK_PER_WH 55.0 2.2 -31.7 7 ano NEG_SELL_BAT_DUMP_SHORTFALL_PENALTY_CZK_KWH 80.0 0.0 0.0 0 NE (mrtvá?) NEG_SELL_CURTAIL_PENALTY_CZK_KWH 1.0 0.0 0.0 0 NE (mrtvá?) NEG_SELL_POST_DETACH_BCPV_DISCOURAGE_CZK_KWH 250.0 0.0 0.0 0 NE (mrtvá?) NEG_SELL_PREP_HOLD_BCPV_PENALTY_CZK_KWH 60.0 0.0 0.0 0 NE (mrtvá?) NEG_SELL_PREP_SOC_SHORTFALL_PENALTY_CZK_PER_WH 0.85 0.0 0.0 0 NE (mrtvá?) -NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH 4.0 7.5 -50.9 19 ano +NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH 4.0 14.8 -113.8 34 ano NEG_SELL_PV_CHARGE_REWARD_CZK_KWH 0.8 0.0 0.0 0 NE (mrtvá?) -NEG_SELL_SOC_UNDERFILL_PENALTY_CZK_PER_WH 0.35 1.3 -539.8 13 ano +NEG_SELL_SOC_UNDERFILL_PENALTY_CZK_PER_WH 0.35 10.0 -3903.3 40 ano NIGHT_SELF_CONSUME_IMPORT_SURCHARGE_CZK_KWH 4.0 0.3 -0.5 10 ano -PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH 80.0 33.1 -18.0 19 ano -POS_SELL_PRE_NEG_SOC_SHORTFALL_PENALTY_CZK_PER_WH 0.3 -20.6 481.2 13 ano +PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH 80.0 33.1 -828.0 19 ano +POS_SELL_PRE_NEG_SOC_SHORTFALL_PENALTY_CZK_PER_WH 0.3 -21.8 649.1 29 ano PRENEG_SELL_SOC_ANCHOR_SLACK_PENALTY_CZK_PER_WH 0.2 0.0 0.0 0 NE (mrtvá?) -PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH 80.0 -2.6 0.0 23 ano +PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH 80.0 4.0 -111.6 56 ano PRE_NEG_BUY_EMPTY_EXPORT_SHORTFALL_PENALTY_CZK_KWH 80.0 0.0 0.0 0 NE (mrtvá?) -PRE_NEG_BUY_PV_CHARGE_PENALTY_CZK_KWH 250.0 1.3 -78.4 12 ano -PRE_NEG_BUY_SOC_CEILING_SLACK_PENALTY_CZK_PER_WH 0.25 20.3 -1480.0 33 ano +PRE_NEG_BUY_PV_CHARGE_PENALTY_CZK_KWH 250.0 7.1 -45.1 24 ano +PRE_NEG_BUY_SOC_CEILING_SLACK_PENALTY_CZK_PER_WH 0.25 25.5 -2149.7 56 ano PRE_NEG_CHARGE_PENALTY_CZK_KWH 400.0 0.0 0.0 0 NE (mrtvá?) PRE_NEG_PV_BCPV_DISCOURAGE_CZK_KWH 90.0 0.0 0.0 0 NE (mrtvá?) PRE_NEG_PV_EXPORT_SHORTFALL_PENALTY_CZK_KWH 55.0 0.0 0.0 0 NE (mrtvá?) -PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH 120.0 0.3 -473.9 9 ano +PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH 120.0 0.3 -2182.6 9 ano Mrtvé penalty (žádný vliv na 4 fixtures): 16 - CURTAILMENT_PENALTY