implementace dynamickeho bodu T (kde se rodpojuje PV A)
This commit is contained in:
@@ -3703,7 +3703,7 @@ class PlannerArbitrageImprovementsTests(unittest.TestCase):
|
||||
|
||||
|
||||
class NegSellSocPhaseTests(unittest.TestCase):
|
||||
"""Fázované SoC v okně sell<0 (v32): prep 80 %, tail rampa, vent B s prahem."""
|
||||
"""Fázované SoC v okně sell<0 (v35): rampa z PV B, tail, vent B s prahem."""
|
||||
|
||||
@staticmethod
|
||||
def _phase_battery(**kw: float) -> SimpleNamespace:
|
||||
@@ -3750,11 +3750,26 @@ class NegSellSocPhaseTests(unittest.TestCase):
|
||||
def test_day_phases_tail_last_four(self) -> None:
|
||||
slots = self._neg_sell_slots(10)
|
||||
bat = self._phase_battery(tail_slots=4)
|
||||
phases, targets, _w = _neg_sell_day_phases(slots, bat)
|
||||
phases, targets, _w, meta = _neg_sell_day_phases(slots, bat)
|
||||
self.assertEqual(phases[5], "prep")
|
||||
self.assertEqual(phases[9], "tail")
|
||||
self.assertEqual(phases.count("tail"), 4)
|
||||
self.assertAlmostEqual(float(targets[9] or 0), bat.soc_max_wh, delta=50.0)
|
||||
self.assertTrue(meta.get("neg_sell_b_ramp_v35"))
|
||||
prep_targets = [float(targets[t] or 0) for t in range(6) if phases[t] == "prep"]
|
||||
self.assertGreater(len(prep_targets), 1)
|
||||
for a, b in zip(prep_targets, prep_targets[1:]):
|
||||
self.assertGreaterEqual(b, a - 1.0)
|
||||
|
||||
def test_b_ramp_t_detach_and_surplus_meta(self) -> None:
|
||||
slots = self._neg_sell_slots(12, pv_b=6000)
|
||||
bat = self._phase_battery(tail_slots=4)
|
||||
_ph, _tg, _w, meta = _neg_sell_day_phases(slots, bat)
|
||||
self.assertIsNotNone(meta.get("t_detach_idx"))
|
||||
self.assertGreaterEqual(int(meta["t_detach_idx"]), 0)
|
||||
self.assertLess(int(meta["t_detach_idx"]), 8)
|
||||
self.assertGreater(float(meta.get("e_surplus_after_t_wh") or 0), 0.0)
|
||||
self.assertIn("post_detach_prep_ts", meta)
|
||||
|
||||
def test_prep_reaches_soc_by_mid_window(self) -> None:
|
||||
slots = self._neg_sell_slots(12)
|
||||
@@ -3782,6 +3797,8 @@ class NegSellSocPhaseTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(snap.get("planner_build_tag"), PLANNER_BUILD_TAG)
|
||||
self.assertTrue(snap.get("inputs", {}).get("neg_sell_phases_enabled"))
|
||||
self.assertTrue(snap.get("inputs", {}).get("neg_sell_b_ramp_v35"))
|
||||
self.assertIsNotNone(snap.get("inputs", {}).get("t_detach_idx"))
|
||||
# Nabíjení z FVE v sell<0: SoC roste, tail má vyšší cíl než začátek okna.
|
||||
self.assertGreater(results[-1].battery_soc_target, results[0].battery_soc_target)
|
||||
self.assertGreaterEqual(results[-1].battery_soc_target, 75.0)
|
||||
@@ -3875,7 +3892,7 @@ class NegSellSocPhaseTests(unittest.TestCase):
|
||||
|
||||
|
||||
class PreNegPvExportForecastTests(unittest.TestCase):
|
||||
"""v33: export FVE před sell<0 jen pokud forecast v sell<0 okně pokryje prep SoC."""
|
||||
"""v33/v35: export FVE před sell<0 jen pokud forecast B v sell<0 okně pokryje soc_need z rampy."""
|
||||
|
||||
@staticmethod
|
||||
def _slots_morning_then_neg(n: int = 22, *, neg_pv_scale: float = 1.0) -> list[PlanningSlot]:
|
||||
@@ -3885,7 +3902,8 @@ class PreNegPvExportForecastTests(unittest.TestCase):
|
||||
sell = -0.25 if i >= 6 else (2.8 if i < 4 else 1.2)
|
||||
if i >= 6:
|
||||
pv_a = (8000 + (i - 6) * 500) * neg_pv_scale
|
||||
pv_b = 6000.0 * neg_pv_scale
|
||||
# v35 cushion: usable jen z B — dostatečný B pro rampu v test_cushion_ok
|
||||
pv_b = 9500.0 * neg_pv_scale
|
||||
else:
|
||||
pv_a = 1500 + i * 400
|
||||
pv_b = 1500.0
|
||||
|
||||
Reference in New Issue
Block a user