From 3a54f3ac7440e9920a1989622a58058743629597 Mon Sep 17 00:00:00 2001 From: Felix Claessen <30658763+Flix6x@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:43:12 +0200 Subject: [PATCH] fix: timerange for sensor with a single belief (#732) * fix: get sensor data timerange in case of a single data point Signed-off-by: F.N. Claessen * refactor: move to util function Signed-off-by: F.N. Claessen * fix: apply fix to sensor timerange, too Signed-off-by: F.N. Claessen * fix: flake8 Signed-off-by: F.N. Claessen * use min and max in query Signed-off-by: F.N. Claessen * docs: changelog entry Signed-off-by: F.N. Claessen --------- Signed-off-by: F.N. Claessen --- documentation/changelog.rst | 1 + flexmeasures/data/models/generic_assets.py | 27 +++---------------- flexmeasures/data/models/time_series.py | 21 +++------------ flexmeasures/data/services/timerange.py | 31 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 flexmeasures/data/services/timerange.py diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 98260ae8a..107cd2259 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -23,6 +23,7 @@ Bugfixes ----------- * Relax constraint validation of `StorageScheduler` to accommodate violations caused by floating point precision [see `PR #731 `_] +* Fix browser console error when loading asset or sensor page with only a single data point [see `PR #732 `_] v0.14.0 | June 15, 2023 diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index facdf2c7b..fe7e792a8 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -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): @@ -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: diff --git a/flexmeasures/data/models/time_series.py b/flexmeasures/data/models/time_series.py index 222863437..70f1c1f08 100644 --- a/flexmeasures/data/models/time_series.py +++ b/flexmeasures/data/models/time_series.py @@ -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, @@ -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 @@ -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"" diff --git a/flexmeasures/data/services/timerange.py b/flexmeasures/data/services/timerange.py new file mode 100644 index 000000000..d894ed4bf --- /dev/null +++ b/flexmeasures/data/services/timerange.py @@ -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