Skip to content
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

Creation of Reporter class #641

Merged
merged 45 commits into from May 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
32e300c
Creating Reporter and PandasReporter classes with their corresponding…
victorgarcia98 Apr 14, 2023
ce17568
Added Tibber Reporter.
victorgarcia98 Apr 17, 2023
848e39a
- Fixing wong DA Price value.
victorgarcia98 Apr 20, 2023
d9867fc
Updating VAT units.
victorgarcia98 Apr 20, 2023
1c9de43
- Attatching report to sensor
victorgarcia98 Apr 21, 2023
f61825b
Fixing wrong arguments to search_beliefs method.
victorgarcia98 Apr 21, 2023
b01f5c3
FIxing wrong type conversion logic.
victorgarcia98 Apr 21, 2023
487233c
Small reporter fixes (#647)
Flix6x Apr 24, 2023
f34f5e1
Add superclass to Reporter that will be common to all three data gene…
victorgarcia98 Apr 24, 2023
1f8457e
Merge remote-tracking branch 'origin/626-add-reporter-class' into 626…
victorgarcia98 Apr 24, 2023
dd21c99
Add start, end, resolution, beliefs_after and beliefs_before to the `…
victorgarcia98 Apr 24, 2023
58d405f
Add FLEXMEASURES_DEFAULT_DATASOURCE config to be the feault datasourc…
victorgarcia98 Apr 24, 2023
bebc320
Fixing wrong input type.
victorgarcia98 Apr 27, 2023
0dfe5f4
Rename DataGenerator class to DataGeneratorMixin
victorgarcia98 Apr 27, 2023
7830e33
Reduce logging level from warning to debug.
victorgarcia98 Apr 27, 2023
e378ac6
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 Apr 27, 2023
5166264
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 Apr 27, 2023
6ab84df
Register Reporter to the app context.
victorgarcia98 Apr 27, 2023
79fe12f
Allowing to use BeliefsDataFrame specific method in the schema.
victorgarcia98 Apr 27, 2023
a64550b
Merge remote-tracking branch 'origin/626-add-reporter-class' into 626…
victorgarcia98 Apr 27, 2023
4c86878
Fixed wrong method. TODO: test with a plugin.
victorgarcia98 Apr 28, 2023
5ca52eb
Using module name instead of the module object.
victorgarcia98 Apr 28, 2023
12267d3
use belief_time instead of beliefs_before and beliefs_after (#652)
Flix6x May 1, 2023
723ed02
Merge remote-tracking branch 'origin/626-add-reporter-class' into 626…
victorgarcia98 May 1, 2023
5a6590e
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 May 1, 2023
cc47742
Fixing example.
victorgarcia98 May 1, 2023
cc11809
Fixing grammar.
victorgarcia98 May 1, 2023
c50df17
Require at least 1 input sensor for the tb_query_config.
victorgarcia98 May 1, 2023
cc8aa22
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 May 2, 2023
c36b904
Bug fix: compute function was overriding the variables to the default…
victorgarcia98 May 2, 2023
3ad4113
Changing end to get 24h and fix assert condition to detect NaN.
victorgarcia98 May 2, 2023
aba68ea
Adding belief time variable to schema.
victorgarcia98 May 2, 2023
cc545e1
Avoid deserializing multiple times.
victorgarcia98 May 2, 2023
9d5dc57
Add scope="module" to avoid recreating objects in DB.
victorgarcia98 May 2, 2023
801e358
fix: remove time paramters (start, end, ...) from the Reporter class …
victorgarcia98 May 5, 2023
99c47b4
style: type hints improvements
victorgarcia98 May 8, 2023
83a776f
style: rename tb_query_config for beliefs_search_config_schema
victorgarcia98 May 8, 2023
97f3b15
style: remove comment
victorgarcia98 May 8, 2023
aa8a266
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 May 8, 2023
912aa4e
docs: add entry to changelog
victorgarcia98 May 8, 2023
9d8c737
style: clarifying attribute
victorgarcia98 May 8, 2023
1383b4a
style: fix docstring
victorgarcia98 May 8, 2023
08020f6
refactor: rename beliefs_search_config_schema beliefs_search_configs
victorgarcia98 May 8, 2023
9fbb699
Merge branch 'main' into 626-add-reporter-class
victorgarcia98 May 8, 2023
96c03b3
style: typo
victorgarcia98 May 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 22 additions & 50 deletions flexmeasures/data/models/reporting/__init__.py
Expand Up @@ -25,10 +25,6 @@ class Reporter(DataGeneratorMixin):
reporter_config_raw: Optional[dict] = None
schema = ReporterConfigSchema
data: Dict[str, Union[tb.BeliefsDataFrame, pd.DataFrame]] = None
start: datetime = None
end: datetime = None
input_resolution: timedelta = None
belief_time: datetime = None

def __init__(
self, sensor: Sensor, reporter_config_raw: Optional[dict] = None
Expand All @@ -48,7 +44,13 @@ def __init__(

self.reporter_config_raw = reporter_config_raw

def fetch_data(self):
def fetch_data(
self,
start: datetime,
end: datetime,
input_resolution: timedelta = None,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
belief_time: datetime = None,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
):
"""
Fetches the time_beliefs from the database
"""
Expand All @@ -57,10 +59,10 @@ def fetch_data(self):
for tb_query in self.tb_query_config:
_tb_query = tb_query.copy()
# using start / end instead of event_starts_after/event_ends_before when not defined
event_starts_after = _tb_query.pop("event_starts_after", self.start)
event_ends_before = _tb_query.pop("event_ends_before", self.end)
resolution = _tb_query.pop("resolution", self.input_resolution)
belief_time = _tb_query.pop("belief_time", self.belief_time)
event_starts_after = _tb_query.pop("event_starts_after", start)
event_ends_before = _tb_query.pop("event_ends_before", end)
resolution = _tb_query.pop("resolution", input_resolution)
belief_time = _tb_query.pop("belief_time", belief_time)

sensor: Sensor = _tb_query.pop("sensor", None)
alias: str = _tb_query.pop("alias", None)
Expand Down Expand Up @@ -89,9 +91,8 @@ def update_attribute(self, attribute, default):

def compute(
self,
*args,
start: datetime = None,
end: datetime = None,
start: datetime,
end: datetime,
input_resolution: timedelta = None,
belief_time: datetime = None,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
**kwargs,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -108,17 +109,11 @@ def compute(
if self.reporter_config is None:
self.deserialize_config()

# if provided, update the class attributes
self.update_attribute("start", start)
self.update_attribute("end", end)
self.update_attribute("input_resolution", input_resolution)
self.update_attribute("belief_time", belief_time)

# fetch data
self.fetch_data()
self.fetch_data(start, end, input_resolution, belief_time)

# Result
result = self._compute()
result = self._compute(start, end, input_resolution, belief_time)

# checking that the event_resolution of the output BeliefDataFrame is equal to the one of the output sensor
assert self.sensor.event_resolution == result.event_resolution
Expand All @@ -128,7 +123,13 @@ def compute(

return result

def _compute(self) -> tb.BeliefsDataFrame:
def _compute(
self,
start: datetime,
end: datetime,
input_resolution: timedelta = None,
belief_time: datetime = None,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
) -> tb.BeliefsDataFrame:
"""
Overwrite with the actual computation of your report.

Expand All @@ -137,31 +138,6 @@ def _compute(self) -> tb.BeliefsDataFrame:
raise NotImplementedError()

def deserialize_config(self):
"""
Check all configurations we have, throwing either ValidationErrors or ValueErrors.
Other code can decide if/how to handle those.
"""
self.deserialize_reporter_config()
self.deserialize_timing_config()

def deserialize_timing_config(self):
"""
Check if the timing of the report is valid.

Raises ValueErrors.
"""

for tb_query in self.tb_query_config:
start = tb_query.get("event_starts_after", self.start)
end = tb_query.get("event_ends_before ", self.end)

if (
start is not None and end is not None
): # not testing when start or end are missing
if end < start:
raise ValueError(f"Start {start} cannot be after end {end}.")

def deserialize_reporter_config(self):
"""
Validate the report config against a Marshmallow Schema.
Ideas:
Expand All @@ -178,7 +154,3 @@ def deserialize_reporter_config(self):
self.tb_query_config = self.reporter_config.get(
"tb_query_config"
) # extracting TimeBelief query configuration parameters
self.start = self.reporter_config.get("start")
self.end = self.reporter_config.get("end")
self.input_resolution = self.reporter_config.get("input_resolution")
self.belief_time = self.reporter_config.get("belief_time")
15 changes: 11 additions & 4 deletions flexmeasures/data/models/reporting/pandas_reporter.py
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import Any
from datetime import datetime, timedelta

from flask import current_app
import timely_beliefs as tb
Expand All @@ -20,15 +21,21 @@ class PandasReporter(Reporter):
transformations: list[dict[str, Any]] = None
final_df_output: str = None

def deserialize_reporter_config(self):
# call super class deserialize_reporter_config
super().deserialize_reporter_config()
def deserialize_config(self):
# call super class deserialize_config
super().deserialize_config()

# extract PandasReporter specific fields
self.transformations = self.reporter_config.get("transformations")
self.final_df_output = self.reporter_config.get("final_df_output")

def _compute(self) -> tb.BeliefsDataFrame:
def _compute(
self,
start: datetime,
end: datetime,
input_resolution: timedelta = None,
belief_time: datetime = None,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
) -> tb.BeliefsDataFrame:
"""
This method applies the transformations and outputs the dataframe
defined in `final_df_output` field of the report_config.
Expand Down
18 changes: 10 additions & 8 deletions flexmeasures/data/models/reporting/tests/test_pandas_reporter.py
@@ -1,5 +1,6 @@
import pytest
from datetime import datetime, timedelta

from pytz import utc

import pandas as pd
Expand Down Expand Up @@ -86,8 +87,6 @@ def test_reporter(setup_dummy_data):
s1, s2, reporter_sensor = setup_dummy_data

reporter_config_raw = dict(
start="2023-04-10T00:00:00 00:00",
end="2023-04-10T10:00:00 00:00",
tb_query_config=[dict(sensor=s1.id), dict(sensor=s2.id)],
transformations=[
dict(
Expand Down Expand Up @@ -120,7 +119,9 @@ def test_reporter(setup_dummy_data):

reporter = PandasReporter(reporter_sensor, reporter_config_raw=reporter_config_raw)

report1 = reporter.compute()
start = datetime(2023, 4, 10, tzinfo=utc)
end = datetime(2023, 4, 10, 10, tzinfo=utc)
report1 = reporter.compute(start, end)

assert len(report1) == 5
assert str(report1.index[0]) == "2023-04-10 00:00:00+00:00"
Expand All @@ -129,12 +130,10 @@ def test_reporter(setup_dummy_data):
) # check that the output sensor is effectively assigned.

# check that calling compute with different parameters changes the result
report3 = reporter.compute(start=datetime(2023, 4, 10, 3, tzinfo=utc))
report3 = reporter.compute(start=datetime(2023, 4, 10, 3, tzinfo=utc), end=end)
assert len(report3) == 4
assert str(report3.index[0]) == "2023-04-10 02:00:00+00:00"

# TODO: resample with BeliefDataFrame specific method (resample_event)


def test_reporter_repeated(setup_dummy_data):
"""check that calling compute doesn't change the result"""
Expand Down Expand Up @@ -184,7 +183,10 @@ def test_reporter_repeated(setup_dummy_data):
)

reporter = PandasReporter(reporter_sensor, reporter_config_raw=reporter_config_raw)
start = datetime(2023, 4, 10, tzinfo=utc)
end = datetime(2023, 4, 10, 10, tzinfo=utc)

report1 = reporter.compute(start=start, end=end)
report2 = reporter.compute(start=start, end=end)

report1 = reporter.compute()
report2 = reporter.compute()
pd.testing.assert_series_equal(report1, report2)
Expand Up @@ -84,7 +84,6 @@ def __init__(self, sensor) -> None:

# create the PandasReporter reporter config
reporter_config = dict(
input_resolution="PT1H",
tb_query_config=[
dict(sensor=EnergyTax.id, alias="energy_tax_df"),
dict(sensor=VAT.id),
Expand Down Expand Up @@ -251,6 +250,7 @@ def test_tibber_reporter(tibber_test_data):
result = tibber_reporter.compute(
start=datetime(2023, 4, 13, tzinfo=utc),
end=datetime(2023, 4, 14, tzinfo=utc),
input_resolution=timedelta(hours=1),
)

# check that we got a result for 24 hours
Expand Down
7 changes: 0 additions & 7 deletions flexmeasures/data/schemas/reporting/__init__.py
Expand Up @@ -41,17 +41,10 @@ class ReporterConfigSchema(Schema):
"""
This schema is used to validate Reporter class configurations (reporter_config).
Inherit from this to extend this schema with your own parameters.

If the fields event_starts_after or event_ends_before are not present in `tb_query_config`
they will look up in the fields `start` and `end`
"""

tb_query_config = fields.List(
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
fields.Nested(TimeBeliefQueryConfigSchema()),
required=True,
validator=validate.Length(min=1),
)
start = AwareDateTimeField()
end = AwareDateTimeField()
input_resolution = DurationField()
belief_time = AwareDateTimeField()
10 changes: 6 additions & 4 deletions flexmeasures/data/schemas/tests/test_reporting.py
Expand Up @@ -36,7 +36,7 @@ def setup_dummy_sensors(db, app):


@pytest.mark.parametrize(
"report_config, is_valid",
"reporter_config, is_valid",
[
(
{ # this checks that the final_df_output dataframe is actually generated at some point of the processing pipeline
Expand Down Expand Up @@ -125,12 +125,14 @@ def setup_dummy_sensors(db, app):
),
],
)
def test_pandas_reporter(report_config, is_valid, db, app, setup_dummy_sensors):
def test_pandas_reporter_schema(
reporter_config, is_valid, db, app, setup_dummy_sensors
):

schema = PandasReporterConfigSchema()

if is_valid:
schema.load(report_config)
schema.load(reporter_config)
else:
with pytest.raises(ValidationError):
schema.load(report_config)
schema.load(reporter_config)