Skip to content

Commit 678a279

Browse files
committed
Add a metric aggregation wrapper
These wrappers are also not present in 0.15, but are added so users can already start writing code that supports aggregates to ease the migration to 0.17. Because of this, no protobuf parsing code is added yet. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 157f2bc commit 678a279

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Definition to work with metric sample values."""
5+
6+
import enum
7+
from collections.abc import Sequence
8+
from dataclasses import dataclass
9+
10+
11+
@enum.unique
12+
class AggregationMethod(enum.Enum):
13+
"""The type of the aggregated value."""
14+
15+
AVG = "avg"
16+
"""The average value of the metric."""
17+
18+
MIN = "min"
19+
"""The minimum value of the metric."""
20+
21+
MAX = "max"
22+
"""The maximum value of the metric."""
23+
24+
25+
@dataclass(frozen=True, kw_only=True)
26+
class AggregatedMetricValue:
27+
"""Encapsulates derived statistical summaries of a single metric.
28+
29+
The message allows for the reporting of statistical summaries — minimum,
30+
maximum, and average values - as well as the complete list of individual
31+
samples if available.
32+
33+
This message represents derived metrics and contains fields for statistical
34+
summaries—minimum, maximum, and average values. Individual measurements are
35+
are optional, accommodating scenarios where only subsets of this information
36+
are available.
37+
"""
38+
39+
avg: float
40+
"""The derived average value of the metric."""
41+
42+
min: float | None
43+
"""The minimum measured value of the metric."""
44+
45+
max: float | None
46+
"""The maximum measured value of the metric."""
47+
48+
raw_values: Sequence[float]
49+
"""All the raw individual values (it might be empty if not provided by the component)."""
50+
51+
def __str__(self) -> str:
52+
"""Return the short string representation of this instance."""
53+
extra: list[str] = []
54+
if self.min is not None:
55+
extra.append(f"min:{self.min}")
56+
if self.max is not None:
57+
extra.append(f"max:{self.max}")
58+
if len(self.raw_values) > 0:
59+
extra.append(f"num_raw:{len(self.raw_values)}")
60+
extra_str = f"<{' '.join(extra)}>" if extra else ""
61+
return f"avg:{self.avg}{extra_str}"

tests/test_metrics.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the Sample class and related classes."""
5+
6+
from frequenz.client.microgrid.metrics import AggregatedMetricValue, AggregationMethod
7+
8+
9+
def test_aggregation_method_values() -> None:
10+
"""Test that AggregationMethod enum has the expected values."""
11+
assert AggregationMethod.AVG.value == "avg"
12+
assert AggregationMethod.MIN.value == "min"
13+
assert AggregationMethod.MAX.value == "max"
14+
15+
16+
def test_aggregated_metric_value() -> None:
17+
"""Test AggregatedMetricValue creation and string representation."""
18+
# Test with full data
19+
value = AggregatedMetricValue(
20+
avg=5.0,
21+
min=1.0,
22+
max=10.0,
23+
raw_values=[1.0, 5.0, 10.0],
24+
)
25+
assert value.avg == 5.0
26+
assert value.min == 1.0
27+
assert value.max == 10.0
28+
assert list(value.raw_values) == [1.0, 5.0, 10.0]
29+
assert str(value) == "avg:5.0<min:1.0 max:10.0 num_raw:3>"
30+
31+
# Test with minimal data (only avg required)
32+
value = AggregatedMetricValue(
33+
avg=5.0,
34+
min=None,
35+
max=None,
36+
raw_values=[],
37+
)
38+
assert value.avg == 5.0
39+
assert value.min is None
40+
assert value.max is None
41+
assert not value.raw_values
42+
assert str(value) == "avg:5.0"

0 commit comments

Comments
 (0)