prepsani s opusem dle planu
This commit is contained in:
@@ -64,7 +64,7 @@ NEG_SELL_PV_B_VENT_PENALTY_CZK_KWH = 4.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
|
||||
PLANNER_BUILD_TAG = "2026-05-26-neg-sell-bat-dump-extreme-buy-v11"
|
||||
PLANNER_BUILD_TAG = "2026-05-27-self-consistent-grid-mask-v12"
|
||||
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
|
||||
@@ -357,6 +357,12 @@ class PlanningSlot:
|
||||
#: Vážená nákupní / opportunity cena zásoby před prvním exportním oknem (SQL odhad z masek).
|
||||
charge_acquisition_buy_czk_kwh: float | None = None
|
||||
charge_acquisition_cutoff_at: datetime | None = None
|
||||
min_buy_before_cutoff_czk_kwh: float | None = None
|
||||
pv_charge_wh_ahead: float | None = None
|
||||
neg_buy_wh_ahead: float | None = None
|
||||
grid_charge_suppressed_reason: str | None = None
|
||||
#: Pomocny atribut pro green_bonus v planning_interval (Kc/slot); lite default 0.
|
||||
green_bonus_czk_per_slot: float = 0.0
|
||||
|
||||
|
||||
# Lookahead pro relax spodní meze SoC: až 36 h od indexu slotu (pevné OTE ceny v horizontu).
|
||||
@@ -510,6 +516,10 @@ class DispatchResult:
|
||||
effective_buy_price: float
|
||||
effective_sell_price: float
|
||||
is_predicted_price: bool # shodné s PlanningSlot (chybí OTE v efektivní ceně → fn_get_predicted_price)
|
||||
cashflow_czk: float
|
||||
battery_arbitrage_czk: float
|
||||
penalty_czk: float
|
||||
green_bonus_czk: float
|
||||
|
||||
|
||||
# ============================================================
|
||||
@@ -997,6 +1007,7 @@ def solve_dispatch_two_pass(
|
||||
snap1["inputs"]["acquisition_pass2_czk_kwh"] = round(acq2, 6)
|
||||
snap1["inputs"]["two_pass_enabled"] = True
|
||||
snap1["inputs"]["two_pass_converged"] = True
|
||||
snap1["inputs"]["two_pass_skipped"] = False
|
||||
return results1, ms1, snap1
|
||||
|
||||
slots2 = _slots_with_charge_acquisition(slots, acq2)
|
||||
@@ -1019,6 +1030,7 @@ def solve_dispatch_two_pass(
|
||||
snap2["inputs"]["acquisition_pass2_czk_kwh"] = round(acq2, 6)
|
||||
snap2["inputs"]["two_pass_enabled"] = True
|
||||
snap2["inputs"]["two_pass_converged"] = False
|
||||
snap2["inputs"]["two_pass_skipped"] = False
|
||||
snap2["inputs"]["solver_duration_ms_pass1"] = ms1
|
||||
return results2, ms1 + ms2, snap2
|
||||
|
||||
@@ -2128,10 +2140,59 @@ def solve_dispatch(
|
||||
if z_gen_cutoff is not None:
|
||||
deye_gen_cutoff = bool(round(float(pulp.value(z_gen_cutoff[t]) or 0)))
|
||||
|
||||
cost = (
|
||||
cashflow_czk_t = (
|
||||
pulp.value(gi[t]) * slots[t].buy_price * INTERVAL_H / 1000
|
||||
- pulp.value(ge[t]) * slots[t].sell_price * INTERVAL_H / 1000
|
||||
)
|
||||
ge_bat_value = float(pulp.value(ge_bat[t]) or 0)
|
||||
battery_arbitrage_czk_t = (
|
||||
ge_bat_value
|
||||
* (float(slots[t].sell_price) - float(charge_acquisition_czk_kwh))
|
||||
* INTERVAL_H
|
||||
/ 1000.0
|
||||
)
|
||||
penalty_terms_t = 0.0
|
||||
for _tt, _sf, _cap in peak_export_shortfall:
|
||||
if _tt == t:
|
||||
penalty_terms_t += (
|
||||
float(pulp.value(_sf) or 0.0)
|
||||
* PEAK_EXPORT_SHORTFALL_PENALTY_CZK_KWH
|
||||
* INTERVAL_H
|
||||
/ 1000.0
|
||||
)
|
||||
for _tt, _sf, _cap in pv_charge_shortfall:
|
||||
if _tt == t:
|
||||
penalty_terms_t += (
|
||||
float(pulp.value(_sf) or 0.0)
|
||||
* PV_CHARGE_SHORTFALL_PENALTY_CZK_KWH
|
||||
* INTERVAL_H
|
||||
/ 1000.0
|
||||
)
|
||||
for _tt, _sf, _cap in neg_sell_bat_dump_shortfall:
|
||||
if _tt == t:
|
||||
penalty_terms_t += (
|
||||
float(pulp.value(_sf) or 0.0)
|
||||
* NEG_SELL_BAT_DUMP_SHORTFALL_PENALTY_CZK_KWH
|
||||
* INTERVAL_H
|
||||
/ 1000.0
|
||||
)
|
||||
for _tt, _us in neg_sell_soc_underfill:
|
||||
if _tt == t:
|
||||
penalty_terms_t += (
|
||||
float(pulp.value(_us) or 0.0)
|
||||
* NEG_SELL_SOC_UNDERFILL_PENALTY_CZK_PER_WH
|
||||
)
|
||||
sv_t = safety_vars[t]
|
||||
if sv_t is not None:
|
||||
penalty_terms_t += float(pulp.value(sv_t) or 0.0) * safety_pen_czk_per_wh[t]
|
||||
for _tt, _cv, _prev in commit_lp:
|
||||
if _tt == t:
|
||||
penalty_terms_t += float(pulp.value(_cv) or 0.0) * INTERVAL_H / 1000.0 * commit_pen
|
||||
penalty_terms_t += float(pulp.value(ca[t]) or 0.0) * CURTAILMENT_PENALTY
|
||||
green_bonus_czk_t = float(
|
||||
getattr(slots[t], "green_bonus_czk_per_slot", 0.0) or 0.0
|
||||
)
|
||||
cost = cashflow_czk_t
|
||||
|
||||
results.append(DispatchResult(
|
||||
interval_start = slots[t].interval_start,
|
||||
@@ -2155,6 +2216,10 @@ def solve_dispatch(
|
||||
effective_buy_price = slots[t].buy_price,
|
||||
effective_sell_price = slots[t].sell_price,
|
||||
is_predicted_price = bool(slots[t].is_predicted_price),
|
||||
cashflow_czk = round(cashflow_czk_t, 4),
|
||||
battery_arbitrage_czk = round(battery_arbitrage_czk_t, 4),
|
||||
penalty_czk = round(penalty_terms_t, 4),
|
||||
green_bonus_czk = round(green_bonus_czk_t, 4),
|
||||
))
|
||||
|
||||
sell_rank = sorted(range(T), key=lambda i: float(slots[i].sell_price), reverse=True)[: min(3, T)]
|
||||
@@ -2230,6 +2295,18 @@ def solve_dispatch(
|
||||
"safety_deficit_wh": sdv,
|
||||
"commitment_shortfall_w": cshort,
|
||||
"commitment_penalty_czk_kwh": float(commit_pen) if cshort is not None else None,
|
||||
"acquisition_used_czk_kwh": float(charge_acquisition_czk_kwh),
|
||||
"grid_charge_suppressed_reason": getattr(
|
||||
st, "grid_charge_suppressed_reason", None
|
||||
),
|
||||
"pv_charge_wh_ahead": float(
|
||||
getattr(st, "pv_charge_wh_ahead", 0.0) or 0.0
|
||||
),
|
||||
"min_buy_before_cutoff_czk_kwh": (
|
||||
float(st.min_buy_before_cutoff_czk_kwh)
|
||||
if getattr(st, "min_buy_before_cutoff_czk_kwh", None) is not None
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
night0 = slots[0]
|
||||
@@ -2844,7 +2921,9 @@ async def _load_slots(
|
||||
night_baseload_target_wh, night_baseload_buffer_wh, safety_soc_target_wh,
|
||||
future_avoided_buy_czk_kwh, future_sell_opportunity_czk_kwh,
|
||||
is_daytime_pv_surplus_slot,
|
||||
charge_acquisition_buy_czk_kwh, charge_acquisition_cutoff_at
|
||||
charge_acquisition_buy_czk_kwh, charge_acquisition_cutoff_at,
|
||||
min_buy_before_cutoff_czk_kwh, pv_charge_wh_ahead, neg_buy_wh_ahead,
|
||||
grid_charge_suppressed_reason
|
||||
from ems.fn_load_planning_slots_full(
|
||||
$1::int, $2::timestamptz, $3::timestamptz, $4::numeric
|
||||
)
|
||||
@@ -2882,6 +2961,12 @@ async def _load_slots(
|
||||
d, "charge_acquisition_buy_czk_kwh"
|
||||
),
|
||||
charge_acquisition_cutoff_at=d.get("charge_acquisition_cutoff_at"),
|
||||
min_buy_before_cutoff_czk_kwh=_slot_float_nullable(
|
||||
d, "min_buy_before_cutoff_czk_kwh"
|
||||
),
|
||||
pv_charge_wh_ahead=_slot_float_nullable(d, "pv_charge_wh_ahead"),
|
||||
neg_buy_wh_ahead=_slot_float_nullable(d, "neg_buy_wh_ahead"),
|
||||
grid_charge_suppressed_reason=d.get("grid_charge_suppressed_reason"),
|
||||
)
|
||||
)
|
||||
if not out:
|
||||
@@ -2960,6 +3045,10 @@ async def _save_planning_run(
|
||||
"heat_pump_setpoint_w": r.heat_pump_setpoint_w,
|
||||
"pv_a_curtailed_w": r.pv_a_curtailed_w,
|
||||
"expected_cost_czk": float(r.expected_cost_czk),
|
||||
"cashflow_czk": float(r.cashflow_czk),
|
||||
"battery_arbitrage_czk": float(r.battery_arbitrage_czk),
|
||||
"penalty_czk": float(r.penalty_czk),
|
||||
"green_bonus_czk": float(r.green_bonus_czk),
|
||||
"effective_buy_price": float(r.effective_buy_price),
|
||||
"effective_sell_price": float(r.effective_sell_price),
|
||||
"is_predicted_price": r.is_predicted_price,
|
||||
|
||||
Reference in New Issue
Block a user