Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into bug/drop-nan-report-v…
Browse files Browse the repository at this point in the history
…alues
  • Loading branch information
Flix6x committed Jun 20, 2023
2 parents 9c49d60 + 99cf03a commit 9048270
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 50 deletions.
3 changes: 3 additions & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ v0.15.0 | July XX, 2023
New features
-------------

* Allow deleting multiple sensors with a single call to ``flexmeasures delete sensor`` by passing the ``--id`` option multiple times [see `PR #734 <https://www.github.com/FlexMeasures/flexmeasures/pull/734>`_]

Bugfixes
-----------

Expand All @@ -24,6 +26,7 @@ Bugfixes

* Relax constraint validation of `StorageScheduler` to accommodate violations caused by floating point precision [see `PR #731 <https://www.github.com/FlexMeasures/flexmeasures/pull/731>`_]
* Avoid saving any :abbr:`NaN (not a number)` values to the database, when calling ``flexmeasures add report`` [see `PR #735 <https://www.github.com/FlexMeasures/flexmeasures/pull/735>`_]
* Fix browser console error when loading asset or sensor page with only a single data point [see `PR #732 <https://www.github.com/FlexMeasures/flexmeasures/pull/732>`_]


v0.14.0 | June 15, 2023
Expand Down
5 changes: 5 additions & 0 deletions documentation/cli/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
FlexMeasures CLI Changelog
**********************

since v0.15.0 | July XX, 2023
=================================

* Allow deleting multiple sensors with a single call to ``flexmeasures delete sensor`` by passing the ``--id`` option multiple times.

since v0.14.1 | June XX, 2023
=================================

