-
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
Add helper function to calculate energy metric #93
Conversation
|
@llucax: This is a very first idea on how to include a helper function to the base client in reporting. Would you please add your input. |
pyproject.toml
Outdated
| "grpcio-tools >= 1.54.2, < 2", | ||
| "protobuf >= 4.25.3, < 6", | ||
| "frequenz-client-base[grpcio] >= 0.5.0, < 0.6.0", | ||
| "pandas == 2.2.2" |
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.
Do we really want to make an API client depend on pandas? For me this sounds like it should be put in a higher level.
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.
No, not at all. It was just a first shot as I am unsure of the data structure in which we want to supply values. I am happy to move away from pandas. The best option may also be connected to data structure that we will use when exposing the states @cwasicki?
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.
Agree, we shouldn't use pandas if not urgently required.
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.
OK, so in terms of code structure, I would make the helper function a free function instead of a class method. And probably would take an Iterator or Sequence instead of a list if that's an option.
|
@flora-hofmann-frequenz just for my understanding, what happens if there is a gap in data for the power? |
|
I also think that the customer would rather expect the total amount of energy that was consumed or delivered as output, rather than the energy for every timestamp. Something like a "Zählerstand" or at least the difference between the "Zählerstände" |
@noah-kreutzer-frequenz can you please make an example for this? Isn't the |
So rather than a Timeseries the looking like this
I think something like this is what the customer would expect
or even only the the last value |
|
and potentially the possoiblity of seperating between consumed energy and delivered energy, so they don't cancel out :)) |
pyproject.toml
Outdated
| "grpcio-tools >= 1.54.2, < 2", | ||
| "protobuf >= 4.25.3, < 6", | ||
| "frequenz-client-base[grpcio] >= 0.5.0, < 0.6.0", | ||
| "pandas == 2.2.2" |
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.
Agree, we shouldn't use pandas if not urgently required.
| # Convert the timedelta to hours | ||
| hours = time_interval.total_seconds() / 3600.0 | ||
|
|
||
| energy_wh_series = ac_active_power_series * hours |
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 don't think this is what is wanted. Usually the energy metric is integrated over time, so the power would need to be converted to energy and then cumulatively added.
| metric_samples: list[MetricSample], time_interval: timedelta | ||
| ) -> pd.Series: | ||
| """ | ||
| Calculate the energy metric with AC_ACTIVE_POWER values and time interval. |
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 don't remember whether this should have been from power or the actual energy metrics. For latter, the only thing that needs to be done is to remove the resets. That could be achieved by taking the diff, removing the negatives (reset) and cumulatively sum them up again.
|
|
||
| @staticmethod | ||
| def calculate_energy_metric( | ||
| metric_samples: list[MetricSample], time_interval: timedelta |
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 think this should be a wrapper around list_microgrid_components_data with the same arguments but the metric, which is given.
| print(f"RPC failed: {e}") | ||
| return | ||
|
|
||
| @staticmethod |
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.
Good that this is staticmethod, I see this as part of a tooling on top of the client and not the client itself. For now that would be overkill though.
| return | ||
|
|
||
| @staticmethod | ||
| def calculate_energy_metric( |
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.
Needs a clearer name, maybe cumulative_energy, total_energy, integrated_power` or so, depending on which approach we go for.
2a8ef4f to
024624d
Compare
Signed-off-by: Flora <[email protected]>
024624d to
1a1d7c1
Compare
Signed-off-by: flora-hofmann-frequenz <[email protected]>
|
@cwasicki & @noah-kreutzer-frequenz please take a look at the energy metric logic again. I was trying to implement the following: Alongside the previous comments and the option to use power or energy metrics (in case energy is not available). |
| 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 comment
The 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. < end_time.
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.
Why do we actually need to extract the values again and not just loop over the metric_samples (see example for power).
| 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 comment
The 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:
consumption = sum(
m1.value * (m2.timestamp-m1.timestamp).total_seconds()
for m in zip(metric_samples, metric_samples[1:]
if m1.value > 0) / 3600.0
|
|
||
|
|
||
| # pylint: disable=too-many-arguments | ||
| async def cumulative_energy( |
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.
|
|
||
| # pylint: disable=too-many-arguments | ||
| async def cumulative_energy( | ||
| client: ReportingApiClient, |
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.
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 comment
The 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.
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 async def cumulative_energy(self: ReportingApiClient,...) the same, it is just called self by convention. You can actually also call a method like this ReportingApiClient.cumulative_energy(client, ...).
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.
@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.
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 frequenz-dispatch-python provides a higher level interface, which makes a lot of assumptions about how the API should be used, so it is much less flexible, but much easier to use for 99% of the cases (scientifically proven :P).
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.
Okay, I think it will be best to set up frequenz-reporting-python and move the energy metric there :-)
|
This PR will be closed as it moved to this PR in a new repo. |
We have a client request to read out energy metrics. Since the inverter / meter readings are rather unreliable, we want to add a helper function to calculate the
energy metric.