-
Notifications
You must be signed in to change notification settings - Fork 38
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
Add endpoint for getting asset annotations #1113
base: main
Are you sure you want to change the base?
Changes from 6 commits
2387126
8e88401
12539ff
f99c444
b00a806
c6e8959
d154e40
fcb7411
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,8 @@ | ||||||||||||
from __future__ import annotations | ||||||||||||
|
||||||||||||
import json | ||||||||||||
import warnings | ||||||||||||
from typing import Any | ||||||||||||
|
||||||||||||
from flask_classful import FlaskView, route | ||||||||||||
from flask_security import current_user | ||||||||||||
|
@@ -20,6 +23,10 @@ | |||||||||||
from flexmeasures.data.models.time_series import Sensor | ||||||||||||
from flexmeasures.data.services.annotations import prepare_annotations_for_chart | ||||||||||||
from flexmeasures.ui.utils.view_utils import set_session_variables | ||||||||||||
from flexmeasures.data.models.annotations import ( | ||||||||||||
SensorAnnotationRelationship, | ||||||||||||
GenericAssetAnnotationRelationship, | ||||||||||||
) | ||||||||||||
|
||||||||||||
|
||||||||||||
class SensorAPI(FlaskView): | ||||||||||||
|
@@ -132,20 +139,13 @@ def get_chart_annotations(self, id: int, sensor: Sensor, **kwargs): | |||||||||||
|
||||||||||||
Annotations for use in charts (in case you have the chart specs already). | ||||||||||||
""" | ||||||||||||
event_starts_after = kwargs.get("event_starts_after", None) | ||||||||||||
event_ends_before = kwargs.get("event_ends_before", None) | ||||||||||||
df = sensor.generic_asset.search_annotations( | ||||||||||||
annotations_after=event_starts_after, | ||||||||||||
annotations_before=event_ends_before, | ||||||||||||
as_frame=True, | ||||||||||||
df = get_annotations_data( | ||||||||||||
sensor_id=id, | ||||||||||||
asset_or_sensor=sensor, | ||||||||||||
relationship_module=SensorAnnotationRelationship, | ||||||||||||
kwargs=kwargs, | ||||||||||||
) | ||||||||||||
|
||||||||||||
# Wrap and stack annotations | ||||||||||||
df = prepare_annotations_for_chart(df) | ||||||||||||
|
||||||||||||
# Return JSON records | ||||||||||||
df = df.reset_index() | ||||||||||||
df["source"] = df["source"].astype(str) | ||||||||||||
return df.to_json(orient="records") | ||||||||||||
|
||||||||||||
@route("/<id>", strict_slashes=False) | ||||||||||||
|
@@ -186,6 +186,70 @@ def get(self, id: int, asset: GenericAsset): | |||||||||||
attributes = ["name", "timezone", "timerange_of_sensors_to_show"] | ||||||||||||
return {attr: getattr(asset, attr) for attr in attributes} | ||||||||||||
|
||||||||||||
@route("/<id>/chart_annotations", strict_slashes=False) | ||||||||||||
@use_kwargs( | ||||||||||||
{"asset": AssetIdField(data_key="id")}, | ||||||||||||
location="path", | ||||||||||||
) | ||||||||||||
@use_kwargs( | ||||||||||||
{ | ||||||||||||
"event_starts_after": AwareDateTimeField(format="iso", required=False), | ||||||||||||
"event_ends_before": AwareDateTimeField(format="iso", required=False), | ||||||||||||
"beliefs_after": AwareDateTimeField(format="iso", required=False), | ||||||||||||
"beliefs_before": AwareDateTimeField(format="iso", required=False), | ||||||||||||
}, | ||||||||||||
location="query", | ||||||||||||
) | ||||||||||||
@permission_required_for_context("read", ctx_arg_name="asset") | ||||||||||||
def get_chart_annotations(self, id: int, asset: GenericAsset, **kwargs): | ||||||||||||
"""GET from /asset/<id>/chart_annotations | ||||||||||||
|
||||||||||||
.. :quickref: Chart; Download annotations for use in charts | ||||||||||||
|
||||||||||||
Annotations for use in charts (in case you have the chart specs already). | ||||||||||||
""" | ||||||||||||
df = get_annotations_data( | ||||||||||||
sensor_id=None, | ||||||||||||
asset_or_sensor=asset, | ||||||||||||
relationship_module=GenericAssetAnnotationRelationship, | ||||||||||||
kwargs=kwargs, | ||||||||||||
) | ||||||||||||
|
||||||||||||
return df.to_json(orient="records") | ||||||||||||
|
||||||||||||
|
||||||||||||
def get_annotations_data( | ||||||||||||
sensor_id: int | None, | ||||||||||||
asset_or_sensor: GenericAsset | Sensor, | ||||||||||||
relationship_module: Any, | ||||||||||||
kwargs, | ||||||||||||
): | ||||||||||||
""" | ||||||||||||
This function fetches a sensor or an asset annotations data | ||||||||||||
""" | ||||||||||||
event_starts_after = kwargs.get("event_starts_after", None) | ||||||||||||
event_ends_before = kwargs.get("event_ends_before", None) | ||||||||||||
if asset_or_sensor is GenericAsset: | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is always |
||||||||||||
asset_or_sensor_class = asset_or_sensor.generic_asset | ||||||||||||
else: | ||||||||||||
asset_or_sensor_class = asset_or_sensor | ||||||||||||
Comment on lines
+232
to
+235
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
df = asset_or_sensor_class.search_annotations( | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need the class, actually? Isn't
Suggested change
|
||||||||||||
annotations_after=event_starts_after, | ||||||||||||
annotations_before=event_ends_before, | ||||||||||||
as_frame=True, | ||||||||||||
sensor_id=sensor_id, | ||||||||||||
relationship_module=relationship_module, | ||||||||||||
) | ||||||||||||
|
||||||||||||
# Wrap and stack annotations | ||||||||||||
df = prepare_annotations_for_chart(df) | ||||||||||||
|
||||||||||||
# Return JSON records | ||||||||||||
df = df.reset_index() | ||||||||||||
df["source"] = df["source"].astype(str) | ||||||||||||
return df | ||||||||||||
|
||||||||||||
|
||||||||||||
def get_sensor_or_abort(id: int) -> Sensor: | ||||||||||||
""" | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,11 @@ | |
from timely_beliefs import BeliefsDataFrame, utils as tb_utils | ||
|
||
from flexmeasures.data import db | ||
from flexmeasures.data.models.annotations import Annotation, to_annotation_frame | ||
from flexmeasures.data.models.annotations import ( | ||
Annotation, | ||
to_annotation_frame, | ||
GenericAssetAnnotationRelationship, | ||
) | ||
from flexmeasures.data.models.charts import chart_type_to_chart_specs | ||
from flexmeasures.data.models.data_sources import DataSource | ||
from flexmeasures.data.models.parsing_utils import parse_source_arg | ||
|
@@ -355,6 +359,8 @@ def search_annotations( | |
annotation_type: str = None, | ||
include_account_annotations: bool = False, | ||
as_frame: bool = False, | ||
sensor_id: int = None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I don't think we need it. See my comment about using calling this method on the instance rather than the class. |
||
relationship_module: Any = GenericAssetAnnotationRelationship, | ||
) -> list[Annotation] | pd.DataFrame: | ||
"""Return annotations assigned to this asset, and optionally, also those assigned to the asset's account. | ||
|
||
|
@@ -366,11 +372,12 @@ def search_annotations( | |
parsed_sources = parse_source_arg(source) | ||
annotations = db.session.scalars( | ||
query_asset_annotations( | ||
asset_id=self.id, | ||
asset_or_sensor_id=self.id if sensor_id is None else sensor_id, | ||
annotations_after=annotations_after, | ||
annotations_before=annotations_before, | ||
sources=parsed_sources, | ||
annotation_type=annotation_type, | ||
relationship_module=relationship_module, | ||
) | ||
).all() | ||
if include_account_annotations and self.owner is not None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,26 +8,33 @@ | |
from flexmeasures.data.models.annotations import ( | ||
Annotation, | ||
GenericAssetAnnotationRelationship, | ||
SensorAnnotationRelationship, | ||
) | ||
from flexmeasures.data.models.data_sources import DataSource | ||
|
||
|
||
def query_asset_annotations( | ||
asset_id: int, | ||
asset_or_sensor_id: int, | ||
relationship_module, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing type annotation, possibly an Enum with the 3 relationship choices we currently have (with Accounts, GenericAssets and Sensors). |
||
annotations_after: datetime | None = None, | ||
annotations_before: datetime | None = None, | ||
sources: list[DataSource] | None = None, | ||
annotation_type: str | None = None, | ||
) -> Query: | ||
"""Match annotations assigned to the given asset.""" | ||
query = ( | ||
select(Annotation) | ||
.join(GenericAssetAnnotationRelationship) | ||
.filter( | ||
GenericAssetAnnotationRelationship.generic_asset_id == asset_id, | ||
query = select(Annotation) | ||
if relationship_module is GenericAssetAnnotationRelationship: | ||
Ahmad-Wahid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
query = query.join( | ||
GenericAssetAnnotationRelationship, | ||
GenericAssetAnnotationRelationship.annotation_id == Annotation.id, | ||
).filter( | ||
GenericAssetAnnotationRelationship.generic_asset_id == asset_or_sensor_id | ||
) | ||
) | ||
else: | ||
query = query.join( | ||
SensorAnnotationRelationship, | ||
SensorAnnotationRelationship.annotation_id == Annotation.id, | ||
).filter(SensorAnnotationRelationship.sensor_id == asset_or_sensor_id) | ||
|
||
if annotations_after is not None: | ||
query = query.filter( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter seems redundant, since we are already passing the sensor or asset instance itself.