Skip to content

Commit 163d993

Browse files
committed
Stream only usable capacity from the battery pool
With this, users won't have to deal with the soc bounds themselves for calculating usable capacity. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent a8a28cf commit 163d993

File tree

3 files changed

+58
-46
lines changed

3 files changed

+58
-46
lines changed

src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,7 @@ def calculate(
206206
High level metric calculated from the given metrics.
207207
Return None if there are no component metrics.
208208
"""
209-
result = CapacityMetrics(
210-
timestamp=_MIN_TIMESTAMP, total_capacity=0, bound=Bound(lower=0, upper=0)
211-
)
209+
result = CapacityMetrics(timestamp=_MIN_TIMESTAMP, total_capacity=0)
212210

213211
for battery_id in working_batteries:
214212
if battery_id not in metrics_data:
@@ -223,11 +221,9 @@ def calculate(
223221
# All metrics are related so if any is missing then we skip the component.
224222
if capacity is None or soc_lower_bound is None or soc_upper_bound is None:
225223
continue
226-
224+
usable_capacity = capacity * (soc_upper_bound - soc_lower_bound) / 100
227225
result.timestamp = max(result.timestamp, metrics.timestamp)
228-
result.total_capacity += capacity
229-
result.bound.upper += capacity * soc_upper_bound
230-
result.bound.lower += capacity * soc_lower_bound
226+
result.total_capacity += usable_capacity
231227

232228
return None if result.timestamp == _MIN_TIMESTAMP else result
233229

src/frequenz/sdk/timeseries/battery_pool/_result_types.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,6 @@ class CapacityMetrics:
3535
total_capacity = sum(battery.capacity for battery in working_batteries)
3636
```
3737
"""
38-
bound: Bound
39-
"""Capacity bounds.
40-
41-
Bounds are calculated with the formula:
42-
```python
43-
working_batteries: Set[BatteryData] # working batteries from the battery
44-
bound.lower = sum(
45-
battery.capacity * battery.soc_lower_bound for battery in working_batteries)
46-
47-
bound.upper = sum(
48-
battery.capacity * battery.soc_upper_bound for battery in working_batteries)
49-
```
50-
"""
5138

5239

5340
@dataclass

tests/timeseries/_battery_pool/test_battery_pool.py

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
495495
component_id=battery_id,
496496
timestamp=datetime.now(tz=timezone.utc),
497497
capacity=50,
498-
soc_lower_bound=20,
499-
soc_upper_bound=80,
498+
soc_lower_bound=25,
499+
soc_upper_bound=75,
500500
),
501501
sampling_rate=0.05,
502502
)
@@ -510,63 +510,92 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
510510
now = datetime.now(tz=timezone.utc)
511511
expected = CapacityMetrics(
512512
timestamp=now,
513-
total_capacity=100,
514-
bound=Bound(lower=2000, upper=8000),
513+
total_capacity=50.0, # 50% of 50 kWh + 50% of 50 kWh = 25 + 25 = 50 kWh
515514
)
516515
compare_messages(msg, expected, WAIT_FOR_COMPONENT_DATA_SEC + 0.2)
517516

