Skip to content

Commit 4725b81

Browse files
committed
Use Sample[Energy] to stream BatteryPool.capacity values
Because the capacity bounds are no longer streamed, and only the usable capacity is streamed, a custom type is no longer necessary. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent c892823 commit 4725b81

File tree

3 files changed

+68
-47
lines changed

3 files changed

+68
-47
lines changed

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
from ...microgrid import connection_manager
1414
from ...microgrid.component import ComponentCategory, ComponentMetricId, InverterType
15-
from ...timeseries import Quantity, Sample
15+
from ...timeseries import Energy, Quantity, Sample
1616
from ._component_metrics import ComponentMetricsData
17-
from ._result_types import Bound, CapacityMetrics, PowerMetrics
17+
from ._result_types import Bound, PowerMetrics
1818

1919
_logger = logging.getLogger(__name__)
2020
_MIN_TIMESTAMP = datetime.min.replace(tzinfo=timezone.utc)
@@ -59,7 +59,7 @@ def battery_inverter_mapping(batteries: Iterable[int]) -> dict[int, int]:
5959

6060
# Formula output types class have no common interface
6161
# Print all possible types here.
62-
T = TypeVar("T", Sample[Quantity], CapacityMetrics, PowerMetrics)
62+
T = TypeVar("T", Sample[Quantity], Sample[Energy], PowerMetrics)
6363

6464

