epyt-flow 0.11.0__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/VERSION +1 -1
- epyt_flow/data/benchmarks/gecco_water_quality.py +2 -2
- epyt_flow/data/benchmarks/leakdb.py +40 -5
- epyt_flow/data/benchmarks/water_usage.py +4 -3
- epyt_flow/gym/__init__.py +0 -3
- epyt_flow/gym/scenario_control_env.py +3 -10
- epyt_flow/rest_api/scenario/control_handlers.py +118 -0
- epyt_flow/rest_api/scenario/event_handlers.py +114 -1
- epyt_flow/rest_api/scenario/handlers.py +33 -0
- epyt_flow/rest_api/server.py +14 -2
- epyt_flow/simulation/backend/__init__.py +1 -0
- epyt_flow/simulation/backend/my_epyt.py +1056 -0
- epyt_flow/simulation/events/quality_events.py +3 -1
- epyt_flow/simulation/scada/scada_data.py +201 -12
- epyt_flow/simulation/scenario_simulator.py +142 -59
- epyt_flow/topology.py +8 -7
- epyt_flow/utils.py +30 -0
- epyt_flow/visualization/scenario_visualizer.py +159 -69
- epyt_flow/visualization/visualization_utils.py +144 -17
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/METADATA +4 -4
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/RECORD +24 -27
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/WHEEL +1 -1
- epyt_flow/gym/control_gyms.py +0 -55
- epyt_flow/metrics.py +0 -471
- epyt_flow/models/__init__.py +0 -2
- epyt_flow/models/event_detector.py +0 -36
- epyt_flow/models/sensor_interpolation_detector.py +0 -123
- epyt_flow/simulation/scada/advanced_control.py +0 -138
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info/licenses}/LICENSE +0 -0
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/top_level.txt +0 -0
epyt_flow/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.12.0
|
|
@@ -17,9 +17,9 @@ import os
|
|
|
17
17
|
from typing import Union
|
|
18
18
|
import numpy as np
|
|
19
19
|
import pandas as pd
|
|
20
|
+
from sklearn.metrics import f1_score
|
|
20
21
|
|
|
21
22
|
from ...utils import get_temp_folder, download_if_necessary
|
|
22
|
-
from ...metrics import f1_score
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
@@ -41,7 +41,7 @@ def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
|
41
41
|
`float`
|
|
42
42
|
Evaluation score.
|
|
43
43
|
"""
|
|
44
|
-
return f1_score(
|
|
44
|
+
return f1_score(y, y_pred)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def load_gecco2017_water_quality_data(download_dir: str = None, return_X_y: bool = True,
|
|
@@ -13,8 +13,6 @@ This module provides functions for loading the original LeakDB data set
|
|
|
13
13
|
The official scoring/evaluation is implemented in
|
|
14
14
|
:func:`~epyt_flow.data.benchmarks.leakdb.compute_evaluation_score` -- i.e. those results can be
|
|
15
15
|
directly compared to the official paper.
|
|
16
|
-
Besides this, the user can choose to evaluate predictions using any other metric from
|
|
17
|
-
:mod:`~epyt_flow.metrics`.
|
|
18
16
|
"""
|
|
19
17
|
import os
|
|
20
18
|
from typing import Union
|
|
@@ -24,12 +22,12 @@ import scipy
|
|
|
24
22
|
import numpy as np
|
|
25
23
|
import pandas as pd
|
|
26
24
|
from scipy.sparse import bsr_array
|
|
25
|
+
from sklearn.metrics import f1_score, recall_score as true_positive_rate
|
|
27
26
|
|
|
28
27
|
from ..networks import load_net1, load_hanoi
|
|
29
28
|
from .leakdb_data import NET1_LEAKAGES, HANOI_LEAKAGES
|
|
30
29
|
from ...utils import get_temp_folder, to_seconds, unpack_zip_archive, create_path_if_not_exist, \
|
|
31
30
|
download_if_necessary
|
|
32
|
-
from ...metrics import f1_score, true_positive_rate, true_negative_rate
|
|
33
31
|
from ...simulation import ScenarioSimulator, ToolkitConstants
|
|
34
32
|
from ...simulation.events import AbruptLeakage, IncipientLeakage
|
|
35
33
|
from ...simulation import ScenarioConfig
|
|
@@ -37,6 +35,43 @@ from ...simulation.scada import ScadaData
|
|
|
37
35
|
from ...uncertainty import ModelUncertainty, UniformUncertainty
|
|
38
36
|
|
|
39
37
|
|
|
38
|
+
def true_negative_rate(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
39
|
+
"""
|
|
40
|
+
Computes the true negative rate (also called specificity).
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
45
|
+
Predicted labels.
|
|
46
|
+
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
47
|
+
Ground truth labels.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
`float`
|
|
52
|
+
True negative rate.
|
|
53
|
+
"""
|
|
54
|
+
if not isinstance(y_pred, np.ndarray):
|
|
55
|
+
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
56
|
+
f"but not of '{type(y_pred)}'")
|
|
57
|
+
if not isinstance(y, np.ndarray):
|
|
58
|
+
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
59
|
+
f"but not of '{type(y)}'")
|
|
60
|
+
if y_pred.shape != y.shape:
|
|
61
|
+
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
62
|
+
if len(y_pred.shape) > 1:
|
|
63
|
+
raise ValueError("'y_pred' must be a 1d array")
|
|
64
|
+
if len(y.shape) > 1:
|
|
65
|
+
raise ValueError("'y' must be a 1d array")
|
|
66
|
+
if set(np.unique(y_pred)) != set([0, 1]):
|
|
67
|
+
raise ValueError("Labels must be either '0' or '1'")
|
|
68
|
+
|
|
69
|
+
tn = np.sum((y == 0) & (y_pred == 0))
|
|
70
|
+
fp = np.sum((y == 0) & (y_pred == 1))
|
|
71
|
+
|
|
72
|
+
return tn / (tn + fp)
|
|
73
|
+
|
|
74
|
+
|
|
40
75
|
def __leak_time_to_idx(t: int, round_up: bool = False, hydraulic_time_step: int = 1800):
|
|
41
76
|
if round_up is False:
|
|
42
77
|
return math.floor(t / hydraulic_time_step)
|
|
@@ -134,8 +169,8 @@ def compute_evaluation_score(scenarios_id: list[int], use_net1: bool,
|
|
|
134
169
|
y_pred = np.stack(y_pred_labels_per_scenario, axis=0)
|
|
135
170
|
|
|
136
171
|
# Evaluate predictions
|
|
137
|
-
f1 = f1_score(
|
|
138
|
-
tpr = true_positive_rate(
|
|
172
|
+
f1 = f1_score(y_true, y_pred)
|
|
173
|
+
tpr = true_positive_rate(y_true, y_pred)
|
|
139
174
|
tnr = true_negative_rate(y_pred, y_true)
|
|
140
175
|
|
|
141
176
|
early_detection_score = 0
|
|
@@ -4,9 +4,9 @@ Module provides a function for loading the water usage data set by P. Pavlou et
|
|
|
4
4
|
import os
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
|
+
from sklearn.metrics import accuracy_score, precision_score, roc_auc_score, f1_score
|
|
7
8
|
|
|
8
9
|
from ...utils import get_temp_folder, download_if_necessary
|
|
9
|
-
from ...metrics import accuracy_score, precision_score, roc_auc_score, f1_micro_score
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> dict:
|
|
@@ -32,8 +32,9 @@ def compute_evaluation_score(y_pred: np.ndarray, y: np.ndarray) -> dict:
|
|
|
32
32
|
`dict`
|
|
33
33
|
All evaluation scores.
|
|
34
34
|
"""
|
|
35
|
-
return {"accuracy": accuracy_score(
|
|
36
|
-
"
|
|
35
|
+
return {"accuracy": accuracy_score(y, y_pred),
|
|
36
|
+
"precision": precision_score(y, y_pred, average="weighted"),
|
|
37
|
+
"f1-micro": f1_score(y, y_pred, average="micro"), "roc-auc": roc_auc_score(y, y_pred)}
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def load_water_usage(download_dir: str = None, return_X_y: bool = True, verbose: bool = True) -> dict:
|
epyt_flow/gym/__init__.py
CHANGED
|
@@ -79,11 +79,13 @@ class ScenarioControlEnv(ABC):
|
|
|
79
79
|
if self._sim_generator is not None:
|
|
80
80
|
next(self._sim_generator)
|
|
81
81
|
self._sim_generator.send(True)
|
|
82
|
+
self._sim_generator = None
|
|
82
83
|
except StopIteration:
|
|
83
84
|
pass
|
|
84
85
|
|
|
85
86
|
if self._scenario_sim is not None:
|
|
86
87
|
self._scenario_sim.close()
|
|
88
|
+
self._scenario_sim = None
|
|
87
89
|
|
|
88
90
|
def contains_events(self) -> bool:
|
|
89
91
|
"""
|
|
@@ -106,16 +108,7 @@ class ScenarioControlEnv(ABC):
|
|
|
106
108
|
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
107
109
|
Current SCADA data (i.e. sensor readings).
|
|
108
110
|
"""
|
|
109
|
-
|
|
110
|
-
# Abort current simulation if any is runing
|
|
111
|
-
try:
|
|
112
|
-
next(self._sim_generator)
|
|
113
|
-
self._sim_generator.send(True)
|
|
114
|
-
except StopIteration:
|
|
115
|
-
pass
|
|
116
|
-
|
|
117
|
-
# Close scenario
|
|
118
|
-
self._scenario_sim.close()
|
|
111
|
+
self.close()
|
|
119
112
|
|
|
120
113
|
self._scenario_sim = ScenarioSimulator(
|
|
121
114
|
scenario_config=self._scenario_config)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides REST API handlers for complex and simple control modules of scenarios.
|
|
3
|
+
"""
|
|
4
|
+
import warnings
|
|
5
|
+
import falcon
|
|
6
|
+
|
|
7
|
+
from .handlers import ScenarioBaseHandler
|
|
8
|
+
from ...simulation import ComplexControlModule, SimpleControlModule
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ScenarioComplexControlHandler(ScenarioBaseHandler):
|
|
12
|
+
"""
|
|
13
|
+
Class for handling GET and POST requests concerning complex control modules.
|
|
14
|
+
"""
|
|
15
|
+
def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Gets all complex control modules of a given scenario.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
22
|
+
Response instance.
|
|
23
|
+
scenario_id : `str`
|
|
24
|
+
UUID of the scenario.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
28
|
+
self.send_invalid_resource_id_error(resp)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
my_simple_controls = self.scenario_mgr.get(scenario_id).complex_controls
|
|
32
|
+
self.send_json_response(resp, my_simple_controls)
|
|
33
|
+
except Exception as ex:
|
|
34
|
+
warnings.warn(str(ex))
|
|
35
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
36
|
+
|
|
37
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Adds a new complex control module to a given scenario.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
|
|
44
|
+
Request instance.
|
|
45
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
46
|
+
Response instance.
|
|
47
|
+
scenario_id : `str`
|
|
48
|
+
UUID of the scenario.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
52
|
+
self.send_invalid_resource_id_error(resp)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
complex_control = self.load_json_data_from_request(req)
|
|
56
|
+
if not isinstance(complex_control, ComplexControlModule):
|
|
57
|
+
self.send_json_parsing_error(resp)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
self.scenario_mgr.get(scenario_id).add_complex_control(complex_control)
|
|
61
|
+
except Exception as ex:
|
|
62
|
+
warnings.warn(str(ex))
|
|
63
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ScenarioSimpleControlHandler(ScenarioBaseHandler):
|
|
67
|
+
"""
|
|
68
|
+
Class for handling GET and POST requests concerning simple control modules.
|
|
69
|
+
"""
|
|
70
|
+
def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Gets all simple control modules of a given scenario.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
77
|
+
Response instance.
|
|
78
|
+
scenario_id : `str`
|
|
79
|
+
UUID of the scenario.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
83
|
+
self.send_invalid_resource_id_error(resp)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
my_simple_controls = self.scenario_mgr.get(scenario_id).simple_controls
|
|
87
|
+
self.send_json_response(resp, my_simple_controls)
|
|
88
|
+
except Exception as ex:
|
|
89
|
+
warnings.warn(str(ex))
|
|
90
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
91
|
+
|
|
92
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Adds a new simple control module to a given scenario.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
|
|
99
|
+
Request instance.
|
|
100
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
101
|
+
Response instance.
|
|
102
|
+
scenario_id : `str`
|
|
103
|
+
UUID of the scenario.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
107
|
+
self.send_invalid_resource_id_error(resp)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
simple_control = self.load_json_data_from_request(req)
|
|
111
|
+
if not isinstance(simple_control, SimpleControlModule):
|
|
112
|
+
self.send_json_parsing_error(resp)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
self.scenario_mgr.get(scenario_id).add_simple_control(simple_control)
|
|
116
|
+
except Exception as ex:
|
|
117
|
+
warnings.warn(str(ex))
|
|
118
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
@@ -5,7 +5,8 @@ import warnings
|
|
|
5
5
|
import falcon
|
|
6
6
|
|
|
7
7
|
from .handlers import ScenarioBaseHandler
|
|
8
|
-
from ...simulation import Leakage, SensorFault
|
|
8
|
+
from ...simulation import Leakage, SensorFault, SensorReadingAttack
|
|
9
|
+
from ...simulation.events import SpeciesInjectionEvent
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ScenarioLeakageHandler(ScenarioBaseHandler):
|
|
@@ -63,6 +64,118 @@ class ScenarioLeakageHandler(ScenarioBaseHandler):
|
|
|
63
64
|
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
class ScenarioQualityEventHandler(ScenarioBaseHandler):
|
|
68
|
+
"""
|
|
69
|
+
Class for handling GET and POST requests concerning quality events.
|
|
70
|
+
"""
|
|
71
|
+
def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Gets all quality events of a given scenario.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
78
|
+
Response instance.
|
|
79
|
+
scenario_id : `str`
|
|
80
|
+
UUID of the scenario.
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
84
|
+
self.send_invalid_resource_id_error(resp)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
my_system_events = self.scenario_mgr.get(scenario_id).system_events
|
|
88
|
+
my_quality_events = list(filter(lambda event: isinstance(event, SpeciesInjectionEvent),
|
|
89
|
+
my_system_events))
|
|
90
|
+
self.send_json_response(resp, my_quality_events)
|
|
91
|
+
except Exception as ex:
|
|
92
|
+
warnings.warn(str(ex))
|
|
93
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
94
|
+
|
|
95
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Adds a new quality event to a given scenario.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
|
|
102
|
+
Request instance.
|
|
103
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
104
|
+
Response instance.
|
|
105
|
+
scenario_id : `str`
|
|
106
|
+
UUID of the scenario.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
110
|
+
self.send_invalid_resource_id_error(resp)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
quality_event = self.load_json_data_from_request(req)
|
|
114
|
+
if not isinstance(quality_event, SpeciesInjectionEvent):
|
|
115
|
+
self.send_json_parsing_error(resp)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
self.scenario_mgr.get(scenario_id).add_system_event(quality_event)
|
|
119
|
+
except Exception as ex:
|
|
120
|
+
warnings.warn(str(ex))
|
|
121
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ScenarioSensorReadingAttackHandler(ScenarioBaseHandler):
|
|
125
|
+
"""
|
|
126
|
+
Class for handling GET and POST requests concerning sensor reading attacks.
|
|
127
|
+
"""
|
|
128
|
+
def on_get(self, _, resp: falcon.Response, scenario_id: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Gets all sensor reading attack events of a given scenario.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
135
|
+
Response instance.
|
|
136
|
+
scenario_id : `str`
|
|
137
|
+
UUID of the scenario.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
141
|
+
self.send_invalid_resource_id_error(resp)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
my_sensor_reading_attacks = self.scenario_mgr.get(scenario_id).sensor_reading_attacks
|
|
145
|
+
self.send_json_response(resp, my_sensor_reading_attacks)
|
|
146
|
+
except Exception as ex:
|
|
147
|
+
warnings.warn(str(ex))
|
|
148
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
149
|
+
|
|
150
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Adds a new sensor reading attack event to a given scenario.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
|
|
157
|
+
Request instance.
|
|
158
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
159
|
+
Response instance.
|
|
160
|
+
scenario_id : `str`
|
|
161
|
+
UUID of the scenario.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
165
|
+
self.send_invalid_resource_id_error(resp)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
sensor_reading_attack = self.load_json_data_from_request(req)
|
|
169
|
+
if not isinstance(sensor_reading_attack, SensorReadingAttack):
|
|
170
|
+
self.send_json_parsing_error(resp)
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
self.scenario_mgr.get(scenario_id).add_sensor_reading_attack(sensor_reading_attack)
|
|
174
|
+
except Exception as ex:
|
|
175
|
+
warnings.warn(str(ex))
|
|
176
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
177
|
+
|
|
178
|
+
|
|
66
179
|
class ScenarioSensorFaultHandler(ScenarioBaseHandler):
|
|
67
180
|
"""
|
|
68
181
|
Class for handling GET and POST requests concerning sensor faults.
|
|
@@ -278,6 +278,39 @@ class ScenarioGeneralParamsHandler(ScenarioBaseHandler):
|
|
|
278
278
|
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
279
279
|
|
|
280
280
|
|
|
281
|
+
class ScenarioQualityParamsHandler(ScenarioBaseHandler):
|
|
282
|
+
"""
|
|
283
|
+
Class for handling POST requests for specifying (EPANET) quality parameters of a given scenario.
|
|
284
|
+
"""
|
|
285
|
+
def on_post(self, req: falcon.Request, resp: falcon.Response, scenario_id: str) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Specifies the (EPANET) quality parameters of a given scenario.
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
req : `falcon.Request <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#request>`_
|
|
292
|
+
Request instance.
|
|
293
|
+
resp : `falcon.Response <https://falcon.readthedocs.io/en/stable/api/request_and_response_asgi.html#response>`_
|
|
294
|
+
Request instance.
|
|
295
|
+
scenario_id : `str`
|
|
296
|
+
UUID of the scenario.
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
if self.scenario_mgr.validate_uuid(scenario_id) is False:
|
|
300
|
+
self.send_invalid_resource_id_error(resp)
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
quality_params = self.load_json_data_from_request(req)
|
|
304
|
+
if not isinstance(quality_params, dict):
|
|
305
|
+
self.send_json_parsing_error(resp)
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
self.scenario_mgr.get(scenario_id).set_quality_parameters(**quality_params)
|
|
309
|
+
except Exception as ex:
|
|
310
|
+
warnings.warn(str(ex))
|
|
311
|
+
resp.status = falcon.HTTP_INTERNAL_SERVER_ERROR
|
|
312
|
+
|
|
313
|
+
|
|
281
314
|
class ScenarioSensorConfigHandler(ScenarioBaseHandler):
|
|
282
315
|
"""
|
|
283
316
|
Class for handling GET and POST requests for the sensor configuration of a given scenario.
|
epyt_flow/rest_api/server.py
CHANGED
|
@@ -7,10 +7,12 @@ import falcon
|
|
|
7
7
|
from .scenario.handlers import ScenarioManager, ScenarioNewHandler, \
|
|
8
8
|
ScenarioRemoveHandler, ScenarioGeneralParamsHandler, ScenarioSensorConfigHandler, \
|
|
9
9
|
ScenarioExportHandler, ScenarioTopologyHandler, ScenarioConfigHandler, \
|
|
10
|
-
ScenarioNodeDemandPatternHandler
|
|
10
|
+
ScenarioNodeDemandPatternHandler, ScenarioQualityParamsHandler
|
|
11
11
|
from .scenario.uncertainty_handlers import ScenarioModelUncertaintyHandler, \
|
|
12
12
|
ScenarioSensorUncertaintyHandler
|
|
13
|
-
from .scenario.
|
|
13
|
+
from .scenario.control_handlers import ScenarioSimpleControlHandler, ScenarioComplexControlHandler
|
|
14
|
+
from .scenario.event_handlers import ScenarioLeakageHandler, ScenarioSensorFaultHandler, \
|
|
15
|
+
ScenarioQualityEventHandler, ScenarioSensorReadingAttackHandler
|
|
14
16
|
from .scenario.simulation_handlers import ScenarioSimulationHandler, \
|
|
15
17
|
ScenarioBasicQualitySimulationHandler, ScenarioAdvancedQualitySimulationHandler
|
|
16
18
|
from .scada_data.handlers import ScadaDataManager, ScadaDataSensorConfigHandler, \
|
|
@@ -54,14 +56,24 @@ class RestApiService():
|
|
|
54
56
|
ScenarioConfigHandler(self.scenario_mgr))
|
|
55
57
|
self.app.add_route("/scenario/{scenario_id}/general_params",
|
|
56
58
|
ScenarioGeneralParamsHandler(self.scenario_mgr))
|
|
59
|
+
self.app.add_route("/scenario/{scenario_id}/quality_params",
|
|
60
|
+
ScenarioQualityParamsHandler(self.scenario_mgr))
|
|
57
61
|
self.app.add_route("/scenario/{scenario_id}/sensor_config",
|
|
58
62
|
ScenarioSensorConfigHandler(self.scenario_mgr))
|
|
59
63
|
self.app.add_route("/scenario/{scenario_id}/uncertainty/model",
|
|
60
64
|
ScenarioModelUncertaintyHandler(self.scenario_mgr))
|
|
61
65
|
self.app.add_route("/scenario/{scenario_id}/uncertainty/sensors",
|
|
62
66
|
ScenarioSensorUncertaintyHandler(self.scenario_mgr))
|
|
67
|
+
self.app.add_route("/scenario/{scenario_id}/controls/simple",
|
|
68
|
+
ScenarioSimpleControlHandler(self.scenario_mgr))
|
|
69
|
+
self.app.add_route("/scenario/{scenario_id}/controls/complex",
|
|
70
|
+
ScenarioComplexControlHandler(self.scenario_mgr))
|
|
63
71
|
self.app.add_route("/scenario/{scenario_id}/events/leakages",
|
|
64
72
|
ScenarioLeakageHandler(self.scenario_mgr))
|
|
73
|
+
self.app.add_route("/scenario/{scenario_id}/events/quality",
|
|
74
|
+
ScenarioQualityEventHandler(self.scenario_mgr))
|
|
75
|
+
self.app.add_route("/scenario/{scenario_id}/events/sensor_reading_attacks",
|
|
76
|
+
ScenarioSensorReadingAttackHandler(self.scenario_mgr))
|
|
65
77
|
self.app.add_route("/scenario/{scenario_id}/events/sensor_faults",
|
|
66
78
|
ScenarioSensorFaultHandler(self.scenario_mgr))
|
|
67
79
|
self.app.add_route("/scenario/{scenario_id}/node/{node_id}/demand_pattern",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .my_epyt import *
|