518517
batteries_in_pool = list(battery_pool.battery_ids)
519518
scenarios: list[Scenario[CapacityMetrics]] = [
520519
Scenario(
521520
batteries_in_pool[0],
522-
{"capacity": 90},
523-
CapacityMetrics(now, 140, Bound(2800, 11200)),
521+
{"capacity": 90.0},
522+
CapacityMetrics(
523+
now,
524+
70.0, # 50% of 90 kWh + 50% of 50 kWh = 45 + 25 = 70 kWh
525+
),
524526
),
525527
Scenario(
526528
batteries_in_pool[1],
527-
{"soc_lower_bound": 0, "soc_upper_bound": 90},
528-
CapacityMetrics(now, 140, Bound(1800, 11700)),
529+
{"soc_lower_bound": 0.0, "soc_upper_bound": 90.0},
530+
CapacityMetrics(
531+
now,
532+
90.0, # 50% of 90 kWh + 90% of 50 kWh = 45 + 45 = 90 kWh
533+
),
529534
),
530535
Scenario(
531536
batteries_in_pool[0],
532-
{"capacity": 0, "soc_lower_bound": 0},
533-
CapacityMetrics(now, 50, Bound(0, 4500)),
537+
{"capacity": 0.0, "soc_lower_bound": 0.0},
538+
CapacityMetrics(
539+
now,
540+
45.0, # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
541+
),
534542
),
535543
# Test zero division error
536544
Scenario(
537545
batteries_in_pool[1],
538-
{"capacity": 0},
539-
CapacityMetrics(now, 0, Bound(0, 0)),
546+
{"capacity": 0.0},
547+
CapacityMetrics(
548+
now,
549+
0.0, # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
550+
),
540551
),
541552
Scenario(
542553
batteries_in_pool[1],
543-
{"capacity": 50},
544-
CapacityMetrics(now, 50, Bound(0, 4500)),
554+
{"capacity": 50.0},
555+
CapacityMetrics(
556+
now,
557+
45.0, # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
558+
),
545559
),
546560
Scenario(
547561
batteries_in_pool[1],
548562
{"soc_upper_bound": float("NaN")},
549-
CapacityMetrics(now, 0, Bound(0, 0)),
563+
CapacityMetrics(
564+
now,
565+
0.0, # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
566+
),
550567
),
551568
Scenario(
552569
batteries_in_pool[0],
553-
{"capacity": 30, "soc_lower_bound": 20, "soc_upper_bound": 90},
554-
CapacityMetrics(now, 30, Bound(600, 2700)),
570+
{"capacity": 30.0, "soc_lower_bound": 20.0, "soc_upper_bound": 90.0},
571+
CapacityMetrics(
572+
now,
573+
21.0, # 70% of 30 kWh + 90% of 0 kWh = 21 + 0 = 21 kWh
574+
),
555575
),
556576
Scenario(
557577
batteries_in_pool[1],
558-
{"capacity": 200, "soc_lower_bound": 20, "soc_upper_bound": 90},
559-
CapacityMetrics(now, 230, Bound(4600, 20700)),
578+
{"capacity": 200.0, "soc_lower_bound": 20.0, "soc_upper_bound": 90.0},
579+
CapacityMetrics(
580+
now,
581+
161.0, # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
582+
),
560583
),
561584
Scenario(
562585
batteries_in_pool[1],
563586
{"capacity": float("NaN")},
564-
CapacityMetrics(now, 30, Bound(600, 2700)),
587+
CapacityMetrics(
588+
now,
589+
21.0, # 70% of 30 kWh + 70% of 0 kWh = 21 + 0 = 21 kWh
590+
),
565591
),
566592
Scenario(
567593
batteries_in_pool[1],
568-
{"capacity": 200},
569-
CapacityMetrics(now, 230, Bound(4600, 20700)),
594+
{"capacity": 200.0},
595+
CapacityMetrics(
596+
now,
597+
161.0, # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
598+
),
570599
),
571600
]
572601

@@ -579,15 +608,15 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
579608
all_batteries=all_batteries,
580609
batteries_in_pool=batteries_in_pool,
581610
waiting_time_sec=waiting_time_sec,
582-
all_pool_result=CapacityMetrics(now, 230, Bound(4600, 20700)),
583-
only_first_battery_result=CapacityMetrics(now, 30, Bound(600, 2700)),
611+
all_pool_result=CapacityMetrics(now, 161.0),
612+
only_first_battery_result=CapacityMetrics(now, 21.0),
584613
)
585614

586615
# One battery stopped sending data.
587616
await streamer.stop_streaming(batteries_in_pool[1])
588617
await asyncio.sleep(MAX_BATTERY_DATA_AGE_SEC + 0.2)
589618
msg = await asyncio.wait_for(capacity_receiver.receive(), timeout=waiting_time_sec)
590-
compare_messages(msg, CapacityMetrics(now, 30, Bound(600, 2700)), 0.2)
619+
compare_messages(msg, CapacityMetrics(now, 21.0), 0.2)
591620

592621
# All batteries stopped sending data.
593622
await streamer.stop_streaming(batteries_in_pool[0])
@@ -599,7 +628,7 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
599628
latest_data = streamer.get_current_component_data(batteries_in_pool[0])
600629
streamer.start_streaming(latest_data, sampling_rate=0.1)
601630
msg = await asyncio.wait_for(capacity_receiver.receive(), timeout=waiting_time_sec)
602-
compare_messages(msg, CapacityMetrics(now, 30, Bound(600, 2700)), 0.2)
631+
compare_messages(msg, CapacityMetrics(now, 21.0), 0.2)
603632

604633

605634
async def run_soc_test(setup_args: SetupArgs) -> None:

0 commit comments

Comments
 (0)