diff --git a/documentation/api/change_log.rst b/documentation/api/change_log.rst index bfea0faeea..4340c76d05 100644 --- a/documentation/api/change_log.rst +++ b/documentation/api/change_log.rst @@ -6,6 +6,11 @@ API change log .. note:: The FlexMeasures API follows its own versioning scheme. This is also reflected in the URL (e.g. `/api/v3_0`), allowing developers to upgrade at their own pace. +v3.0-26 | 2025-09-02 +"""""""""""""""""""" +- Added endpoint `GET /assets/types`. + + v3.0-25 | 2025-07-24 """""""""""""""""""" - Removed /play blueprint with endpoint `PUT /restoreData`. diff --git a/documentation/changelog.rst b/documentation/changelog.rst index fa30389d3a..95f9f6fc7d 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -11,8 +11,10 @@ v0.28.0 | September XX, 2025 New features ------------- + * Display KPIs for asset sensors with daily event resolution [see `PR #1608 `_, `PR #1634 `_ and `PR #1656 `_] * Improved timestamp on sensor detail page to be more friendly [see `PR #1632 `_] +* Asset types support: new API endpoint (`GET /assets/types`), better docs and fix CLI command `flexmeasures show asset-types` [see `PR #1663 `_] Infrastructure / Support ---------------------- diff --git a/documentation/concepts/data-model.rst b/documentation/concepts/data-model.rst index 2e491e40a6..3c92793355 100644 --- a/documentation/concepts/data-model.rst +++ b/documentation/concepts/data-model.rst @@ -39,7 +39,14 @@ Here is an example of an asset with sub-assets: D <--> H[Battery] D <--> I["Charge
Point"] -We model asset types explicitly. None are required for running FlexMeasures. Some asset types have support in the UI (for icons, like a sun for ``"solar"``), and in the toy tutorial and test. Some are used to select the scheduler (e.g. using ``"battery"`` or ``"one-way_evse"`` leads to using the storage scheduler). You can add your own types, which is useful for plugin logic (an example is the ``"weather station"`` type for a plugin that reads in weather forecasts). +We model asset types explicitly. None are required for running FlexMeasures. +Some asset types have support in the UI (for icons, like a sun for ``"solar"``), and in the toy tutorial and test. + +Some are used to select the scheduler (e.g. using ``"battery"`` or ``"one-way_evse"`` leads to using the storage scheduler). +However, in practice (for now), we default to storage scheduling for everything not typed "process" or "load" (as those would use the process scheduler). + +You can add default types (and other useful things like data sources and user roles) by running ``flexmeasures add initial-structure``. +You can add your own types, which is useful for plugin logic (an example is the ``"weather station"`` type for a plugin that reads in weather forecasts). The CLI command for this is ``flexmeasures add asset-type``. Sensors --------- diff --git a/documentation/dev/setup-and-guidelines.rst b/documentation/dev/setup-and-guidelines.rst index be150c3c46..e2105829c1 100644 --- a/documentation/dev/setup-and-guidelines.rst +++ b/documentation/dev/setup-and-guidelines.rst @@ -151,6 +151,21 @@ Otherwise, you need to add some other user first. Here is how we add an admin: We recommend to use the Windows Sub-system for Linux (https://learn.microsoft.com/en-us/windows/wsl/install) or work via Docker-compose (https://flexmeasures.readthedocs.io/en/latest/dev/docker-compose.html). +We recommend you populate the database with some standard asset types, user roles, data sources etc.: + +.. code-block:: bash + + $ flexmeasures add initial-structure + +For instance, without an asset type, you cannot add an asset. + +Actually, you can add many things from the terminal. Check what data you can add yourself: + +.. code-block:: bash + + $ flexmeasures add --help + + Logfile -------- diff --git a/documentation/host/data.rst b/documentation/host/data.rst index 07459d213c..01163e40df 100644 --- a/documentation/host/data.rst +++ b/documentation/host/data.rst @@ -253,23 +253,26 @@ First, you can get the database structure with: .. note:: If you develop code (and might want to make changes to the data model), you should also check out the maintenance section about database migrations. -You can create users with the ``add user`` command. Check it out: + +You should create some pre-determined asset types, user/account roles and data sources with this command: .. code-block:: bash - $ flexmeasures add account --help - $ flexmeasures add user --help + $ flexmeasures add initial-structure -You can create some pre-determined asset types and data sources with this command: +Another good first step is to create an account for yourself, plus a user to log in with: .. code-block:: bash - $ flexmeasures add initial-structure + $ flexmeasures add account --help + $ flexmeasures add user --help + +Creating accounts and users for your clients would also happen this way (soon also in the UI). -You can also create assets in the FlexMeasures UI. +You can also create assets in the CLI (``flexmeasures add asset``), but that is also possible in the FlexMeasures UI. -On the command line, you can add many things. Check what data you can add yourself: +Actually, you can add many things from the terminal. Check what data you can add yourself: .. code-block:: bash diff --git a/documentation/host/installation.rst b/documentation/host/installation.rst index 890ee83030..aff3449050 100644 --- a/documentation/host/installation.rst +++ b/documentation/host/installation.rst @@ -222,7 +222,7 @@ FlexMeasures is also a web-based platform, so we need to create a user to authen Add initial structure ^^^^^^^^^^^^^^^^^^^^^^^ -Populate the database with some standard asset types, user roles etc.: +Populate the database with some standard asset types, user roles, data sources etc.: .. code-block:: bash diff --git a/flexmeasures/api/v3_0/__init__.py b/flexmeasures/api/v3_0/__init__.py index 42800be61a..d646c7b6c4 100644 --- a/flexmeasures/api/v3_0/__init__.py +++ b/flexmeasures/api/v3_0/__init__.py @@ -7,7 +7,7 @@ from flexmeasures.api.v3_0.sensors import SensorAPI from flexmeasures.api.v3_0.accounts import AccountAPI from flexmeasures.api.v3_0.users import UserAPI -from flexmeasures.api.v3_0.assets import AssetAPI +from flexmeasures.api.v3_0.assets import AssetAPI, AssetTypesAPI from flexmeasures.api.v3_0.health import HealthAPI from flexmeasures.api.v3_0.public import ServicesAPI @@ -21,5 +21,6 @@ def register_at(app: Flask): AccountAPI.register(app, route_prefix=v3_0_api_prefix) UserAPI.register(app, route_prefix=v3_0_api_prefix) AssetAPI.register(app, route_prefix=v3_0_api_prefix) + AssetTypesAPI.register(app, route_prefix=v3_0_api_prefix) HealthAPI.register(app, route_prefix=v3_0_api_prefix) ServicesAPI.register(app) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index cab3c4ead4..ed0c340ca8 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -26,12 +26,13 @@ from flexmeasures.data import db from flexmeasures.data.models.user import Account from flexmeasures.data.models.audit_log import AssetAuditLog -from flexmeasures.data.models.generic_assets import GenericAsset +from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType from flexmeasures.data.queries.generic_assets import query_assets_by_search_terms from flexmeasures.data.schemas import AwareDateTimeField from flexmeasures.data.schemas.generic_assets import ( GenericAssetSchema as AssetSchema, GenericAssetIdField as AssetIdField, + GenericAssetTypeSchema as AssetTypeSchema, ) from flexmeasures.data.schemas.scheduling import AssetTriggerSchema from flexmeasures.data.services.scheduling import ( @@ -56,6 +57,7 @@ from flexmeasures.utils.time_utils import naturalized_datetime_str from flexmeasures.data.utils import get_downsample_function_and_value +asset_type_schema = AssetTypeSchema() asset_schema = AssetSchema() assets_schema = AssetSchema(many=True) sensor_schema = SensorSchema() @@ -75,6 +77,42 @@ def get_accessible_accounts() -> list[Account]: return accounts +class AssetTypesAPI(FlaskView): + """ + This API view exposes generic asset types. + """ + + route_base = "/assets/types" + trailing_slash = False + decorators = [auth_required()] + + @route("", methods=["GET"]) + @as_json + def index(self): + """List all asset types available on this platform. + + .. :quickref: Asset; Get list of available asset types + + **Example response** + + An example of one asset type being returned in the response: + + .. sourcecode:: json + + [ + { + "id": 1, + "name": "solar", + "description": "solar panel(s)", + } + ] + """ + response = asset_type_schema.dump( + db.session.scalars(select(GenericAssetType)).all(), many=True + ) + return response, 200 + + class AssetAPI(FlaskView): """ This API view exposes generic assets. diff --git a/flexmeasures/api/v3_0/health.py b/flexmeasures/api/v3_0/health.py index 4efaacb128..d7875c5d0c 100644 --- a/flexmeasures/api/v3_0/health.py +++ b/flexmeasures/api/v3_0/health.py @@ -47,8 +47,8 @@ def is_ready(self): .. sourcecode:: json { - 'database_sql': True, - 'database_redis': False + "database_sql": True, + "database_redis": False } """ diff --git a/flexmeasures/api/v3_0/tests/test_assets_api.py b/flexmeasures/api/v3_0/tests/test_assets_api.py index 20f053fb40..c6733372bf 100644 --- a/flexmeasures/api/v3_0/tests/test_assets_api.py +++ b/flexmeasures/api/v3_0/tests/test_assets_api.py @@ -12,6 +12,22 @@ from flexmeasures.utils.unit_utils import is_valid_unit +@pytest.mark.parametrize( + "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True +) +def test_get_asset_types( + client, setup_api_test_data, setup_roles_users, requesting_user +): + get_asset_types_response = client.get(url_for("AssetTypesAPI:index")) + print("Server responded with:\n%s" % get_asset_types_response.json) + assert get_asset_types_response.status_code == 200 + assert isinstance(get_asset_types_response.json, list) + assert len(get_asset_types_response.json) > 0 + assert isinstance(get_asset_types_response.json[0], dict) + for key in ("id", "name", "description"): + assert key in get_asset_types_response.json[0].keys() + + @pytest.mark.parametrize( "requesting_user, status_code", [ diff --git a/flexmeasures/data/scripts/data_gen.py b/flexmeasures/data/scripts/data_gen.py index 2e71a1f667..f4efdda6c9 100644 --- a/flexmeasures/data/scripts/data_gen.py +++ b/flexmeasures/data/scripts/data_gen.py @@ -39,13 +39,13 @@ def add_default_data_sources(db: SQLAlchemy): ("Seita", "forecaster"), ("Seita", "scheduler"), ): - source = db.session.execute( + sources = db.session.execute( select(DataSource).filter( and_(DataSource.name == source_name, DataSource.type == source_type) ) - ).scalar_one_or_none() - if source: - click.echo(f"Source {source_name} ({source_type}) already exists.") + ).scalar() + if sources: + click.echo(f"A source {source_name} ({source_type}) already exists.") else: db.session.add(DataSource(name=source_name, type=source_type)) @@ -101,7 +101,7 @@ def add_default_user_roles(db: SQLAlchemy): select(Role).filter_by(name=role_name) ).scalar_one_or_none() if role: - click.echo(f"Role {role_name} already exists.") + click.echo(f"User role {role_name} already exists.") else: db.session.add(Role(name=role_name, description=role_description))