SoC série, neg-sell fáze/okna, evening push, pre-neg logika — čistý přesun, fasáda v planning_engine.py beze změny chování (golden 5/5, baseline faily beze změny). Roztroušené konstanty MORNING_PRENEG_* doplněny do constants.py. planning_engine.py: 6345 → 3925 řádků (zbývá: solver, orchestrace, compare). heuristics.py nese warning: hlavní kandidáti na prune ve Fázi 2/3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 lines
6.7 KiB
Python
119 lines
6.7 KiB
Python
# backend/services/planning/constants.py
|
||
#
|
||
# EMS plánovač – konstanty (Fáze 1 dekompozice, čistý přesun z planning_engine.py).
|
||
# POZOR: ekonomické penalty/váhy jsou kandidáti na přesun do DB ve Fázi 2
|
||
# (CLAUDE.md pravidlo 16: žádný skrytý faktor v Pythonu).
|
||
|
||
from zoneinfo import ZoneInfo
|
||
|
||
# ============================================================
|
||
# Konstanty
|
||
# ============================================================
|
||
|
||
# Když DB vrátí NULL (skoro žádná OTE data), denní plán použije krátký fallback (soulad s min hodinami ve fn_planning_horizon_end).
|
||
_DAILY_FALLBACK_HORIZON_HOURS = 1.0
|
||
# Shadow cena zbytkové energie na konci horizontu: - (avg_buy * FACTOR / 1000) * soc[T-1] (Kč; soc v Wh).
|
||
INTERVAL_H = 0.25 # 15 minut v hodinách
|
||
CURTAILMENT_PENALTY = 0.001 # Kč/Wh – malá penalizace za omezení FVE pole A
|
||
SOLVER_TIME_LIMIT = 10 # sekund
|
||
# MILP: významný export ge (W) ⇒ koncové soc[t] ≥ podlaha; mimo arbitrážní relax je to arb_base_wh
|
||
# (rezerva z DB). Při relaxaci spodku před extrémně záporným buy je podlaha soc_panel_min[t]
|
||
# (planner floor), jinak by šlo jen do zátěže a nešlo by „vypustit do sítě“ před levným nákupem.
|
||
GE_MIN_EXPORT_W = 1.0
|
||
# Dvouprůchodové solve: stop když acquisition z pass1 vs pass2 se liší méně než (Kč/kWh).
|
||
ACQUISITION_TWO_PASS_EPS_KWH = 0.05
|
||
# Load-first (Deye): PV nejdřív pokryje load+EV+TČ; bc_pv/ge_pv jen z pv_sp (přebytek).
|
||
LOAD_FIRST_INCENTIVE_CZK_KWH = 0.05
|
||
# Dokud je kotva pro hluboký dump (první sell < 0 v horizontu, jinak první extrémní buy) dál než
|
||
# tento počet 15min slotů, držíme plánovací spodek na rezervě (arb_base_wh) místo planner floor —
|
||
# priorita: beze „ztráty na prodeji“ (sell >= 0) držet buffer, hluboký vývoz až těsně před záporným prodejem.
|
||
DEFAULT_PLANNER_DISCHARGE_RELAX_PREWINDOW_SLOTS = 8
|
||
# Měkká kotva: chceme být u planner floor už v posledním slotu před prvním sell < 0.
|
||
# Penalizace je v Kč/Wh (např. 0.20 = 200 Kč/kWh). Musí být dost velká, aby přebila
|
||
# bezpečnostní SoC buffer + terminal shadow cenu a solver skutečně „dovylil“ před sell<0.
|
||
PRENEG_SELL_SOC_ANCHOR_SLACK_PENALTY_CZK_PER_WH = 0.20
|
||
PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||
# Měkký tlak: v okně sell<0 + block_export využít PV přebytek do baterie (ne curtail).
|
||
PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 120.0
|
||
# Curtailment při sell<0 + allow_charge: nesmí být téměř zdarma oproti nabíjení (BA81).
|
||
NEG_SELL_CURTAIL_PENALTY_CZK_KWH = 1.0
|
||
# Odměna v objective za FVE→baterie při sell<0 (doplňuje shortfall; BA81 fixed tarif).
|
||
NEG_SELL_PV_CHARGE_REWARD_CZK_KWH = 0.8
|
||
# Měkký tlak: v okně sell<0 dobít na soc_max (ne zastavit na ~94 % kvůli curtail).
|
||
NEG_SELL_SOC_UNDERFILL_PENALTY_CZK_PER_WH = 0.35
|
||
# Jen ventil nekontrolovatelného pole B při plné baterii a sell<0 (spot); ne celý PV přebytek.
|
||
NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH = 4.0
|
||
# Fáze sell<0 (v32): ASAP na prep_soc %, tail rampa na soc_max.
|
||
NEG_SELL_PREP_SOC_SHORTFALL_PENALTY_CZK_PER_WH = 0.85
|
||
NEG_SELL_PREP_HOLD_BCPV_PENALTY_CZK_KWH = 60.0
|
||
# Výboj baterie při sell<0 jen těsně před extrémně záporným buy (round-trip arbitráž).
|
||
EXTREME_BUY_DUMP_PREWINDOW_SLOTS = 12
|
||
NEG_SELL_BAT_DUMP_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||
NEG_BUY_CHARGE_SHORTFALL_PENALTY_CZK_KWH = 100.0
|
||
PRE_NEG_CHARGE_PENALTY_CZK_KWH = 400.0
|
||
PRE_NEG_BATT_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||
PRE_NEG_BATT_EXPORT_MIN_SELL_CZK_KWH = 1.0
|
||
PLANNER_BUILD_TAG = "2026-06-06-home01-strict-late-replan-v5"
|
||
SOLVER_RELAX_STEPS: tuple[str, ...] = (
|
||
"strict",
|
||
"relaxed_expensive_import",
|
||
"relaxed_neg_buy_charge",
|
||
"relaxed_neg_prep_hold_only",
|
||
"relaxed_neg_prep_window",
|
||
"neg_sell_phases_fallback",
|
||
"relaxed_pos_sell_ge_block",
|
||
"relaxed_solver_masks",
|
||
)
|
||
# Ranní slabá FVE: neaplikovat pv_store ge_pv=0 (jinak curtail při sell < večerní peak).
|
||
DAWN_LOW_PV_NO_CURTAIL_W = 1500
|
||
# Mimo evening_push: preferovat bd pro dům místo gi, když buy >> acq (účinná cena importu).
|
||
NIGHT_SELF_CONSUME_IMPORT_SURCHARGE_CZK_KWH = 4.0
|
||
# Po t_detach v prep: necpát PV do bat (měkké; tvrdý hold přes soc_target z rampy).
|
||
NEG_SELL_POST_DETACH_BCPV_DISCOURAGE_CZK_KWH = 250.0
|
||
# Večer před neg dnem: výboj do sítě (měkký shortfall na ge_bat).
|
||
NEG_EVENING_PREP_DISCHARGE_SHORTFALL_PENALTY_CZK_KWH = 120.0
|
||
# Kotva: SoC na konci večera D−1 a těsně před 1. sell<0 ráno D ≤ reserve_soc.
|
||
NEG_EVENING_RESERVE_SOC_MAX_SLACK_WH = 400.0
|
||
NEG_EVENING_RESERVE_SOC_SLACK_PENALTY_CZK_PER_WH = 55.0
|
||
# Terminal SoC shadow price: effective_factor = base × (1 − w_neg); w_neg roste s blízkostí a záporností buy<0.
|
||
TERMINAL_NEG_BUY_WEIGHT_HORIZON_SLOTS = int(36 / INTERVAL_H)
|
||
TERMINAL_NEG_BUY_MAGNITUDE_REF_CZK = 1.0
|
||
TERMINAL_NEG_BUY_MAGNITUDE_FLOOR = 0.25
|
||
TERMINAL_NEG_BUY_WEIGHT_CAP = 0.95
|
||
# Před prvním sell<0: export FVE jen pokud predikce v sell<0 okně pokryje dobítí na prep cíl.
|
||
PRE_NEG_PV_EXPORT_FORECAST_MARGIN = 1.15
|
||
PRE_NEG_PV_EXPORT_MIN_NEEDED_WH = 2500.0
|
||
PRE_NEG_PV_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 55.0
|
||
PRE_NEG_PV_BCPV_DISCOURAGE_CZK_KWH = 90.0
|
||
POS_SELL_PRE_NEG_SOC_SHORTFALL_PENALTY_CZK_PER_WH = 0.30
|
||
PRE_NEG_BUY_SOC_CEILING_SLACK_PENALTY_CZK_PER_WH = 0.25
|
||
PRE_NEG_BUY_EMPTY_EXPORT_SHORTFALL_PENALTY_CZK_KWH = 80.0
|
||
EVENING_PEAK_SELL_EPS_CZK_KWH = 0.05
|
||
# Rolling replan: držet evening_push_ts při malé změně peak sell / SoC.
|
||
EVENING_PUSH_HYSTERESIS_SELL_PEAK_DELTA_CZK_KWH = 0.5
|
||
EVENING_PUSH_HYSTERESIS_SOC_PCT = 5.0
|
||
# Noční výprodej baterie: večer (≥17h) + ráno do východu FVE (0–5h Prague), jedna špička přes půlnoc.
|
||
NIGHT_EXPORT_EVENING_START_HOUR = 17
|
||
NIGHT_EXPORT_MORNING_END_HOUR = 5
|
||
NIGHT_EXPORT_PV_SUNRISE_SURPLUS_W = 500.0
|
||
# Převáží terminal SoC shadow price při krátkém večerním horizontu (home-01).
|
||
EVENING_PUSH_Z_EXPORT_BONUS_CZK = 2500.0
|
||
# buy<0: preferovat import před PV A→bat (měkké; tvrdé bc_pv=0 láme bilanci s polem B).
|
||
PRE_NEG_BUY_PV_CHARGE_PENALTY_CZK_KWH = 250.0
|
||
CORRECTION_WINDOW_H = 1 # hodina zpět pro výpočet korekčního faktoru
|
||
CORRECTION_MIN_CLAMP = 0.5 # spodní limit korekčního faktoru
|
||
CORRECTION_MAX_CLAMP = 1.5 # horní limit korekčního faktoru
|
||
# Útlum korekce: čím dál od aktuálního času, tím méně korigujeme forecast
|
||
CORRECTION_DECAY_SLOTS = 16 # po 16 slotech (4h) klesne korekce na 0
|
||
# Dynamická ekonomická podlaha (MILP w_arb): lookahead FVE energie v dalších slotech
|
||
ARB_LOOKAHEAD_SLOTS = 32 # 8 h při INTERVAL_H=0.25
|
||
ARB_FLOOR_E_REF_FRAC = 0.5 # má scale Wh = tato frakce usable_capacity (0..1)
|
||
|
||
_PRAGUE_TZ = ZoneInfo("Europe/Prague")
|
||
|
||
|
||
|
||
# --- Konstanty původně roztroušené mezi funkcemi planning_engine.py (Fáze 1) ---
|
||
MORNING_PRENEG_START_HOUR = 5
|
||
MORNING_PRENEG_END_HOUR = 11
|