zruseni fixnich konstant
Some checks failed
CI and deploy / migration-check (push) Failing after 20s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-25 09:41:06 +02:00
parent 37a525cb4f
commit f1a4dbd7e7
4 changed files with 153 additions and 26 deletions

View File

@@ -68,7 +68,7 @@ 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-05-28-pre-neg-batt-discharge-v23"
PLANNER_BUILD_TAG = "2026-05-28-evening-push-dynamic-budget-v24"
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
@@ -935,37 +935,71 @@ def _evening_peak_export_indices(
return out
def _evening_push_discharge_budget_wh(
*,
current_soc_wh: float,
min_soc_wh: float,
soc_max_wh: float,
discharge_slot_buffer: float,
) -> float:
"""
Rozpočet Wh pro tvrdý večerní push — stejný princip jako R__063 (discharge_slot_buffer).
Tvrdý push nesmí překročit energii nad min_soc na začátku horizontu (jinak Infeasible).
"""
exportable_full_wh = max(0.0, float(soc_max_wh) - float(min_soc_wh))
available_wh = max(0.0, float(current_soc_wh) - float(min_soc_wh))
buf = float(discharge_slot_buffer)
if buf <= 0.0:
return available_wh
return min(available_wh, exportable_full_wh * buf)
def _evening_battery_export_push_indices(
slots: list[PlanningSlot],
*,
profitable_export_ts: set[int],
degrad_czk_kwh: float,
current_soc_wh: float,
min_soc_wh: float,
soc_max_wh: float,
per_slot_discharge_wh: float,
discharge_slot_buffer: float,
evening_start_hour: int = 17,
max_slots_per_day: int = 3,
) -> list[int]:
"""
Tvrdý push ge_bat jen u několika nejlepších večerních slotů/den (profitable ∩ peak).
Jinak součet ge_bat × z_export přes celý peak pásmo může překrit dostupné SoC → Infeasible.
Tvrdý push ge_bat u večerních peak slotů (profitable ∩ pásmo ≥17:00 degrad).
Počet slotů = kolik jich unese rozpet Wh (ne pevné top-3 / ≥2 sloty).
"""
if per_slot_discharge_wh <= 0.0:
return []
peak_ts = _evening_peak_export_indices(
slots,
degrad_czk_kwh=degrad_czk_kwh,
evening_start_hour=evening_start_hour,
)
by_day: dict = {}
for t in peak_ts:
if t not in profitable_export_ts:
continue
d = _prague_calendar_date(slots[t])
by_day.setdefault(d, []).append(t)
candidates = [t for t in peak_ts if t in profitable_export_ts]
if not candidates:
return []
push_budget_wh = _evening_push_discharge_budget_wh(
current_soc_wh=current_soc_wh,
min_soc_wh=min_soc_wh,
soc_max_wh=soc_max_wh,
discharge_slot_buffer=discharge_slot_buffer,
)
if push_budget_wh < per_slot_discharge_wh * 0.5:
return []
ranked = sorted(
candidates,
key=lambda i: (float(slots[i].sell_price), -i),
reverse=True,
)
out: list[int] = []
for d in sorted(by_day.keys()):
ranked = sorted(
by_day[d],
key=lambda i: float(slots[i].sell_price),
reverse=True,
)
out.extend(ranked[:max_slots_per_day])
cum_wh = 0.0
for t in ranked:
if cum_wh + per_slot_discharge_wh > push_budget_wh + 1e-6:
break
out.append(t)
cum_wh += per_slot_discharge_wh
return sorted(out)
@@ -1687,6 +1721,12 @@ def solve_dispatch(
* INTERVAL_H,
1000.0,
)
per_slot_discharge_wh = max(
float(battery.max_discharge_power_w)
* float(battery.discharge_efficiency)
* INTERVAL_H,
0.0,
)
if om == "AUTO":
profitable_export_ts = profitable_export_ts_pre
export_push_w = _battery_export_cap_w(battery, grid)
@@ -1695,17 +1735,21 @@ def solve_dispatch(
prob += ge_bat[t_peak] >= export_push_w * z_export[t_peak]
for t_pnd in pre_neg_buy_discharge_ts:
prob += ge_bat[t_pnd] >= export_push_w * z_export[t_pnd]
discharge_buf = float(getattr(battery, "discharge_slot_buffer", 0) or 0)
evening_push_ts = _evening_battery_export_push_indices(
slots,
profitable_export_ts=profitable_export_ts,
degrad_czk_kwh=float(degradation_cost_effective),
current_soc_wh=float(current_soc_wh),
min_soc_wh=float(min_soc_wh),
soc_max_wh=float(battery.soc_max_wh),
per_slot_discharge_wh=per_slot_discharge_wh,
discharge_slot_buffer=discharge_buf,
)
# Push jen při reálném večerním okně (≥2 sloty); 1-slot regresní testy bez tvrdého push.
if len(evening_push_ts) >= 2:
for t_peak in evening_push_ts:
if t_peak not in discharge_export_slots:
continue
prob += ge_bat[t_peak] >= export_push_w * z_export[t_peak]
for t_peak in evening_push_ts:
if t_peak not in discharge_export_slots:
continue
prob += ge_bat[t_peak] >= export_push_w * z_export[t_peak]
# Ostatní profitable sloty: shortfall penalizace (ne tvrdý push na celý horizont).
if t_anchor is not None and soc_anchor_slack is not None:
target_floor_wh = float(planner_floor_effective_wh)