-
Couldn't load subscription status.
- Fork 2
Add helper function to calculate energy metric #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # License: MIT | ||
| # Copyright © 2024 Frequenz Energy-as-a-Service GmbH | ||
|
|
||
| """A highlevel interface for the reporting API.""" | ||
|
|
||
| from collections import namedtuple | ||
| from datetime import datetime | ||
|
|
||
| from frequenz.client.common.metric import Metric | ||
| from frequenz.client.reporting import ReportingApiClient | ||
|
|
||
| CumulativeEnergy = namedtuple( | ||
| "CumulativeEnergy", ["start_time", "end_time", "consumption", "production"] | ||
| ) | ||
| """Type for cumulative energy consumption and production over a specified time.""" | ||
|
|
||
|
|
||
| # pylint: disable-next=too-many-arguments | ||
| async def cumulative_energy( | ||
| client: ReportingApiClient, | ||
| microgrid_id: int, | ||
| component_id: int, | ||
| start_time: datetime, | ||
| end_time: datetime, | ||
| use_active_power: bool, | ||
| resolution: int | None = None, | ||
| ) -> CumulativeEnergy: | ||
| """ | ||
| Calculate the cumulative energy consumption and production over a specified time range. | ||
|
|
||
| Args: | ||
| client: The client used to fetch the metric samples from the Reporting API. | ||
| microgrid_id: The ID of the microgrid. | ||
| component_id: The ID of the component within the microgrid. | ||
| start_time: The start date and time for the period. | ||
| end_time: The end date and time for the period. | ||
| use_active_power: If True, use the 'AC_ACTIVE_POWER' metric. | ||
| If False, use the 'AC_ACTIVE_ENERGY' metric. | ||
| resolution: The resampling resolution for the data, represented in seconds. | ||
| If None, no resampling is applied. | ||
| Returns: | ||
| EnergyMetric: A named tuple with start_time, end_time, consumption, and production | ||
| in Wh. Consumption has a positive sign, production has a negative sign. | ||
| """ | ||
| metric = Metric.AC_ACTIVE_POWER if use_active_power else Metric.AC_ACTIVE_ENERGY | ||
|
|
||
| metric_samples = [ | ||
| sample | ||
| async for sample in client.list_microgrid_components_data( | ||
| microgrid_components=[(microgrid_id, [component_id])], | ||
| metrics=metric, | ||
| start_dt=start_time, | ||
| end_dt=end_time, | ||
| resolution=resolution, | ||
| ) | ||
| ] | ||
|
|
||
| if metric_samples: | ||
| if use_active_power: | ||
| # Convert power to energy if using AC_ACTIVE_POWER | ||
| consumption = ( | ||
| sum( | ||
| m1.value * (m2.timestamp - m1.timestamp).total_seconds() | ||
| for m1, m2 in zip(metric_samples, metric_samples[1:]) | ||
| if m1.value > 0 | ||
| ) | ||
| / 3600.0 | ||
| ) # Convert seconds to hours | ||
|
|
||
| last_value_consumption = ( | ||
| metric_samples[-1].value | ||
| * (end_time - metric_samples[-1].timestamp).total_seconds() | ||
| if metric_samples[-1].value > 0 | ||
| else 0 | ||
| ) / 3600.0 | ||
|
|
||
| consumption += last_value_consumption | ||
|
|
||
| production = ( | ||
| sum( | ||
| m1.value * (m2.timestamp - m1.timestamp).total_seconds() | ||
| for m1, m2 in zip(metric_samples, metric_samples[1:]) | ||
| if m1.value < 0 | ||
| ) | ||
| / 3600.0 | ||
| ) | ||
|
|
||
| last_value_production = ( | ||
| metric_samples[-1].value | ||
| * (end_time - metric_samples[-1].timestamp).total_seconds() | ||
| if metric_samples[-1].value < 0 | ||
| else 0 | ||
| ) / 3600.0 | ||
|
|
||
| production += last_value_production | ||
|
|
||
| else: | ||
| # Directly use energy values if using AC_ACTIVE_ENERGY | ||
| consumption = sum( | ||
| m2.value - m1.value | ||
| for m1, m2 in zip(metric_samples, metric_samples[1:]) | ||
| if m2.value - m1.value > 0 | ||
| ) | ||
| production = sum( | ||
| m2.value - m1.value | ||
| for m1, m2 in zip(metric_samples, metric_samples[1:]) | ||
| if m2.value - m1.value < 0 | ||
| ) | ||
|
|
||
| if len(metric_samples) > 1: | ||
| last_value_diff = metric_samples[-1].value - metric_samples[-2].value | ||
| if last_value_diff > 0: | ||
| consumption += last_value_diff | ||
| elif last_value_diff < 0: | ||
| production += last_value_diff | ||
| else: | ||
| consumption = production = 0.0 | ||
|
|
||
| return CumulativeEnergy( | ||
| start_time=start_time, | ||
| end_time=end_time, | ||
| consumption=consumption, | ||
| production=production, | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be test cases for this function. For that you could mock the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, test cases will be added in a follow up PR. I have added Issue #8. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize we are missing on the last value of the list. But we don't know the sampling period between the last sample in the list and the one that would follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just discussed: For the last value we know the sampling period from the end time, i.e.
metric_samples[-1].value * (end_time - metric_samples[-1].timestamp)should be added to the sum.