Skip to content

Commit db02800

Browse files
joshuaunitynhoeningCopilot
authored
feat: Cleanup of stale sensor references (#2106)
* feat: implement cleanup of stale sensor references in assets on sensor deletion Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * feat: streamline cleanup of sensor references in assets Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * feat: replace cleanup of sensor references with delete_sensor function for improved data integrity Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * feat: enhance sensor reference cleanup with detailed audit logging for improved traceability Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * Update flexmeasures/data/services/sensors.py Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: JDev <45713692+joshuaunity@users.noreply.github.com> * Update flexmeasures/api/v3_0/tests/test_sensors_api.py Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: JDev <45713692+joshuaunity@users.noreply.github.com> * feat: enhance sensor reference pruning functions for improved JSON handling and clarity Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * feat: clarify audit log messages for deleted sensor references Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * docs(sensors): fix doctest examples for prune helpers Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/0ffebf72-a172-44fc-8399-423b45337462 Co-authored-by: joshuaunity <45713692+joshuaunity@users.noreply.github.com> * feat: update variable names for clarity in sensor reference cleanup Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> * chore: add changelog entry Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> --------- Signed-off-by: joshuaunity <oghenerobojosh01@gmail.com> Signed-off-by: JDev <45713692+joshuaunity@users.noreply.github.com> Co-authored-by: Nicolas Höning <nicolas@seita.nl> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 5877d91 commit db02800

5 files changed

Lines changed: 450 additions & 22 deletions

File tree

documentation/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ New features
4444
* Separate the ``StorageScheduler``'s tie-breaking preference for a full :abbr:`SoC (state of charge)` from its reported energy costs [see `PR #2023 <https://www.github.com/FlexMeasures/flexmeasures/pull/2023>`_ and `PR #2108 <https://www.github.com/FlexMeasures/flexmeasures/pull/2108>`_]
4545
* Improve asset graph hover interaction with a vertical ruler across subcharts, while keeping hover dots for easier visual tracking [see `PR #2079 <https://www.github.com/FlexMeasures/flexmeasures/pull/2079>`_]
4646
* Improve asset audit log messages for JSON field edits (especially ``sensors_to_show`` and nested flex-config values) [see `PR #2055 <https://www.github.com/FlexMeasures/flexmeasures/pull/2055>`_]
47+
* Clean up stale sensor references from ``flex-config`` and ``sensors_to_show`` when deleting a sensor, using JSONB queries to find affected assets before pruning those references [see `PR #2106 <https://www.github.com/FlexMeasures/flexmeasures/pull/2106>`_]
4748

4849
Infrastructure / Support
4950
----------------------

flexmeasures/api/v3_0/sensors.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from flexmeasures.data.schemas.scheduling import GetScheduleSchema
6666
from flexmeasures.data.schemas.units import UnitField
6767
from flexmeasures.data.services.sensors import get_sensor_stats
68+
from flexmeasures.data.services.sensors import delete_sensor as delete_sensor_and_data
6869
from flexmeasures.data.services.scheduling import (
6970
create_scheduling_job,
7071
get_data_source_for_job,
@@ -1350,19 +1351,8 @@ def delete(self, id: int, sensor: Sensor):
13501351
- Sensors
13511352
"""
13521353

1353-
"""Delete time series data."""
1354-
db.session.execute(delete(TimedBelief).filter_by(sensor_id=sensor.id))
1355-
1356-
AssetAuditLog.add_record(
1357-
sensor.generic_asset, f"Deleted sensor '{sensor.name}': {sensor.id}"
1358-
)
1359-
13601354
sensor_name = sensor.name
1361-
AssetAuditLog.add_record(
1362-
sensor.generic_asset,
1363-
f"Deleted sensor '{sensor_name}': {id}",
1364-
)
1365-
db.session.execute(delete(Sensor).filter_by(id=sensor.id))
1355+
delete_sensor_and_data(sensor)
13661356
db.session.commit()
13671357
current_app.logger.info("Deleted sensor '%s'." % sensor_name)
13681358
return {}, 204

flexmeasures/api/v3_0/tests/test_sensors_api.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44
import math
55
import io
6+
import json
67

78
from flask import url_for
89
from sqlalchemy import select, func
@@ -536,6 +537,29 @@ def test_delete_a_sensor(client, setup_api_test_data, requesting_user, db):
536537
existing_sensor_id = existing_sensor.id
537538
sensor_count = db.session.scalar(select(func.count()).select_from(Sensor))
538539

540+
asset = existing_sensor.generic_asset
541+
asset.flex_model = {
542+
"soc-max": {"sensor": existing_sensor_id},
543+
"static-limit": "10 kW",
544+
}
545+
asset.flex_context = {
546+
"consumption-price": {"sensor": existing_sensor_id},
547+
"inflexible-device-sensors": [existing_sensor_id],
548+
}
549+
asset.sensors_to_show = [
550+
{"title": "Power", "plots": [{"sensor": existing_sensor_id}]},
551+
existing_sensor_id,
552+
]
553+
asset.sensors_to_show_as_kpis = [
554+
{
555+
"sensor": existing_sensor_id,
556+
"title": "Temperature KPI",
557+
"function": "sum",
558+
}
559+
]
560+
db.session.add(asset)
561+
db.session.commit()
562+
539563
delete_sensor_response = client.delete(
540564
url_for("SensorAPI:delete", id=existing_sensor_id),
541565
)
@@ -552,12 +576,46 @@ def test_delete_a_sensor(client, setup_api_test_data, requesting_user, db):
552576
db.session.scalar(select(func.count()).select_from(Sensor)) == sensor_count - 1
553577
)
554578

579+
asset_after = db.session.get(GenericAsset, asset.id)
580+
assert asset_after.flex_model.get("soc-max") is None
581+
assert asset_after.flex_model.get("static-limit") == "10 kW"
582+
assert asset_after.flex_context.get("consumption-price") is None
583+
assert asset_after.flex_context.get("inflexible-device-sensors") == []
584+
assert str(existing_sensor_id) not in json.dumps(asset_after.sensors_to_show)
585+
assert str(existing_sensor_id) not in json.dumps(
586+
asset_after.sensors_to_show_as_kpis
587+
)
588+
555589
check_audit_log_event(
556590
db=db,
557591
event=f"Deleted sensor '{existing_sensor.name}': {existing_sensor.id}",
558592
user=requesting_user,
559593
asset=existing_sensor.generic_asset,
560594
)
595+
check_audit_log_event(
596+
db=db,
597+
event=f"Removed sensor reference '{existing_sensor.name}': {existing_sensor.id} from flex-model (because sensor has been deleted).",
598+
user=requesting_user,
599+
asset=existing_sensor.generic_asset,
600+
)
601+
check_audit_log_event(
602+
db=db,
603+
event=f"Removed sensor reference '{existing_sensor.name}': {existing_sensor.id} from flex-context (because sensor has been deleted).",
604+
user=requesting_user,
605+
asset=existing_sensor.generic_asset,
606+
)
607+
check_audit_log_event(
608+
db=db,
609+
event=f"Removed sensor reference '{existing_sensor.name}': {existing_sensor.id} from sensors-to-show (because sensor has been deleted).",
610+
user=requesting_user,
611+
asset=existing_sensor.generic_asset,
612+
)
613+
check_audit_log_event(
614+
db=db,
615+
event=f"Removed sensor reference '{existing_sensor.name}': {existing_sensor.id} from sensors-to-show-as-kpis (because sensor has been deleted).",
616+
user=requesting_user,
617+
asset=existing_sensor.generic_asset,
618+
)
561619

562620

563621
@pytest.mark.parametrize(

flexmeasures/cli/data_delete.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
SourceIdField,
2828
)
2929
from flexmeasures.data.services.users import find_user_by_email, delete_user
30+
from flexmeasures.data.services.sensors import delete_sensor as delete_sensor_and_data
3031
from flexmeasures.cli.utils import (
3132
abort,
3233
done,
@@ -539,18 +540,17 @@ def delete_sensor(
539540
sensors: list[Sensor],
540541
):
541542
"""Delete sensors and their (time series) data."""
542-
n = delete(TimedBelief).where(
543-
TimedBelief.sensor_id.in_(sensor.id for sensor in sensors)
544-
)
545-
statements = []
546-
for sensor in sensors:
547-
statements.append(delete(Sensor).filter_by(id=sensor.id))
543+
n_beliefs = db.session.execute(
544+
select(func.count())
545+
.select_from(TimedBelief)
546+
.where(TimedBelief.sensor_id.in_([sensor.id for sensor in sensors]))
547+
).scalar_one()
548548
click.confirm(
549-
f"Delete {', '.join(sensor.__repr__() for sensor in sensors)}, along with {n} beliefs?",
549+
f"Delete {', '.join(sensor.__repr__() for sensor in sensors)}, along with {n_beliefs} beliefs?",
550550
abort=True,
551551
)
552-
for statement in statements:
553-
db.session.execute(statement)
552+
for sensor in sensors:
553+
delete_sensor_and_data(sensor)
554554
db.session.commit()
555555

556556

0 commit comments

Comments
 (0)