6565
class MetricCalculator(ABC, Generic[T]):
@@ -142,7 +142,7 @@ def calculate(
142142
"""
143143

144144

145-
class CapacityCalculator(MetricCalculator[CapacityMetrics]):
145+
class CapacityCalculator(MetricCalculator[Sample[Energy]]):
146146
"""Define how to calculate Capacity metrics."""
147147

148148
def __init__(self, batteries: Set[int]) -> None:
@@ -190,7 +190,7 @@ def calculate(
190190
self,
191191
metrics_data: dict[int, ComponentMetricsData],
192192
working_batteries: set[int],
193-
) -> CapacityMetrics | None:
193+
) -> Sample[Energy] | None:
194194
"""Aggregate the metrics_data and calculate high level metric.
195195
196196
Missing components will be ignored. Formula will be calculated for all
@@ -207,7 +207,8 @@ def calculate(
207207
High level metric calculated from the given metrics.
208208
Return None if there are no component metrics.
209209
"""
210-
result = CapacityMetrics(timestamp=_MIN_TIMESTAMP, total_capacity=0)
210+
timestamp = _MIN_TIMESTAMP
211+
total_capacity = 0.0
211212

212213
for battery_id in working_batteries:
213214
if battery_id not in metrics_data:
@@ -223,10 +224,14 @@ def calculate(
223224
if capacity is None or soc_lower_bound is None or soc_upper_bound is None:
224225
continue
225226
usable_capacity = capacity * (soc_upper_bound - soc_lower_bound) / 100
226-
result.timestamp = max(result.timestamp, metrics.timestamp)
227-
result.total_capacity += usable_capacity
227+
timestamp = max(timestamp, metrics.timestamp)
228+
total_capacity += usable_capacity
228229

229-
return None if result.timestamp == _MIN_TIMESTAMP else result
230+
return (
231+
None
232+
if timestamp == _MIN_TIMESTAMP
233+
else Sample[Energy](timestamp, Energy.from_watt_hours(total_capacity))
234+
)
230235

231236

232237
class SoCCalculator(MetricCalculator[Sample[Quantity]]):

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
FormulaGeneratorConfig,
2828
FormulaType,
2929
)
30-
from .._quantities import Power
30+
from .._quantities import Energy, Power
3131
from ._methods import MetricAggregator, SendOnUpdate
3232
from ._metric_calculator import CapacityCalculator, PowerBoundsCalculator, SoCCalculator
33-
from ._result_types import CapacityMetrics, PowerMetrics
33+
from ._result_types import PowerMetrics
3434

3535

3636
class BatteryPool:
@@ -357,7 +357,7 @@ def soc(self) -> MetricAggregator[Sample[Quantity]]:
357357
return self._active_methods[method_name]
358358

359359
@property
360-
def capacity(self) -> MetricAggregator[CapacityMetrics]:
360+
def capacity(self) -> MetricAggregator[Sample[Energy]]:
361361
"""Get receiver to receive new capacity metrics when they change.
362362
363363
Capacity formulas are described in the receiver return type. None will be send

tests/timeseries/_battery_pool/test_battery_pool.py

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,8 @@
2424
from frequenz.sdk.actor import ResamplerConfig
2525
from frequenz.sdk.actor.power_distributing import BatteryStatus
2626
from frequenz.sdk.microgrid.component import ComponentCategory
27-
from frequenz.sdk.timeseries import Quantity, Sample
28-
from frequenz.sdk.timeseries._quantities import Power
29-
from frequenz.sdk.timeseries.battery_pool import (
30-
BatteryPool,
31-
Bound,
32-
CapacityMetrics,
33-
PowerMetrics,
34-
)
27+
from frequenz.sdk.timeseries import Energy, Power, Quantity, Sample
28+
from frequenz.sdk.timeseries.battery_pool import BatteryPool, Bound, PowerMetrics
3529
from frequenz.sdk.timeseries.battery_pool._metric_calculator import (
3630
battery_inverter_mapping,
3731
)
@@ -508,93 +502,115 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
508502
capacity_receiver.receive(), timeout=WAIT_FOR_COMPONENT_DATA_SEC + 0.2
509503
)
510504
now = datetime.now(tz=timezone.utc)
511-
expected = CapacityMetrics(
505+
expected = Sample[Energy](
512506
timestamp=now,
513-
total_capacity=50.0, # 50% of 50 kWh + 50% of 50 kWh = 25 + 25 = 50 kWh
507+
value=Energy.from_watt_hours(
508+
50.0
509+
), # 50% of 50 kWh + 50% of 50 kWh = 25 + 25 = 50 kWh
514510
)
515511
compare_messages(msg, expected, WAIT_FOR_COMPONENT_DATA_SEC + 0.2)
516512

517513
batteries_in_pool = list(battery_pool.battery_ids)
518-
scenarios: list[Scenario[CapacityMetrics]] = [
514+
scenarios: list[Scenario[Sample[Energy]]] = [
519515
Scenario(
520516
batteries_in_pool[0],
521517
{"capacity": 90.0},
522-
CapacityMetrics(
518+
Sample(
523519
now,
524-
70.0, # 50% of 90 kWh + 50% of 50 kWh = 45 + 25 = 70 kWh
520+
Energy.from_watt_hours(
521+
70.0
522+
), # 50% of 90 kWh + 50% of 50 kWh = 45 + 25 = 70 kWh
525523
),
526524
),
527525
Scenario(
528526
batteries_in_pool[1],
529527
{"soc_lower_bound": 0.0, "soc_upper_bound": 90.0},
530-
CapacityMetrics(
528+
Sample(
531529
now,
532-
90.0, # 50% of 90 kWh + 90% of 50 kWh = 45 + 45 = 90 kWh
530+
Energy.from_watt_hours(
531+
90.0
532+
), # 50% of 90 kWh + 90% of 50 kWh = 45 + 45 = 90 kWh
533533
),
534534
),
535535
Scenario(
536536
batteries_in_pool[0],
537537
{"capacity": 0.0, "soc_lower_bound": 0.0},
538-
CapacityMetrics(
538+
Sample(
539539
now,
540-
45.0, # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
540+
Energy.from_watt_hours(
541+
45.0
542+
), # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
541543
),
542544
),
543545
# Test zero division error
544546
Scenario(
545547
batteries_in_pool[1],
546548
{"capacity": 0.0},
547-
CapacityMetrics(
549+
Sample(
548550
now,
549-
0.0, # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
551+
Energy.from_watt_hours(
552+
0.0
553+
), # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
550554
),
551555
),
552556
Scenario(
553557
batteries_in_pool[1],
554558
{"capacity": 50.0},
555-
CapacityMetrics(
559+
Sample(
556560
now,
557-
45.0, # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
561+
Energy.from_watt_hours(
562+
45.0
563+
), # 75% of 0 kWh + 90% of 50 kWh = 0 + 45 = 45 kWh
558564
),
559565
),
560566
Scenario(
561567
batteries_in_pool[1],
562568
{"soc_upper_bound": float("NaN")},
563-
CapacityMetrics(
569+
Sample(
564570
now,
565-
0.0, # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
571+
Energy.from_watt_hours(
572+
0.0
573+
), # 75% of 0 kWh + 90% of 0 kWh = 0 + 0 = 0 kWh
566574
),
567575
),
568576
Scenario(
569577
batteries_in_pool[0],
570578
{"capacity": 30.0, "soc_lower_bound": 20.0, "soc_upper_bound": 90.0},
571-
CapacityMetrics(
579+
Sample(
572580
now,
573-
21.0, # 70% of 30 kWh + 90% of 0 kWh = 21 + 0 = 21 kWh
581+
Energy.from_watt_hours(
582+
21.0
583+
), # 70% of 30 kWh + 90% of 0 kWh = 21 + 0 = 21 kWh
574584
),
575585
),
576586
Scenario(
577587
batteries_in_pool[1],
578588
{"capacity": 200.0, "soc_lower_bound": 20.0, "soc_upper_bound": 90.0},
579-
CapacityMetrics(
589+
Sample(
580590
now,
581-
161.0, # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
591+
Energy.from_watt_hours(
592+
161.0
593+
), # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
582594
),
583595
),
584596
Scenario(
585597
batteries_in_pool[1],
586598
{"capacity": float("NaN")},
587-
CapacityMetrics(
599+
Sample(
588600
now,
589-
21.0, # 70% of 30 kWh + 70% of 0 kWh = 21 + 0 = 21 kWh
601+
Energy.from_watt_hours(
602+
21.0
603+
), # 70% of 30 kWh + 70% of 0 kWh = 21 + 0 = 21 kWh
590604
),
591605
),
592606
Scenario(
593607
batteries_in_pool[1],
594608
{"capacity": 200.0},
595-
CapacityMetrics(
609+
Sample(
596610
now,
597-
161.0, # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
611+
Energy.from_watt_hours(
612+
161.0
613+
), # 70% of 30 kWh + 70% of 200 kWh = 21 + 140 = 161 kWh
598614
),
599615
),
600616
]
@@ -608,15 +624,15 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
608624
all_batteries=all_batteries,
609625
batteries_in_pool=batteries_in_pool,
610626
waiting_time_sec=waiting_time_sec,
611-
all_pool_result=CapacityMetrics(now, 161.0),
612-
only_first_battery_result=CapacityMetrics(now, 21.0),
627+
all_pool_result=Sample(now, Energy.from_watt_hours(161.0)),
628+
only_first_battery_result=Sample(now, Energy.from_watt_hours(21.0)),
613629
)
614630

615631
# One battery stopped sending data.
616632
await streamer.stop_streaming(batteries_in_pool[1])
617633
await asyncio.sleep(MAX_BATTERY_DATA_AGE_SEC + 0.2)
618634
msg = await asyncio.wait_for(capacity_receiver.receive(), timeout=waiting_time_sec)
619-
compare_messages(msg, CapacityMetrics(now, 21.0), 0.2)
635+
compare_messages(msg, Sample(now, Energy.from_watt_hours(21.0)), 0.2)
620636

621637
# All batteries stopped sending data.
622638
await streamer.stop_streaming(batteries_in_pool[0])
@@ -628,7 +644,7 @@ async def run_capacity_test(setup_args: SetupArgs) -> None:
628644
latest_data = streamer.get_current_component_data(batteries_in_pool[0])
629645
streamer.start_streaming(latest_data, sampling_rate=0.1)
630646
msg = await asyncio.wait_for(capacity_receiver.receive(), timeout=waiting_time_sec)
631-
compare_messages(msg, CapacityMetrics(now, 21.0), 0.2)
647+
compare_messages(msg, Sample(now, Energy.from_watt_hours(21.0)), 0.2)
632648

633649

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

0 commit comments

Comments
 (0)