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 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 @@ -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(account=account)
return sensors_schema.dump(sensors), 200

@route("/data", methods=["POST"])
Expand Down
15 changes: 12 additions & 3 deletions flexmeasures/data/models/generic_assets.py
Expand Up @@ -431,18 +431,27 @@ 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(
account=self.owner,
include_public_assets=True,
sensor_id_allowlist=sensor_ids,
)
}

# Return sensors in the order given by the sensors_to_show attribute
Expand Down
58 changes: 29 additions & 29 deletions flexmeasures/data/services/sensors.py
@@ -1,43 +1,43 @@
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,
account: Account | list[Account],
include_public_assets: bool = False,
sensor_id_allowlist: list[int] | None = None,
sensor_name_allowlist: list[str] | None = None,
) -> list[Sensor]:
"""Return a list of Sensor objects.
"""Return a list of Sensor objects that belong to the given account, and/or public sensors.

:param account_name: optionally, filter by account name.
:param account: select only sensors from this account (or list of accounts)
:param include_public_assets: if True, include sensors that belong to a public asset
:param sensor_id_allowlist: optionally, allow only sensors whose id is in this list
:param sensor_name_allowlist: optionally, allow only sensors whose name is in this list
"""
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)
)

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 isinstance(account, list):
account_ids = [account.id for account in account]
else:
account_ids = [account.id]
sensor_query = sensor_query.join(GenericAsset).filter(
Sensor.generic_asset_id == GenericAsset.id
)
if sensor_ids:
sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids))
if include_public_assets:
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 sensor_id_allowlist:
sensor_query = sensor_query.filter(Sensor.id.in_(sensor_id_allowlist))
if sensor_name_allowlist:
sensor_query = sensor_query.filter(Sensor.name.in_(sensor_name_allowlist))
return sensor_query.all()