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

fix: timerange for sensor with a single belief #732

Merged
merged 7 commits into from Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -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