Skip to content

Commit 77947e0

Browse files
authored
Merge pull request #190 from JohanAlvedal/codex/implement-advanced-braking-logic
Apply braking relative to active/next price block and expose metadata
2 parents b60bc88 + 78cc98e commit 77947e0

File tree

2 files changed

+53
-20
lines changed

2 files changed

+53
-20
lines changed

custom_components/pumpsteer/price_brake.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22
import math
33
from statistics import median
4-
from typing import List, Optional
4+
from typing import List, Optional, Tuple, Union
55

66

77
@dataclass
@@ -190,6 +190,35 @@ def filter_upcoming_blocks(
190190
return [block for block in blocks if block.end_offset_minutes > now_offset_minutes]
191191

192192

193+
def resolve_active_and_next_blocks(
194+
blocks: Optional[Union[PriceBlock, List[PriceBlock]]],
195+
now_offset_minutes: float,
196+
) -> Tuple[Optional[PriceBlock], Optional[PriceBlock], List[PriceBlock]]:
197+
"""Resolve active and next blocks from a block or list of blocks."""
198+
if blocks is None:
199+
return None, None, []
200+
block_list = blocks if isinstance(blocks, list) else [blocks]
201+
active_block = next(
202+
(
203+
block
204+
for block in block_list
205+
if block.start_offset_minutes <= now_offset_minutes < block.end_offset_minutes
206+
),
207+
None,
208+
)
209+
upcoming_blocks = [
210+
block
211+
for block in block_list
212+
if block.start_offset_minutes > now_offset_minutes
213+
]
214+
next_block = (
215+
min(upcoming_blocks, key=lambda block: block.start_offset_minutes)
216+
if upcoming_blocks
217+
else None
218+
)
219+
return active_block, next_block, block_list
220+
221+
193222
def compute_brake_level(
194223
block: Optional[PriceBlock],
195224
amplitude: float,
@@ -252,23 +281,10 @@ def compute_price_brake(
252281
)
253282
upcoming_blocks = filter_upcoming_blocks(blocks, now_offset_minutes)
254283
selected_blocks = select_price_blocks(upcoming_blocks, max_blocks=2)
255-
active_block = None
256-
for block in selected_blocks:
257-
if block.start_offset_minutes <= now_offset_minutes < block.end_offset_minutes:
258-
active_block = block
259-
break
260-
upcoming_block = None
261-
if active_block is None:
262-
upcoming_candidates = [
263-
block
264-
for block in selected_blocks
265-
if block.start_offset_minutes > now_offset_minutes
266-
]
267-
if upcoming_candidates:
268-
upcoming_block = min(
269-
upcoming_candidates, key=lambda block: block.start_offset_minutes
270-
)
271-
primary_block = active_block or upcoming_block
284+
active_block, next_block, _ = resolve_active_and_next_blocks(
285+
upcoming_blocks, now_offset_minutes
286+
)
287+
primary_block = active_block or next_block
272288
area = primary_block.area if primary_block else 0.0
273289
safe_area_scale = area_scale if area_scale > 0 else 1.0
274290
amplitude = clamp(area / safe_area_scale, 0.0, 1.0) if primary_block else 0.0
@@ -279,6 +295,11 @@ def compute_price_brake(
279295
post_release_minutes,
280296
now_offset_minutes,
281297
)
298+
blocks_for_status = list(selected_blocks)
299+
for candidate in (active_block, next_block):
300+
if candidate and candidate not in blocks_for_status:
301+
blocks_for_status.append(candidate)
302+
blocks_for_status.sort(key=lambda block: block.start_index)
282303

283304
return {
284305
"baseline": baseline,
@@ -287,5 +308,7 @@ def compute_price_brake(
287308
"amplitude": amplitude,
288309
"brake_level": brake_level,
289310
"block": primary_block,
290-
"blocks": selected_blocks,
311+
"blocks": blocks_for_status,
312+
"active_block": active_block,
313+
"next_block": next_block,
291314
}

custom_components/pumpsteer/sensor/sensor.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def _compute_controls(
342342
area_scale=PRICE_BLOCK_AREA_SCALE,
343343
now_offset_minutes=0.0,
344344
)
345+
next_block = price_brake.get("next_block")
345346

346347
(
347348
block_start,
@@ -370,7 +371,16 @@ def _compute_controls(
370371
desired_brake_level, base_min + 0.20 * scale
371372
)
372373
brake_blocked_reason = "expensive_now"
373-
if desired_brake_level <= 0.0 and not in_price_block and not expensive_now:
374+
pre_window_active = False
375+
if next_block is not None:
376+
pre_start = next_block.start_offset_minutes - PRICE_BRAKE_PRE_MINUTES
377+
pre_window_active = pre_start <= 0 < next_block.start_offset_minutes
378+
if (
379+
desired_brake_level <= 0.0
380+
and not in_price_block
381+
and not expensive_now
382+
and not pre_window_active
383+
):
374384
brake_blocked_reason = "no_price_block"
375385
if too_cold_to_brake:
376386
desired_brake_level = 0.0

0 commit comments

Comments
 (0)