Expand Down
4 changes: 2 additions & 2 deletions flexmeasures/cli/data_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def add_source(name: str, model: str, version: str, source_type: str):
"sensor",
required=True,
type=SensorIdField(),
help="Sensor to which the beliefs pertain.",
help="Record the beliefs under this sensor. Follow up with the sensor's ID. ",
)
@click.option(
"--source",
Expand Down Expand Up @@ -1165,7 +1165,7 @@ def add_schedule_for_storage(
"sensor",
type=SensorIdField(),
required=True,
help="ID of the sensor used to save the report."
help="Sensor used to save the report. Follow up with the sensor's ID. "
" If needed, use `flexmeasures add sensor` to create a new sensor first.",
)
@click.option(
Expand Down
22 changes: 15 additions & 7 deletions flexmeasures/cli/data_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,18 +307,26 @@ def delete_nan_beliefs(sensor_id: int | None = None):
@with_appcontext
@click.option(
"--id",
"sensor",
"sensors",
type=SensorIdField(),
required=True,
help="Delete a single sensor and its (time series) data. Follow up with the sensor's ID.",
multiple=True,
help="Delete a sensor and its (time series) data. Follow up with the sensor's ID. "
"This argument can be given multiple times",
)
def delete_sensor(
sensor: Sensor,
sensors: list[Sensor],
):
"""Delete a sensor and all beliefs about it."""
n = TimedBelief.query.filter(TimedBelief.sensor_id == sensor.id).delete()
db.session.delete(sensor)
click.confirm(f"Delete {sensor.__repr__()}, along with {n} beliefs?", abort=True)
"""Delete sensors and their (time series) data."""
n = TimedBelief.query.filter(
TimedBelief.sensor_id.in_(sensor.id for sensor in sensors)
).delete()
for sensor in sensors:
db.session.delete(sensor)
click.confirm(
f"Delete {', '.join(sensor.__repr__() for sensor in sensors)}, along with {n} beliefs?",
abort=True,
)
db.session.commit()


Expand Down
27 changes: 4 additions & 23 deletions flexmeasures/data/models/generic_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
from flexmeasures.data.models.parsing_utils import parse_source_arg
from flexmeasures.data.models.user import User
from flexmeasures.data.queries.annotations import query_asset_annotations
from flexmeasures.data.services.timerange import get_timerange
from flexmeasures.auth.policy import AuthModelMixin, EVERY_LOGGED_IN_USER
from flexmeasures.utils import geo_utils
from flexmeasures.utils.coding_utils import flatten_unique
from flexmeasures.utils.time_utils import (
determine_minimum_resampling_resolution,
server_now,
)
from flexmeasures.utils.time_utils import determine_minimum_resampling_resolution


class GenericAssetType(db.Model):
Expand Down Expand Up @@ -525,26 +523,9 @@ def get_timerange(cls, sensors: List["Sensor"]) -> Dict[str, datetime]: # noqa
'end': datetime.datetime(2020, 12, 3, 14, 30, tzinfo=pytz.utc)
}
"""
from flexmeasures.data.models.time_series import TimedBelief

sensor_ids = [s.id for s in flatten_unique(sensors)]
least_recent_query = (
TimedBelief.query.filter(TimedBelief.sensor_id.in_(sensor_ids))
.order_by(TimedBelief.event_start.asc())
.limit(1)
)
most_recent_query = (
TimedBelief.query.filter(TimedBelief.sensor_id.in_(sensor_ids))
.order_by(TimedBelief.event_start.desc())
.limit(1)
)
results = least_recent_query.union_all(most_recent_query).all()
if not results:
# return now in case there is no data for any of the sensors
now = server_now()
return dict(start=now, end=now)
least_recent, most_recent = results
return dict(start=least_recent.event_start, end=most_recent.event_end)
start, end = get_timerange(sensor_ids)
return dict(start=start, end=end)


def create_generic_asset(generic_asset_type: str, **kwargs) -> GenericAsset:
Expand Down
21 changes: 3 additions & 18 deletions flexmeasures/data/models/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from flexmeasures.data import db
from flexmeasures.data.models.parsing_utils import parse_source_arg
from flexmeasures.data.services.annotations import prepare_annotations_for_chart
from flexmeasures.data.services.timerange import get_timerange
from flexmeasures.data.queries.utils import (
create_beliefs_query,
get_belief_timing_criteria,
Expand All @@ -43,7 +44,6 @@
from flexmeasures.data.models.generic_assets import GenericAsset
from flexmeasures.data.models.validation_utils import check_required_attributes
from flexmeasures.data.queries.sensors import query_sensors_by_proximity
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.geo_utils import parse_lat_lng


Expand Down Expand Up @@ -480,23 +480,8 @@ def timerange(self) -> dict[str, datetime_type]:
'end': datetime.datetime(2020, 12, 3, 14, 30, tzinfo=pytz.utc)
}
"""
least_recent_query = (
TimedBelief.query.filter(TimedBelief.sensor == self)
.order_by(TimedBelief.event_start.asc())
.limit(1)
)
most_recent_query = (
TimedBelief.query.filter(TimedBelief.sensor == self)
.order_by(TimedBelief.event_start.desc())
.limit(1)
)
results = least_recent_query.union_all(most_recent_query).all()
if not results:
# return now in case there is no data for the sensor
now = server_now()
return dict(start=now, end=now)
least_recent, most_recent = results
return dict(start=least_recent.event_start, end=most_recent.event_end)
start, end = get_timerange([self.id])
return dict(start=start, end=end)

def __repr__(self) -> str:
return f"<Sensor {self.id}: {self.name}, unit: {self.unit} res.: {self.event_resolution}>"
Expand Down
31 changes: 31 additions & 0 deletions flexmeasures/data/services/timerange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from datetime import datetime

from sqlalchemy import func

from flexmeasures.utils import time_utils


def get_timerange(sensor_ids: list[int]) -> tuple[datetime, datetime]:
"""Get the start and end of the least recent and most recent event, respectively.
In case of no data, defaults to (now, now).
"""
from flexmeasures.data.models.time_series import Sensor, TimedBelief

least_recent_event_start_and_most_recent_event_end = (
TimedBelief.query.with_entities(
# least recent event start
func.min(TimedBelief.event_start),
# most recent event end
func.max(TimedBelief.event_start + Sensor.event_resolution),
)
.join(Sensor, TimedBelief.sensor_id == Sensor.id)
.filter(TimedBelief.sensor_id.in_(sensor_ids))
).one_or_none()
if least_recent_event_start_and_most_recent_event_end == (None, None):
# return now in case there is no data for any of the sensors
now = time_utils.server_now()
return now, now
return least_recent_event_start_and_most_recent_event_end

0 comments on commit 9048270

Please sign in to comment.