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 all 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
3 changes: 2 additions & 1 deletion documentation/changelog.rst
Expand Up @@ -10,7 +10,8 @@ New features
-------------

* Introduction of the classes `Reporter` and `PandasReporter` [see `PR #641 <https://www.github.com/FlexMeasures/flexmeasures/pull/641>`_]

* Add CLI command ``flexmeasures add report`` [see `PR #659 <https://www.github.com/FlexMeasures/flexmeasures/pull/659>`_]
* Add CLI command ``flexmeasures show reporters`` [see `PR #686 <https://www.github.com/FlexMeasures/flexmeasures/pull/686>`_]

Bugfixes
-----------
Expand Down
2 changes: 2 additions & 0 deletions documentation/cli/commands.rst
Expand Up @@ -39,6 +39,7 @@ of which some are referred to in this documentation.
``flexmeasures add holidays`` Add holiday annotations to accounts and/or assets.
``flexmeasures add annotation`` Add annotation to accounts, assets and/or sensors.
``flexmeasures add toy-account`` Create a toy account, for tutorials and trying things.
``flexmeasures add report`` Create a report.
================================================= =======================================


Expand All @@ -53,6 +54,7 @@ of which some are referred to in this documentation.
``flexmeasures show roles`` List available account- and user roles.
``flexmeasures show data-sources`` List available data sources.
``flexmeasures show beliefs`` Plot time series data.
``flexmeasures show reporters`` List available reporters.
================================================= =======================================


Expand Down
259 changes: 250 additions & 9 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 @@ -58,8 +62,11 @@
)
from flexmeasures.data.services.utils import get_or_create_model
from flexmeasures.utils import flexmeasures_inflection
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.time_utils import server_now, get_timezone, apply_offset_chain
from flexmeasures.utils.unit_utils import convert_units, ur
from flexmeasures.data.utils import save_to_db
from flexmeasures.data.models.reporting import Reporter
from timely_beliefs import BeliefsDataFrame


