-
Couldn't load subscription status.
- Fork 13
Add helper function to calculate energy metric #93
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 |
|---|---|---|
|
|
@@ -40,6 +40,11 @@ | |
| or a pandas DataFrame. | ||
| """ | ||
|
|
||
| CumulativeEnergy = namedtuple( | ||
| "CumulativeEnergy", ["start_time", "end_time", "consumption", "production"] | ||
| ) | ||
| """Type for cumulative energy consumption and production over a specified time.""" | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ComponentsDataBatch: | ||
|
|
@@ -252,3 +257,82 @@ def dt2ts(dt: datetime) -> PBTimestamp: | |
| except grpcaio.AioRpcError as e: | ||
| print(f"RPC failed: {e}") | ||
| return | ||
|
|
||
|
|
||
| # pylint: disable=too-many-arguments | ||
| async def cumulative_energy( | ||
| client: ReportingApiClient, | ||
|
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. If it's not part of the client, it needs an URL and key. I guess part of the client makes things simpler now. @llucax Any thoughts on this? This helper function IMO goes beyond a thin client and could be seen as additional tooling on top of the client. We do think it is overkill to start that now though and wait for further cases. 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.
No, it just needs a client, which you should have already instantiated. It really exactly the same, is just a syntax issue. The method would be I like utilities being separated because it keeps the client interface clear, otherwise is really hard to draw a line between what should be part of the client what shouldn't.
I think we should adopt the https://github.com/frequenz-floss/frequenz-dispatch-python approach if there is a room for a higher level interface. At least IMHO that worked out well. In dispatch we basically have the https://github.com/frequenz-floss/frequenz-client-dispatch-python as a thin wrapper over the API and then 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. Okay, I think it will be best to set up |
||
| 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. | ||
| """ | ||
| 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, | ||
| ) | ||
| ] | ||
|
|
||
| values = [ | ||
| sample.value | ||
| for sample in metric_samples | ||
| if start_time <= sample.timestamp <= end_time | ||
|
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. Shouldn't happen anyway, but typically exclusive timestamps for the end time are used, i.e. 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. Why do we actually need to extract the values again and not just loop over the |
||
| ] | ||
|
|
||
| if values: | ||
| if use_active_power: | ||
| # Convert power to energy if using AC_ACTIVE_POWER | ||
| time_interval_hours = ( | ||
| resolution or (end_time - start_time).total_seconds() | ||
|
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. The second part looks off to me since each power needs to be integrated over it's time period to energy, so would only work for given resolution. You could just use: |
||
| ) / 3600.0 | ||
| consumption = sum( | ||
| max(e2 - e1, 0) * time_interval_hours | ||
| for e1, e2 in zip(values, values[1:]) | ||
| ) | ||
| production = sum( | ||
| min(e2 - e1, 0) * time_interval_hours | ||
| for e1, e2 in zip(values, values[1:]) | ||
| ) | ||
| else: | ||
| # Directly use energy values if using AC_ACTIVE_ENERGY | ||
| consumption = sum( | ||
| e2 - e1 for e1, e2 in zip(values, values[1:]) if e2 - e1 > 0 | ||
| ) | ||
| production = sum( | ||
| e2 - e1 for e1, e2 in zip(values, values[1:]) if e2 - e1 < 0 | ||
| ) | ||
| 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this should be part of the client for now, otherwise it could be a separate module.