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

Issue 499 allow showing sensor data from other assets in the same account #500

Merged
Show file tree
Hide file tree
Changes from 12 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 @@ -9,6 +9,7 @@ New features
-------------

* Hit the replay button to replay what happened, available on the sensor and asset pages [see `PR #463 <http://www.github.com/FlexMeasures/flexmeasures/pull/463>`_]
* The asset page also allows to show sensor data from other assets that belong to the same account [see `PR #500 <http://www.github.com/FlexMeasures/flexmeasures/pull/500>`_]
* Improved import of time series data from CSV file: 1) drop duplicate records with warning, and 2) allow configuring which column contains explicit recording times for each data point (use case: import forecasts) [see `PR #501 <http://www.github.com/FlexMeasures/flexmeasures/pull/501>`_]

Bugfixes
Expand Down
2 changes: 1 addition & 1 deletion flexmeasures/api/v3_0/sensors.py
Expand Up @@ -110,7 +110,7 @@ def index(self, account: Account):
:status 403: INVALID_SENDER
:status 422: UNPROCESSABLE_ENTITY
"""
sensors = get_sensors(account_name=account.name)
sensors = get_sensors(accounts=[account])
return sensors_schema.dump(sensors), 200

@route("/data", methods=["POST"])
Expand Down
14 changes: 11 additions & 3 deletions flexmeasures/data/models/generic_assets.py
Expand Up @@ -431,18 +431,26 @@ def search_beliefs(
def sensors_to_show(self) -> List["Sensor"]: # noqa F821
"""Sensors to show, as defined by the sensors_to_show attribute.

Sensors to show are defined as a list of sensor ids, which
is set by the "sensors_to_show" field of the asset's "attributes" column.
Valid sensors either belong to the asset itself, to other assets in the same account,
or to public assets.


Defaults to two of the asset's sensors.
"""
if not self.has_attribute("sensors_to_show"):
return self.sensors[:2]

from flexmeasures.data.services.sensors import get_public_sensors
from flexmeasures.data.services.sensors import get_sensors

sensor_ids = self.get_attribute("sensors_to_show")
sensor_map = {
sensor.id: sensor
for sensor in self.sensors + get_public_sensors(sensor_ids)
if sensor.id in sensor_ids
for sensor in get_sensors(
accounts=[self.owner, None], # include public sensors
filter_by_sensor_ids=sensor_ids,
)
}

# Return sensors in the order given by the sensors_to_show attribute
Expand Down
51 changes: 22 additions & 29 deletions flexmeasures/data/services/sensors.py
@@ -1,43 +1,36 @@
from __future__ import annotations

from werkzeug.exceptions import NotFound
import sqlalchemy as sa

from flexmeasures import Sensor, Account
from flexmeasures.data.models.generic_assets import GenericAsset


def get_sensors(
Flix6x marked this conversation as resolved.
Show resolved Hide resolved
account_name: str | None = None,
accounts: list[Account | None] = None,
filter_by_sensor_ids: list[int] | None = None,
) -> list[Sensor]:
"""Return a list of Sensor objects.
"""Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors.

:param account_name: optionally, filter by account name.
:param accounts: optionally, select only sensors from this list of accounts
(include None to select sensors that belong to a public asset).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of the None argument to indicate public assets.
I believe an explicit parameter "include_public_assets", which defaults to False, would make it easier to see what's going on.

:param filter_by_sensor_ids: optionally, filter by sensor id.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"optionally, filter out any sensors which are not in this whitelist"

"""
sensor_query = Sensor.query

if account_name is not None:
account = Account.query.filter(Account.name == account_name).one_or_none()
if not account:
raise NotFound(f"There is no account named {account_name}!")
sensor_query = (
sensor_query.join(GenericAsset)
.filter(Sensor.generic_asset_id == GenericAsset.id)
.filter(GenericAsset.owner == account)
if accounts is not None:
account_ids = [account.id for account in accounts if account is not None]
sensor_query = sensor_query.join(GenericAsset).filter(
Sensor.generic_asset_id == GenericAsset.id
)

return sensor_query.all()


def get_public_sensors(sensor_ids: list[int] | None = None) -> list[Sensor]:
"""Return a list of Sensor objects that belong to a public asset.

:param sensor_ids: optionally, filter by sensor id.
"""
sensor_query = (
Sensor.query.join(GenericAsset)
.filter(Sensor.generic_asset_id == GenericAsset.id)
.filter(GenericAsset.account_id.is_(None))
)
if sensor_ids:
sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids))
if None in accounts:
sensor_query = sensor_query.filter(
sa.or_(
GenericAsset.account_id.in_(account_ids),
GenericAsset.account_id.is_(None),
)
)
else:
sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids))
if filter_by_sensor_ids:
sensor_query = sensor_query.filter(Sensor.id.in_(filter_by_sensor_ids))
return sensor_query.all()