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

Cli unit conversion for adding data #341

Merged
merged 6 commits into from Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -8,6 +8,7 @@ v0.9.0 | February XX, 2022
New features
-----------
* Three new CLI commands for cleaning up your database: delete 1) unchanged beliefs, 2) NaN values or 3) a sensor and all of its time series data [see `PR #328 <http://www.github.com/FlexMeasures/flexmeasures/pull/328>`_]
* Add CLI option to pass a data unit when reading in time series data from CSV, so data can automatically be converted to the sensor unit [see `PR #341 <http://www.github.com/FlexMeasures/flexmeasures/pull/341>`_]

Bugfixes
-----------
Expand Down
16 changes: 7 additions & 9 deletions flexmeasures/api/common/schemas/sensor_data.py
Expand Up @@ -14,7 +14,7 @@
from flexmeasures.api.common.utils.api_utils import upsample_values
from flexmeasures.data.schemas.times import AwareDateTimeField, DurationField
from flexmeasures.utils.unit_utils import (
determine_unit_conversion_multiplier,
convert_units,
units_are_convertible,
)

Expand Down Expand Up @@ -139,14 +139,12 @@ def possibly_convert_units(self, data, **kwargs):
Convert values if needed, to fit the sensor's unit.
Marshmallow runs this after validation.
"""
posted_unit = data["unit"]
required_unit = data["sensor"].unit

if posted_unit != required_unit:
multiplier = determine_unit_conversion_multiplier(
posted_unit, required_unit, data["sensor"].event_resolution
)
data["values"] = [multiplier * value for value in data["values"]]
data["values"] = convert_units(
data["values"],
from_unit=data["unit"],
to_unit=data["sensor"].unit,
event_resolution=data["sensor"].event_resolution,
)
return data

@post_load()
Expand Down
17 changes: 17 additions & 0 deletions flexmeasures/cli/data_add.py
Expand Up @@ -34,6 +34,7 @@
get_source_or_none,
)
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.unit_utils import convert_units


@click.group("add")
Expand Down Expand Up @@ -390,6 +391,14 @@ def add_initial_structure():
type=str,
help="Source of the beliefs (an existing source id or name, or a new name).",
)
@click.option(
"--unit",
required=False,
type=str,
help="Unit of the data, for conversion to the sensor unit, if possible (a string unit such as 'kW' or 'm³/h').\n"
"Hint: to switch the sign of the data, prepend a minus sign.\n"
"For example, when assigning kW consumption data to a kW production sensor, use '-kW'.",
)
@click.option(
"--horizon",
required=False,
Expand Down Expand Up @@ -471,6 +480,7 @@ def add_beliefs(
file: str,
sensor_id: int,
source: str,
unit: Optional[str] = None,
horizon: Optional[int] = None,
cp: Optional[float] = None,
resample: bool = True,
Expand Down Expand Up @@ -541,6 +551,13 @@ def add_beliefs(
parse_dates=True,
**kwargs,
)
if unit is not None:
bdf["event_value"] = convert_units(
bdf["event_value"],
from_unit=unit,
to_unit=sensor.unit,
event_resolution=sensor.event_resolution,
)
try:
TimedBelief.add(
bdf,
Expand Down
22 changes: 21 additions & 1 deletion flexmeasures/utils/unit_utils.py
@@ -1,8 +1,9 @@
from datetime import timedelta
from typing import Optional
from typing import List, Optional, Union

from moneyed import list_all_currencies
import importlib.resources as pkg_resources
import pandas as pd
import pint

# Edit constants template to stop using h to represent planck_constant
Expand Down Expand Up @@ -151,3 +152,22 @@ def is_energy_unit(unit: str) -> bool:
if not is_valid_unit(unit):
return False
return ur.Quantity(unit).dimensionality == ur.Quantity("Wh").dimensionality


def convert_units(
data: Union[pd.Series, List[Union[int, float]]],
from_unit: str,
to_unit: str,
event_resolution: Optional[timedelta],
) -> List[Union[int, float]]:
"""Updates data values to reflect the given unit conversion."""

if from_unit != to_unit:
multiplier = determine_unit_conversion_multiplier(
from_unit, to_unit, event_resolution
)
if isinstance(data, pd.Series):
data = multiplier * data
else:
data = [multiplier * value for value in data]
return data