@click.group("add")
Expand Down Expand Up @@ -127,7 +134,7 @@ def new_account(name: str, roles: str):
@click.option(
"--timezone",
"timezone_optional",
help="timezone as string, e.g. 'UTC' or 'Europe/Amsterdam' (defaults to FLEXMEASURES_TIMEZONE config setting)",
help="Timezone as string, e.g. 'UTC' or 'Europe/Amsterdam' (defaults to FLEXMEASURES_TIMEZONE config setting)",
)
def new_user(
username: str,
Expand Down Expand Up @@ -190,7 +197,7 @@ def new_user(
@click.option(
"--timezone",
required=True,
help="timezone as string, e.g. 'UTC' or 'Europe/Amsterdam'",
help="Timezone as string, e.g. 'UTC' or 'Europe/Amsterdam'",
)
@click.option(
"--asset-id",
Expand Down Expand Up @@ -439,7 +446,7 @@ def add_source(name: str, model: str, version: str, source_type: str):
"--timezone",
required=False,
default=None,
help="timezone as string, e.g. 'UTC' or 'Europe/Amsterdam'",
help="Timezone as string, e.g. 'UTC' or 'Europe/Amsterdam'",
)
@click.option(
"--filter-column",
Expand All @@ -458,21 +465,21 @@ def add_source(name: str, model: str, version: str, source_type: str):
required=True,
type=str,
default=",",
help="[For csv files] Character to delimit columns per row, defaults to comma",
help="[For CSV files] Character to delimit columns per row, defaults to comma",
)
@click.option(
"--decimal",
required=False,
default=".",
type=str,
help="[For csv files] decimal character, e.g. '.' for 10.5",
help="[For CSV files] decimal character, e.g. '.' for 10.5",
)
@click.option(
"--thousands",
required=False,
default=None,
type=str,
help="[For csv files] thousands separator, e.g. '.' for 10.035,2",
help="[For CSV files] thousands separator, e.g. '.' for 10.035,2",
)
@click.option(
"--sheet_number",
Expand Down Expand Up @@ -505,9 +512,9 @@ def add_beliefs(
sheet_number: int | None = None,
**kwargs, # in-code calls to this CLI command can set additional kwargs for use in pandas.read_csv or pandas.read_excel
):
"""Add sensor data from a csv file (also accepts xls or xlsx).
"""Add sensor data from a CSV or Excel file.

To use default settings, structure your csv file as follows:
To use default settings, structure your CSV file as follows:

- One header line (will be ignored!)
- UTC datetimes in 1st column
Expand Down Expand Up @@ -1117,6 +1124,240 @@ def add_schedule_for_storage(
click.secho("New schedule is stored.", **MsgStyle.SUCCESS)


@fm_add_data.command("report")
@with_appcontext
@click.option(
"--sensor-id",
"sensor",
type=SensorIdField(),
required=True,
help="ID of the sensor used to save the report."
" If needed, use `flexmeasures add sensor` to create a new sensor first.",
)
@click.option(
"--reporter-config",
"reporter_config",
required=True,
type=click.File("r"),
help="Path to the JSON file with the reporter configuration.",
)
@click.option(
"--reporter",
"reporter_class",
default="PandasReporter",
type=click.STRING,
help="Reporter class registered in flexmeasures.data.models.reporting or in an available flexmeasures plugin."
" Use the command `flexmeasures show reporters` to list all the available reporters.",
)
@click.option(
"--start",
"start",
type=AwareDateTimeField(format="iso"),
required=False,
help="Report start time. `--start-offset` can be used instead. Follow up with a timezone-aware datetime in ISO 6801 format.",
)
@click.option(
"--start-offset",
"start_offset",
type=str,
required=False,
help="Report start offset time from now. Use multiple Pandas offset strings separated by commas, e.g: -3D,DB,1W. Use DB or HB to offset to the begin of the day or hour, respectively.",
)
@click.option(
"--end-offset",
"end_offset",
type=str,
required=False,
help="Report end offset time from now. Use multiple Pandas offset strings separated by commas, e.g: -3D,DB,1W. Use DB or HB to offset to the begin of the day or hour, respectively.",
)
@click.option(
"--end",
"end",
type=AwareDateTimeField(format="iso"),
required=False,
help="Report end time. `--end-offset` 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 save the report to file. Will override any previous file contents."
" 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(
"--dry-run",
"dry_run",
is_flag=True,
help="Add this flag to avoid saving the results to the database.",
)
def add_report( # noqa: C901
reporter_class: str,
sensor: Sensor,
reporter_config: TextIOBase,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
start_offset: Optional[str] = None,
end_offset: Optional[str] = None,
resolution: Optional[timedelta] = None,
output_file: Optional[Path] = None,
dry_run: bool = False,
timezone: str | pytz.BaseTzInfo = get_timezone(),
):
"""
Create a new report using the Reporter class and save the results
to the database or export them as CSV or Excel file.
"""

# parse timezone into a BaseTzInfo object
if isinstance(timezone, str):
check_timezone(timezone)
timezone = pytz.timezone(zone=timezone)

now = timezone.localize(datetime.now())

# apply offsets, if provided
if start_offset is not None:
if start is None:
start = now
start = apply_offset_chain(start, start_offset)

if end_offset is not None:
if end is None:
end = now
end = apply_offset_chain(end, end_offset)

# the case of not getting --start or --start-offset
if start is None:
click.secho(
"Either --start or --start-offset should be provided."
" Trying to use the latest datapoint of the report sensor as the start time...",
**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
if last_value_datetime[0] is not None:
start = last_value_datetime[0]
else:
click.secho(
f"Could not find any data for the report sensor {sensor}.",
**MsgStyle.ERROR,
)
raise click.Abort()

# the case of not getting --end or --end-offset
if end is None:
click.secho(
"Either --end or --end-offset should be provided."
" Trying to use the current time as the end...",
**MsgStyle.WARN,
)
end = now

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: Type[Reporter] = 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)

# initialize reporter class with the reporter sensor and reporter config
reporter: Reporter = ReporterClass(
sensor=sensor, reporter_config_raw=reporter_config_raw
)

victorgarcia98 marked this conversation as resolved.
Show resolved Hide resolved
click.echo("Report computation is running...")

# compute the report
result: BeliefsDataFrame = reporter.compute(
start=start, end=end, input_resolution=resolution
)

if not result.empty:
click.secho("Report computation done.", **MsgStyle.SUCCESS)
else:
click.secho(
"Report computation done, but the report is empty.", **MsgStyle.WARN
)

# save the report it's not running in dry mode
if not dry_run:
click.echo("Saving report to the database...")
save_to_db(result)
db.session.commit()
click.secho(
"Success. The report has been saved to the database.",
**MsgStyle.SUCCESS,
)
else:
click.echo(
f"Not saving report to the database (because of --dry-run), but this is what I computed:\n{result}"
)

# 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,
)
result.to_csv(output_file)
else:
click.secho(
"Success.",
**MsgStyle.SUCCESS,
)


@fm_add_data.command("toy-account")
@with_appcontext
@click.option(
Expand Down
24 changes: 24 additions & 0 deletions flexmeasures/cli/data_show.py
Expand Up @@ -376,4 +376,28 @@ def plot_beliefs(
click.secho("Data saved to file.", **MsgStyle.SUCCESS)


@fm_show_data.command("reporters")
@with_appcontext
def list_reporters():
"""
Show available reporters.
"""

click.echo("Reporters:\n")
click.echo(
tabulate(
[
(
reporter_name,
reporter_class.__version__,
reporter_class.__author__,
reporter_class.__module__,
)
for reporter_name, reporter_class in app.reporters.items()
],
headers=["name", "version", "author", "module"],
)
)


app.cli.add_command(fm_show_data)