33from datetime import timedelta
44import json
55from http import HTTPStatus
6- from typing import Any
6+ from typing import Any , TYPE_CHECKING
77
88from flask import abort
99from marshmallow import validates , ValidationError , fields , validates_schema
1010from marshmallow .validate import OneOf
1111from flask_security import current_user
1212from sqlalchemy import select
1313
14-
14+ if TYPE_CHECKING :
15+ from flexmeasures import Sensor
1516from flexmeasures .data import ma , db
1617from flexmeasures .data .models .user import Account
1718from flexmeasures .data .models .generic_assets import GenericAsset , GenericAssetType
2122from flexmeasures .data .schemas .utils import (
2223 FMValidationError ,
2324 MarshmallowClickMixin ,
24- extract_sensors_from_flex_config ,
2525)
2626from flexmeasures .auth .policy import user_has_admin_access
2727from flexmeasures .cli import is_running as running_as_cli
@@ -211,23 +211,34 @@ def _validate_flex_config_field_is_valid_choice(
211211 )
212212
213213 @classmethod
214- def flatten (cls , nested_list ) -> list [int ]:
214+ def flatten (cls , nested_list : list ) -> list [int ] | list [ Sensor ]:
215215 """
216- Flatten a nested list of sensors or sensor dictionaries into a unique list of sensor IDs.
217-
218- This method processes the following formats, for each of the entries of the nested list:
219- - A list of sensor IDs: `[1, 2, 3]`
220- - A list of dictionaries where each dictionary contains a `sensors` list, a `sensor` key or a `plots` key
221- `[{"title": "Temperature", "sensors": [1, 2]}, {"title": "Pressure", "sensor": 3}, {"title": "Pressure", "plots": [{"sensor": 4}, {"sensors": [5,6]}]}]`
222- - Mixed formats: `[{"title": "Temperature", "sensors": [1, 2]}, {"title": "Pressure", "sensor": 3}, 4, 5, 1]`
223-
224- It extracts all sensor IDs, removes duplicates, and returns a flattened list of unique sensor IDs.
225-
226- Args:
227- nested_list (list): A list containing sensor IDs, or dictionaries with `sensors` or `sensor` keys.
228-
229- Returns:
230- list: A unique list of sensor IDs.
216+ Flatten a nested list of sensor IDs into a unique list. Also works for Sensor objects.
217+
218+ This method processes the following formats for each entry in the list:
219+ 1. A single sensor ID:
220+ `3`
221+ 2. A list of sensor IDs:
222+ `[1, 2]`
223+ 3. A dictionary with a `sensor` key:
224+ `{"sensor": 3}`
225+ 4. A dictionary with a `sensors` key:
226+ `{"sensors": [1, 2]}`
227+ 5. A dictionary with a `plots` key, containing a list of dictionaries,
228+ each with a `sensor` or `sensors` key:
229+ `{"plots": [{"sensor": 4}, {"sensors": [5, 6]}]}`
230+ 6. A dictionary under the `plots` key containing the `asset` key together with a `flex-model` or `flex-context` key,
231+ containing a field name or a list of field names:
232+ `{"plots": [{"asset": 100, "flex-model": ["consumption-capacity", "production-capacity"], "flex-context": "site-power-capacity"}}`
233+ 7. Mixed formats:
234+ `[{"title": "Temperature", "sensors": [1, 2]}, {"title": "Pressure", "sensor": 3}, {"title": "Pressure", "plots": [{"sensor": 4}, {"sensors": [5, 6]}]}]`
235+
236+ Example:
237+ >>> SensorsToShowSchema.flatten([1, [2, 20, 6], 10, [6, 2], {"title": None,"sensors": [10, 15]}, 15, {"plots": [{"sensor": 1}, {"sensors": [20, 8]}]}])
238+ [1, 2, 20, 6, 10, 15, 8]
239+
240+ :param nested_list: A list containing sensor IDs, or dictionaries with `sensors` or `sensor` keys.
241+ :returns: A unique list of sensor IDs, or a unique list of Sensors
231242 """
232243 all_objects = []
233244 for s in nested_list :
@@ -249,7 +260,6 @@ def flatten(cls, nested_list) -> list[int]:
249260 all_objects .extend (s ["sensors" ])
250261 elif "sensor" in s :
251262 all_objects .append (s ["sensor" ])
252-
253263 return list (dict .fromkeys (all_objects ).keys ())
254264
255265
@@ -427,3 +437,41 @@ def _deserialize(self, value: Any, attr, data, **kwargs) -> GenericAsset:
427437 def _serialize (self , value : GenericAsset , attr , obj , ** kwargs ) -> int :
428438 """Turn a GenericAsset into a generic asset id."""
429439 return value .id
440+
441+
442+ def extract_sensors_from_flex_config (plot : dict ) -> list [Sensor ]:
443+ """
444+ Extracts a consolidated list of sensors from an asset based on
445+ flex-context or flex-model definitions provided in a plot dictionary.
446+ """
447+ all_sensors = []
448+
449+ asset = GenericAssetIdField ().deserialize (plot .get ("asset" ))
450+
451+ fields_to_check = {
452+ "flex-context" : asset .flex_context ,
453+ "flex-model" : asset .flex_model ,
454+ }
455+
456+ for plot_key , flex_config in fields_to_check .items ():
457+ if plot_key not in plot :
458+ continue
459+
460+ field_keys = plot [plot_key ]
461+ data = flex_config or {}
462+
463+ if isinstance (field_keys , str ):
464+ field_keys = [field_keys ]
465+ elif not isinstance (field_keys , list ):
466+ continue
467+
468+ for field_key in field_keys :
469+ field_value = data .get (field_key )
470+
471+ if isinstance (field_value , dict ):
472+ # Add a single sensor if it exists
473+ sensor = field_value .get ("sensor" )
474+ if sensor :
475+ all_sensors .append (sensor )
476+
477+ return all_sensors
0 commit comments