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.
Files changed (30) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/data/benchmarks/gecco_water_quality.py +2 -2
  3. epyt_flow/data/benchmarks/leakdb.py +40 -5
  4. epyt_flow/data/benchmarks/water_usage.py +4 -3
  5. epyt_flow/gym/__init__.py +0 -3
  6. epyt_flow/gym/scenario_control_env.py +3 -10
  7. epyt_flow/rest_api/scenario/control_handlers.py +118 -0
  8. epyt_flow/rest_api/scenario/event_handlers.py +114 -1
  9. epyt_flow/rest_api/scenario/handlers.py +33 -0
  10. epyt_flow/rest_api/server.py +14 -2
  11. epyt_flow/simulation/backend/__init__.py +1 -0
  12. epyt_flow/simulation/backend/my_epyt.py +1056 -0
  13. epyt_flow/simulation/events/quality_events.py +3 -1
  14. epyt_flow/simulation/scada/scada_data.py +201 -12
  15. epyt_flow/simulation/scenario_simulator.py +142 -59
  16. epyt_flow/topology.py +8 -7
  17. epyt_flow/utils.py +30 -0
  18. epyt_flow/visualization/scenario_visualizer.py +159 -69
  19. epyt_flow/visualization/visualization_utils.py +144 -17
  20. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/METADATA +4 -4
  21. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/RECORD +24 -27
  22. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/WHEEL +1 -1
  23. epyt_flow/gym/control_gyms.py +0 -55
  24. epyt_flow/metrics.py +0 -471
  25. epyt_flow/models/__init__.py +0 -2
  26. epyt_flow/models/event_detector.py +0 -36
  27. epyt_flow/models/sensor_interpolation_detector.py +0 -123
  28. epyt_flow/simulation/scada/advanced_control.py +0 -138
  29. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info/licenses}/LICENSE +0 -0
  30. {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.11.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(y_pred, y)
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(y_pred, y_true)
138
- tpr = true_positive_rate(y_pred, y_true)
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(y_pred, y), "precision": precision_score(y_pred, y),
36
- "f1-micro": f1_micro_score(y_pred, y), "roc-auc": roc_auc_score(y_pred, y)}
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
@@ -1,4 +1 @@
1
1
  from .scenario_control_env import *
2
- from .control_gyms import make, register
3
-
4
- # TODO: Register default environments
@@ -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
- if self._scenario_sim is not None:
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.
@@ -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.event_handlers import ScenarioLeakageHandler, ScenarioSensorFaultHandler
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 *