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

642 add report cli command #659

Merged
merged 100 commits into from May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
100 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
a82615e
Add command flexmeasures add report.
victorgarcia98 May 1, 2023
9d5dc57
Add scope="module" to avoid recreating objects in DB.
victorgarcia98 May 2, 2023
4804bcf
Merge branch '626-add-reporter-class' into 642-add-report-cli-command-2
victorgarcia98 May 2, 2023
8b66c66
Skip tests and fixtures in GitHub.
victorgarcia98 May 2, 2023
9a2adb5
style(cli): state that --last-X can be used instead of start or end.
victorgarcia98 May 4, 2023
2d1a663
style(cli): add three dots after message
victorgarcia98 May 4, 2023
684383b
style(cli): add CLI message to show that the computation is running.
victorgarcia98 May 4, 2023
bb045cd
style(cli): add type hint for reporter and result.
victorgarcia98 May 4, 2023
fe9f3f5
style(cli.utils): renaming function.
victorgarcia98 May 4, 2023
f8b05e2
style(cli.data_add): replace --save-to-database with --dry-run.
victorgarcia98 May 4, 2023
83e894c
feat(cli.data_add): default start to the latest time the reporter sen…
victorgarcia98 May 5, 2023
bc2e63e
fix: setting a right default value for the timezone.
victorgarcia98 May 5, 2023
adb407b
style: using click parameter File instead of Path.
victorgarcia98 May 5, 2023
5733bf0
style: add comments
victorgarcia98 May 5, 2023
801e358
fix: remove time paramters (start, end, ...) from the Reporter class …
victorgarcia98 May 5, 2023
c0ded6d
Merge branch '626-add-reporter-class' into 642-add-report-cli-command-2
victorgarcia98 May 5, 2023
436e494
style: print success at the end.
victorgarcia98 May 8, 2023
16a0aa2
fix: add test for get_timerange_from_flag and fix some bugs
victorgarcia98 May 8, 2023
a6ac38a
style: move fixtures to the file conftest
victorgarcia98 May 8, 2023
124557c
style: add docstring to the fixture setup_dummy_data
victorgarcia98 May 8, 2023
910508d
style: typo
victorgarcia98 May 8, 2023
75507ea
feat: check for the right values
victorgarcia98 May 8, 2023
0545b3b
style: delete redundant test
victorgarcia98 May 8, 2023
243fd01
style: improve docstring
victorgarcia98 May 8, 2023
7c577dc
style: improving docstring.
victorgarcia98 May 8, 2023
e24bc07
style: removing "In"s
victorgarcia98 May 8, 2023
dbc27aa
style: change comments from triple quotes to block style
victorgarcia98 May 8, 2023
b7a03a2
style: add a more informative test check
victorgarcia98 May 8, 2023
6a25e07
style: add comment
victorgarcia98 May 8, 2023
755621e
Merge branch 'main' into 642-add-report-cli-command-2
victorgarcia98 May 8, 2023
c85b781
Small fixes from reviewing the report cli command (#685)
Flix6x May 15, 2023
e45f838
fix: change dry_run default to False
victorgarcia98 May 17, 2023
0281ceb
Merge branch 'main' into 642-add-report-cli-command-2
victorgarcia98 May 17, 2023
9016d11
test: update field name
victorgarcia98 May 17, 2023
907e270
fix: use timezone in get_timerange_from_flag
victorgarcia98 May 17, 2023
e3bbd49
style: typo
victorgarcia98 May 17, 2023
ed1fb1d
style: week -> day
victorgarcia98 May 17, 2023
6bc6d04
refactor: last_week -> last_7_days
victorgarcia98 May 17, 2023
1ff4560
feat: add datasource to report
victorgarcia98 May 17, 2023
b686f7c
feat: set reporter_class default value to ReporterClass
victorgarcia98 May 17, 2023
95d19a7
style: simplify reporter-config file param name
victorgarcia98 May 17, 2023
475fe67
style: clarify help message
victorgarcia98 May 17, 2023
d4d6a13
style: update repoter_config param click
victorgarcia98 May 17, 2023
6f18753
test: update cli parameter names
victorgarcia98 May 17, 2023
aff61eb
feat: fill missing indexes in beliefdataframes with default values
victorgarcia98 May 17, 2023
3cfa921
style: combine two lines into one
victorgarcia98 May 17, 2023
5313241
test: update tests to check with considering that BeliefDataframe is …
victorgarcia98 May 17, 2023
86257f4
feat: add data_source property to shared mixin
victorgarcia98 May 18, 2023
d8bdd1e
fix: potential bug -> multiple data source creation
victorgarcia98 May 18, 2023
650ddd3
fix: issubclass instead of isinstance
victorgarcia98 May 18, 2023
9883b49
docs: clarify test_add_reporter docstring
victorgarcia98 May 18, 2023
129a549
feat: add function to apply an offset chain
victorgarcia98 May 18, 2023
a90105d
feat: add start_offset and end_offset to add_report
victorgarcia98 May 18, 2023
86bb329
feat: add start and end offsets
victorgarcia98 May 18, 2023
1a73c1a
Merge branch 'main' into 642-add-report-cli-command-2
victorgarcia98 May 18, 2023
11778a0
docs: add entry for `flexmeasures add report`
victorgarcia98 May 19, 2023
c412802
feat: create the CLI command `flexmeasures show reporters` (#686)
victorgarcia98 May 19, 2023
f2bd1b7
Merge remote-tracking branch 'origin/642-add-report-cli-command-2' in…
victorgarcia98 May 19, 2023
b0813c4
docs: add entry to changelog
victorgarcia98 May 19, 2023
1d5d7be
Small report cli fixes (#691)
Flix6x May 19, 2023
db0a94c
docs: update PR number in the changelog
victorgarcia98 May 19, 2023
50e2dd3
fix: changing types
victorgarcia98 May 19, 2023
880232d
test: move fixture to conftest
victorgarcia98 May 21, 2023
954f8f0
fix: handle empty result
victorgarcia98 May 21, 2023
459ad9c
style: fix inaccurate message to the user
victorgarcia98 May 21, 2023
6d0f3e0
refactor: remove last-X parameters
victorgarcia98 May 21, 2023
36152c4
Merge branch 'main' into 642-add-report-cli-command-2
victorgarcia98 May 29, 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
244 changes: 244 additions & 0 deletions flexmeasures/cli/data_add.py
Expand Up @@ -2,7 +2,10 @@
from __future__ import annotations

from datetime import datetime, timedelta
from typing import Optional, Type
import json
from pathlib import Path
from io import TextIOBase

from marshmallow import validate
import pandas as pd
Expand All @@ -12,6 +15,7 @@
import click
import getpass
from sqlalchemy.exc import IntegrityError
from sqlalchemy import func
from timely_beliefs.sensors.func_store.knowledge_horizons import x_days_ago_at_y_oclock
import timely_beliefs as tb
import timely_beliefs.utils as tb_utils
Expand Down Expand Up @@ -60,6 +64,10 @@
from flexmeasures.utils import flexmeasures_inflection
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.unit_utils import convert_units, ur
from flexmeasures.data.utils import save_to_db
from flexmeasures.cli.utils import get_timerange_from_flag
from flexmeasures.data.models.reporting import Reporter
from timely_beliefs import BeliefsDataFrame


@click.group("add")
Expand Down Expand Up @@ -1117,6 +1125,242 @@ def add_schedule_for_storage(
click.secho("New schedule is stored.", **MsgStyle.SUCCESS)


@fm_add_data.command("report")
@with_appcontext
@click.option(
"--reporter",
"reporter_class",
required=True,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
type=click.STRING,
help="Reporter class registered in flexmeasures.data.models.reporting or in an available flexmeasures plugin .",
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
)
@click.option(
"--sensor-id",
"sensor",
type=SensorIdField(),
required=True,
help="ID of the report sensor. Needs to exist in advanced.",
)
@click.option(
"--reporter-config-file",
"reporter_config_file",
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
required=True,
type=click.File("r"),
help="Path to the JSON file with the reporter configuration.",
)
@click.option(
"--start",
"start",
type=AwareDateTimeField(format="iso"),
required=False,
help="Report start time. Either `--last-X` flag can be used instead. Follow up with a timezone-aware datetime in ISO 6801 format.",
)
@click.option(
"--end",
"end",
type=AwareDateTimeField(format="iso"),
required=False,
help="Report end time. Either `--last-X` flag can be used instead. Follow up with a timezone-aware datetime in ISO 6801 format.",
)
@click.option(
"--resolution",
"resolution",
type=DurationField(format="iso"),
required=False,
help="Time resolution of the input time series to employ for the calculations. Follow up with a ISO 8601 duration string",
)
@click.option(
"--output-file",
"output_file",
required=False,
type=click.Path(),
help="Path to the output file of the results of the report."
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
"Use the `.csv` suffix to save the results as Comma Separated Values and `.xlsx` to export them as Excel sheets.",
)
@click.option(
"--timezone",
"timezone",
required=False,
default="UTC",
help="timezone as string, e.g. 'UTC' or 'Europe/Amsterdam' (defaults to FLEXMEASURES_TIMEZONE config setting)",
)
@click.option(
"--last-hour",
"last_hour",
is_flag=True,
help="Use the last known 1 hour period for the scope of the report.",
)
@click.option(
"--last-day",
"last_day",
is_flag=True,
help="Use the last known 1 day period for the scope of the report.",
)
@click.option(
"--last-week",
"last_week",
is_flag=True,
help="Use the last known 7 days period for the scope of the report.",
)
@click.option(
"--last-month",
"last_month",
is_flag=True,
help="Use the last known 1 month period for the scope of the report.",
)
@click.option(
"--last-year",
"last_year",
is_flag=True,
help="Use the last known 1 year period for the scope of the report.",
)
@click.option(
"--dry-run",
"dry_run",
is_flag=True,
help="Add this flag to avoid saving the results to the database.",
)
def add_report(
reporter_class: str,
sensor: Sensor,
reporter_config_file: TextIOBase,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
resolution: Optional[timedelta] = None,
output_file: Optional[Path] = None,
dry_run: bool = True,
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
last_hour: bool = False,
last_day: bool = False,
last_week: bool = False,
last_month: bool = False,
last_year: bool = False,
timezone: str = "UTC",
):
"""
Create a new report using the Reporter class and save the results
to the database or export them as csv or excel file.
"""
check_timezone(timezone)

tz = pytz.timezone(zone=timezone)

# check that only 1 flag is provided
last_x_flags = [last_hour, last_day, last_week, last_month, last_year]
last_x_flag_given = last_x_flags.count(True) == 1

if ((start is None) or (end is None)) and not last_x_flag_given:
click.secho(
"Either --start and --end, any of the --last-X flags should be provided."
"Trying to use the the latest datapoint of the report sensor as the start time and "
"the current time as the end...",
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
**MsgStyle.WARN,
)

last_value_datetime = (
db.session.query(func.max(TimedBelief.event_start))
.filter(TimedBelief.sensor_id == sensor.id)
.one_or_none()
)

# If there's data saved to the reporter sensors, we use:
# - The latest date as the start.
# - The current time as the end.
if last_value_datetime is not None:
start = last_value_datetime[0]
end = datetime.now(tz=tz)
else:
click.secho(
f"Could not find any data for the report sensor {sensor}.",
**MsgStyle.ERROR,
)
raise click.Abort()

# if any of the last-X flag is provided
if last_x_flag_given:
start, end = get_timerange_from_flag(
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
last_hour=last_hour,
last_day=last_day,
last_week=last_week,
last_month=last_month,
last_year=last_year,
)

click.echo(f"Report scope:\n\tstart={start}\n\tend={end}")

click.echo(
f"Looking for the Reporter {reporter_class} among all the registered reporters...",
)

# get reporter class
ReporterClass = app.reporters.get(reporter_class)

# check if it exists
if ReporterClass is None:
click.secho(
f"Reporter class `{reporter_class}` not available.",
**MsgStyle.ERROR,
)
raise click.Abort()

click.secho(f"Reporter {reporter_class} found", **MsgStyle.SUCCESS)

reporter_config_raw = json.load(reporter_config_file)

# initialize reporter class with the reporter sensor and reporter config
reporter: Type[Reporter] = ReporterClass(
Flix6x marked this conversation as resolved.
Show resolved Hide resolved
sensor=sensor, reporter_config_raw=reporter_config_raw
)

click.echo("Report computation is running....")

victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
# compute the report
result: Type[BeliefsDataFrame] = reporter.compute(
Flix6x marked this conversation as resolved.
Show resolved Hide resolved
start=start, end=end, input_resolution=resolution
)
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved

click.secho("Report computation done.", **MsgStyle.SUCCESS)

# save the report it's not running in dry mode
if not dry_run:
click.echo("Storing report to the database...")
save_to_db(result)
db.session.commit()
click.secho(
"Success. The report has been saved to the database.",
**MsgStyle.SUCCESS,
)

# if an output file path is provided, save the results
if output_file:
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
suffix = str(output_file).split(".")[-1] if "." in str(output_file) else ""

if suffix == "xlsx": # save to EXCEL
result.to_excel(output_file)
click.secho(
f"Success. The report has been exported as EXCEL to the file `{output_file}`",
**MsgStyle.SUCCESS,
)

elif suffix == "csv": # save to CSV
result.to_csv(output_file)
click.secho(
f"Success. The report has been exported as CSV to the file `{output_file}`",
**MsgStyle.SUCCESS,
)

else: # default output format: CSV.
click.secho(
f"File suffix not provided. Exporting results as CSV to file {output_file}",
**MsgStyle.WARN,
)
reporter.to_csv(output_file)
else:
click.secho(
"Success.",
**MsgStyle.SUCCESS,
)


@fm_add_data.command("toy-account")
@with_appcontext
@click.option(
Expand Down
98 changes: 98 additions & 0 deletions flexmeasures/cli/tests/conftest.py
@@ -0,0 +1,98 @@
import pytest

from datetime import datetime, timedelta
from pytz import utc

from flexmeasures.data.models.data_sources import DataSource
from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType
from flexmeasures.data.models.time_series import Sensor, TimedBelief


@pytest.fixture(scope="module")
@pytest.mark.skip_github
def setup_dummy_data(db, app):
"""
Create an asset with two sensors (1 and 2), and add the same set of 200 beliefs with an hourly resolution to each of them.
Return the two sensors and a result sensor (which has no data).
"""

dummy_asset_type = GenericAssetType(name="DummyGenericAssetType")
report_asset_type = GenericAssetType(name="ReportAssetType")

db.session.add_all([dummy_asset_type, report_asset_type])

dummy_asset = GenericAsset(
name="DummyGenericAsset", generic_asset_type=dummy_asset_type
)

pandas_report = GenericAsset(
name="PandasReport", generic_asset_type=report_asset_type
)

db.session.add_all([dummy_asset, pandas_report])

sensor1 = Sensor(
"sensor 1", generic_asset=dummy_asset, event_resolution=timedelta(hours=1)
)

db.session.add(sensor1)
sensor2 = Sensor(
"sensor 2", generic_asset=dummy_asset, event_resolution=timedelta(hours=1)
)
db.session.add(sensor2)
report_sensor = Sensor(
"report sensor",
generic_asset=pandas_report,
event_resolution=timedelta(hours=2),
)
db.session.add(report_sensor)

# Create 1 DataSources
source = DataSource("source1")

# Create TimedBeliefs
beliefs = []
for sensor in [sensor1, sensor2]:
for t in range(200):
beliefs.append(
TimedBelief(
event_start=datetime(2023, 4, 10, tzinfo=utc) + timedelta(hours=t),
belief_time=datetime(2023, 4, 9, tzinfo=utc),
event_value=t,
sensor=sensor,
source=source,
)
)

db.session.add_all(beliefs)
db.session.commit()

yield sensor1, sensor2, report_sensor


@pytest.fixture(scope="module")
@pytest.mark.skip_github
def reporter_config_raw(app, db, setup_dummy_data):
"""
This reporter_config defines the operations to add up the
values of the sensors 1 and 2 and resamples the result to a
two hour resolution.
"""

sensor1, sensor2, report_sensor = setup_dummy_data

reporter_config_raw = dict(
tb_query_config=[dict(sensor=sensor1.id), dict(sensor=sensor2.id)],
victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
transformations=[
dict(
df_input="sensor_1",
method="add",
args=["@sensor_2"],
df_output="df_agg",
),
dict(method="resample_events", args=["2h"]),
],
final_df_output="df_agg",
)

return reporter_config_raw