Skip to content

Commit

Permalink
fix: timerange for sensor with a single belief (#732)
Browse files Browse the repository at this point in the history
* fix: get sensor data timerange in case of a single data point

Signed-off-by: F.N. Claessen <felix@seita.nl>

* refactor: move to util function

Signed-off-by: F.N. Claessen <felix@seita.nl>

* fix: apply fix to sensor timerange, too

Signed-off-by: F.N. Claessen <felix@seita.nl>

* fix: flake8

Signed-off-by: F.N. Claessen <felix@seita.nl>

* use min and max in query

Signed-off-by: F.N. Claessen <felix@seita.nl>

* docs: changelog entry

Signed-off-by: F.N. Claessen <felix@seita.nl>

---------

Signed-off-by: F.N. Claessen <felix@seita.nl>
  • Loading branch information
Flix6x committed Jun 20, 2023
1 parent b4b3b1b commit 3a54f3a
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 41 deletions.
1 change: 1 addition & 0 deletions documentation/changelog.rst
Expand Up @@ -23,6 +23,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>`_]
* 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
27 changes: 4 additions & 23 deletions flexmeasures/data/models/generic_assets.py
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
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
@@ -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 3a54f3a

Please sign in to comment.