Fáze 1.1: extrakce konstant plánovače do services/planning/constants.py

Čistý přesun 57 konstant (vč. SOLVER_RELAX_STEPS) z planning_engine.py;
engine je importuje zpět (fasáda, beze změny chování). Golden replay 5/5,
unit testy beze změny vůči baseline (4+1 předexistující faily).

Ekonomické penalty/váhy tím získaly jedno místo — kandidáti na DB ve Fázi 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-11 11:09:10 +02:00
parent 484f1f85fc
commit 4ee5cebf2a
3 changed files with 172 additions and 104 deletions

View File

@@ -0,0 +1,113 @@
# 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 D1 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 (05h 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")