Skip to content

Commit 1205e74

Browse files
committed
Add a Percentage quantity type
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 1a6d3fd commit 1205e74

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

src/frequenz/sdk/timeseries/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from ._base_types import UNIX_EPOCH, Sample, Sample3Phase
3939
from ._moving_window import MovingWindow
4040
from ._periodic_feature_extractor import PeriodicFeatureExtractor
41-
from ._quantities import Current, Energy, Power, Quantity, QuantityT, Voltage
41+
from ._quantities import Current, Energy, Percentage, Power, Quantity, Voltage
4242
from ._resampling import ResamplerConfig
4343

4444
__all__ = [
@@ -56,4 +56,5 @@
5656
"Energy",
5757
"Power",
5858
"Voltage",
59+
"Percentage",
5960
]

src/frequenz/sdk/timeseries/_quantities.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from datetime import timedelta
1010
from typing import Any, NoReturn, Self, TypeVar, overload
1111

12-
QuantityT = TypeVar("QuantityT", "Quantity", "Power", "Current", "Voltage", "Energy")
12+
QuantityT = TypeVar(
13+
"QuantityT", "Quantity", "Power", "Current", "Voltage", "Energy", "Percentage"
14+
)
1315

1416

1517
class Quantity:
@@ -704,3 +706,55 @@ def __truediv__(self, other: timedelta | Power) -> Power | timedelta:
704706
raise TypeError(
705707
f"unsupported operand type(s) for /: '{type(self)}' and '{type(other)}'"
706708
)
709+
710+
711+
class Percentage(
712+
Quantity,
713+
metaclass=_NoDefaultConstructible,
714+
exponent_unit_map={0: "%"},
715+
):
716+
"""A percentage quantity."""
717+
718+
@classmethod
719+
def from_percent(cls, percent: float) -> Self:
720+
"""Initialize a new percentage quantity from a percent value.
721+
722+
Args:
723+
percent: The percent value, normally in the 0.0-100.0 range.
724+
725+
Returns:
726+
A new percentage quantity.
727+
"""
728+
percentage = cls.__new__(cls)
729+
percentage._base_value = percent
730+
return percentage
731+
732+
@classmethod
733+
def from_fraction(cls, fraction: float) -> Self:
734+
"""Initialize a new percentage quantity from a fraction.
735+
736+
Args:
737+
fraction: The fraction, normally in the 0.0-1.0 range.
738+
739+
Returns:
740+
A new percentage quantity.
741+
"""
742+
percentage = cls.__new__(cls)
743+
percentage._base_value = fraction * 100
744+
return percentage
745+
746+
def as_percent(self) -> float:
747+
"""Return this quantity as a percentage.
748+
749+
Returns:
750+
This quantity as a percentage.
751+
"""
752+
return self._base_value
753+
754+
def as_fraction(self) -> float:
755+
"""Return this quantity as a fraction.
756+
757+
Returns:
758+
This quantity as a fraction.
759+
"""
760+
return self._base_value / 100

tests/timeseries/test_quantities.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from frequenz.sdk.timeseries._quantities import (
1111
Current,
1212
Energy,
13+
Percentage,
1314
Power,
1415
Quantity,
1516
Voltage,
@@ -257,3 +258,13 @@ def test_quantity_compositions() -> None:
257258
assert energy / power == timedelta(hours=6.2)
258259
assert energy / timedelta(hours=6.2) == power
259260
assert energy == power * timedelta(hours=6.2)
261+
262+
263+
def test_percentage() -> None:
264+
"""Test the percentage class."""
265+
pct = Percentage.from_fraction(0.204)
266+
assert f"{pct}" == "20.4 %"
267+
pct = Percentage.from_percent(20.4)
268+
assert f"{pct}" == "20.4 %"
269+
assert pct.as_percent() == 20.4
270+
assert pct.as_fraction() == 0.204

0 commit comments

Comments
 (0)