From 30f16a14c2f8091fe79b785cacc2b3591146e836 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Tue, 12 May 2026 22:41:35 +0200 Subject: [PATCH] fix toolu --- docs/04-modules/market-prices.md | 2 ++ scripts/analysis/battery_sizing_screen.py | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/04-modules/market-prices.md b/docs/04-modules/market-prices.md index 38e558c..f507a52 100644 --- a/docs/04-modules/market-prices.md +++ b/docs/04-modules/market-prices.md @@ -145,6 +145,8 @@ Volitelně pak na celý součet aplikuje: Tato logika je implementovaná přímo ve `build_buy_prices_96()` v `scripts/analysis/battery_sizing_screen.py`. Účel je screening nové lokality nebo obchodního modelu ještě před seedem do DB; nejde o náhradu `ems.fn_effective_buy_price`. +Skript navíc v `solve_one_day()` explicitně zakazuje současný import a export do sítě v jednom 15min slotu a zároveň současné nabíjení a vybíjení baterie. Tím se eliminuje artefakt, kdy by při výhodnějším `buy` než `sell` model vytvářel umělý „loop“ bez fyzického významu. + Ověření: - spusť skript nad krátkým vzorkem OTE (`--price-csv` nebo `--db`) a zkontroluj vypsané shrnutí režimu nákupu diff --git a/scripts/analysis/battery_sizing_screen.py b/scripts/analysis/battery_sizing_screen.py index a49eb4d..288fc03 100644 --- a/scripts/analysis/battery_sizing_screen.py +++ b/scripts/analysis/battery_sizing_screen.py @@ -41,8 +41,9 @@ E_d z PVGIS CSV (--pvgis-csv, opakovat pro více orientací); denní energie = E NT/VT podle hodin Europe/Prague (--buy-nt-kwh, VT = NT + --buy-vt-surcharge-kwh), nebo od raw OTE spotu: --buy-spot-add-fixed-kwh / --buy-spot-asym-pct; u všech režimů lze přičíst --buy-distribution-kwh a --buy-other-fees-kwh a výslednou cenu násobit ---buy-vat-multiplier. Mikroinvertory / GEN nejsou; zelený bonus není v účelové funkci. -Výsledek = screening, ne nabídka. +--buy-vat-multiplier. Model explicitně zakazuje současný import+export a současné +nabíjení+vybíjení v jednom slotu. Mikroinvertory / GEN nejsou; zelený bonus není +v účelové funkci. Výsledek = screening, ne nabídka. """ from __future__ import annotations @@ -404,15 +405,23 @@ def solve_one_day( gexp = pulp.LpVariable.dicts("gexp", range(SLOTS_PER_DAY), lowBound=0) gimp = pulp.LpVariable.dicts("gimp", range(SLOTS_PER_DAY), lowBound=0) curt = pulp.LpVariable.dicts("curt", range(SLOTS_PER_DAY), lowBound=0) + batt_is_charging = pulp.LpVariable.dicts( + "batt_is_charging", range(SLOTS_PER_DAY), lowBound=0, upBound=1, cat="Binary" + ) + grid_is_import = pulp.LpVariable.dicts( + "grid_is_import", range(SLOTS_PER_DAY), lowBound=0, upBound=1, cat="Binary" + ) prob += soc[0] == soc_start_wh obj = [] for t in range(SLOTS_PER_DAY): - prob += ch[t] <= max_ch - prob += dis[t] <= max_dis - prob += gexp[t] <= max_exp - prob += gimp[t] <= max_imp + # Anti-loop constraints: a slot cannot both charge and discharge the battery, + # and it cannot both import from and export to the grid. + prob += ch[t] <= max_ch * batt_is_charging[t] + prob += dis[t] <= max_dis * (1 - batt_is_charging[t]) + prob += gimp[t] <= max_imp * grid_is_import[t] + prob += gexp[t] <= max_exp * (1 - grid_is_import[t]) prob += curt[t] <= pv_wh[t] prob += ( pv_wh[t] - curt[t] + dis[t] + gimp[t] == load_wh[t] + ch[t] + gexp[t]