NREL-erad 0.0.0a0__py3-none-any.whl → 0.1.1__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 (73) hide show
  1. erad/__init__.py +1 -0
  2. erad/constants.py +80 -11
  3. erad/default_fragility_curves/__init__.py +15 -0
  4. erad/default_fragility_curves/default_fire_boundary_dist.py +94 -0
  5. erad/default_fragility_curves/default_flood_depth.py +108 -0
  6. erad/default_fragility_curves/default_flood_velocity.py +101 -0
  7. erad/default_fragility_curves/default_fragility_curves.py +23 -0
  8. erad/default_fragility_curves/default_peak_ground_acceleration.py +163 -0
  9. erad/default_fragility_curves/default_peak_ground_velocity.py +94 -0
  10. erad/default_fragility_curves/default_wind_speed.py +94 -0
  11. erad/enums.py +40 -0
  12. erad/gdm_mapping.py +83 -0
  13. erad/models/__init__.py +1 -0
  14. erad/models/asset.py +300 -0
  15. erad/models/asset_mapping.py +20 -0
  16. erad/models/edit_store.py +22 -0
  17. erad/models/fragility_curve.py +116 -0
  18. erad/models/hazard/__init__.py +5 -0
  19. erad/models/hazard/base_models.py +12 -0
  20. erad/models/hazard/common.py +26 -0
  21. erad/models/hazard/earthquake.py +93 -0
  22. erad/models/hazard/flood.py +83 -0
  23. erad/models/hazard/wild_fire.py +121 -0
  24. erad/models/hazard/wind.py +143 -0
  25. erad/models/probability.py +76 -0
  26. erad/probability_builder.py +38 -0
  27. erad/quantities.py +31 -0
  28. erad/runner.py +125 -0
  29. erad/systems/__init__.py +2 -0
  30. erad/systems/asset_system.py +462 -0
  31. erad/systems/hazard_system.py +122 -0
  32. nrel_erad-0.1.1.dist-info/METADATA +61 -0
  33. nrel_erad-0.1.1.dist-info/RECORD +36 -0
  34. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/WHEEL +1 -1
  35. NREL_erad-0.0.0a0.dist-info/METADATA +0 -61
  36. NREL_erad-0.0.0a0.dist-info/RECORD +0 -42
  37. erad/cypher_queries/load_data_v1.cypher +0 -212
  38. erad/data/World_Earthquakes_1960_2016.csv +0 -23410
  39. erad/db/__init__.py +0 -0
  40. erad/db/assets/__init__.py +0 -0
  41. erad/db/assets/critical_infras.py +0 -171
  42. erad/db/assets/distribution_lines.py +0 -101
  43. erad/db/credential_model.py +0 -20
  44. erad/db/disaster_input_model.py +0 -23
  45. erad/db/inject_earthquake.py +0 -52
  46. erad/db/inject_flooding.py +0 -53
  47. erad/db/neo4j_.py +0 -162
  48. erad/db/utils.py +0 -14
  49. erad/exceptions.py +0 -68
  50. erad/metrics/__init__.py +0 -0
  51. erad/metrics/check_microgrid.py +0 -208
  52. erad/metrics/metric.py +0 -178
  53. erad/programs/__init__.py +0 -0
  54. erad/programs/backup.py +0 -62
  55. erad/programs/microgrid.py +0 -45
  56. erad/scenarios/__init__.py +0 -0
  57. erad/scenarios/abstract_scenario.py +0 -103
  58. erad/scenarios/common.py +0 -93
  59. erad/scenarios/earthquake_scenario.py +0 -161
  60. erad/scenarios/fire_scenario.py +0 -160
  61. erad/scenarios/flood_scenario.py +0 -494
  62. erad/scenarios/utilities.py +0 -76
  63. erad/scenarios/wind_scenario.py +0 -89
  64. erad/utils/__init__.py +0 -0
  65. erad/utils/ditto_utils.py +0 -252
  66. erad/utils/hifld_utils.py +0 -147
  67. erad/utils/opendss_utils.py +0 -357
  68. erad/utils/overpass.py +0 -76
  69. erad/utils/util.py +0 -178
  70. erad/visualization/__init__.py +0 -0
  71. erad/visualization/plot_graph.py +0 -218
  72. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info/licenses}/LICENSE.txt +0 -0
  73. {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ from functools import cached_property
2
+ from typing import Literal
3
+ from pathlib import Path
4
+
5
+
6
+ from infrasys import Component, BaseQuantity
7
+ from scipy.stats import _continuous_distns
8
+ from pydantic import field_validator
9
+ import plotly.express as px
10
+ import numpy as np
11
+
12
+ from erad.probability_builder import ProbabilityFunctionBuilder
13
+ from erad.models.asset import AssetState
14
+ from erad.enums import AssetTypes
15
+ from erad.quantities import Speed
16
+
17
+
18
+ FRAGILITY_CURVE_TYPES = [
19
+ fc for fc in AssetState.model_fields if fc not in ["name", "uuid", "timestamp"]
20
+ ]
21
+ SUPPORTED_CONT_DIST = [name for name in dir(_continuous_distns) if not name.startswith("_")]
22
+
23
+
24
+ class ProbabilityFunction(Component):
25
+ name: str = ""
26
+ distribution: Literal[*SUPPORTED_CONT_DIST]
27
+ parameters: list[float | BaseQuantity]
28
+
29
+ @field_validator("parameters")
30
+ def validate_parameters(cls, value):
31
+ if not any(isinstance(v, BaseQuantity) for v in value):
32
+ raise ValueError("There should be atleast one BaseQuantity in the parameters")
33
+
34
+ units = set([v.units for v in value if isinstance(v, BaseQuantity)])
35
+ if not len(units) == 1:
36
+ raise ValueError("All BaseQuantities should have the same units")
37
+ return value
38
+
39
+ @cached_property
40
+ def prob_model(self) -> ProbabilityFunctionBuilder:
41
+ return ProbabilityFunctionBuilder(self.distribution, self.parameters)
42
+
43
+ @classmethod
44
+ def example(cls) -> "ProbabilityFunction":
45
+ return ProbabilityFunction(
46
+ distribution="norm",
47
+ parameters=[Speed(1.5, "m/s"), 2],
48
+ )
49
+
50
+
51
+ class FragilityCurve(Component):
52
+ name: str = ""
53
+ asset_type: AssetTypes
54
+ prob_function: ProbabilityFunction
55
+
56
+ @classmethod
57
+ def example(cls) -> "FragilityCurve":
58
+ return FragilityCurve(
59
+ asset_type=AssetTypes.substation,
60
+ prob_function=ProbabilityFunction.example(),
61
+ )
62
+
63
+
64
+ class HazardFragilityCurves(Component):
65
+ name: str = "DEFAULT_CURVES"
66
+ asset_state_param: Literal[*FRAGILITY_CURVE_TYPES]
67
+ curves: list[FragilityCurve]
68
+
69
+ @classmethod
70
+ def example(cls) -> "HazardFragilityCurves":
71
+ return HazardFragilityCurves(
72
+ asset_state_param="peak_ground_acceleration",
73
+ curves=[FragilityCurve.example()],
74
+ )
75
+
76
+ def plot(
77
+ self, file_path: Path, x_min: float = 0, x_max: float = None, number_of_points: int = 100
78
+ ):
79
+ """Plot the fragility curves."""
80
+ file_path = Path(file_path)
81
+ assert file_path.suffix.lower() == ".html", "File path should be an HTML file"
82
+
83
+ if not self.curves:
84
+ raise ValueError("No curves to plot")
85
+
86
+ quantities = [
87
+ p for p in self.curves[0].prob_function.parameters if isinstance(p, BaseQuantity)
88
+ ]
89
+
90
+ x_label = self.asset_state_param.replace("_", " ").title()
91
+ x = np.linspace(x_min, x_max, number_of_points)
92
+
93
+ plot_data = {"x": [], "y": [], "Asset Type": []}
94
+
95
+ for curve in self.curves:
96
+ model_class = quantities[0].__class__
97
+ units = quantities[0].units
98
+ prob_model = curve.prob_function.prob_model
99
+ y = prob_model.probability(model_class(x, units))
100
+ label = curve.asset_type.name.replace("_", " ").title()
101
+
102
+ plot_data["x"].extend(x)
103
+ plot_data["y"].extend(y)
104
+ plot_data["Asset Type"].extend([label] * len(x))
105
+
106
+ fig = px.line(
107
+ plot_data,
108
+ x="x",
109
+ y="y",
110
+ color="Asset Type",
111
+ labels={"x": f"{x_label} [{units}]", "y": "Probability of Failure"},
112
+ title=f"Fragility Curves for {x_label}",
113
+ )
114
+
115
+ fig.show()
116
+ fig.write_html(file_path)
@@ -0,0 +1,5 @@
1
+ from erad.models.hazard.base_models import BaseDisasterModel
2
+ from erad.models.hazard.earthquake import EarthQuakeModel
3
+ from erad.models.hazard.wild_fire import FireModel, FireModelArea
4
+ from erad.models.hazard.wind import WindModel
5
+ from erad.models.hazard.flood import FloodModel, FloodModelArea
@@ -0,0 +1,12 @@
1
+ import plotly.graph_objects as go
2
+ from pydantic import ConfigDict
3
+ from infrasys import Component
4
+
5
+
6
+ class BaseDisasterModel(Component):
7
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
8
+
9
+ def plot(
10
+ self, time_index: int, figure: go.Figure, map_obj: go.Scattergeo | go.Scattermap
11
+ ) -> int:
12
+ raise NotImplementedError("This method should be implemented in the derived classes")
@@ -0,0 +1,26 @@
1
+ from pathlib import Path
2
+
3
+ from loguru import logger
4
+ import requests
5
+
6
+
7
+ TEST_PATH = Path(__file__).parents[4] / "tests"
8
+ DATA_FOLDER_NAME = "data"
9
+ DATA_FOLDER = TEST_PATH / DATA_FOLDER_NAME
10
+ DB_FILENAME = "erad_data.sqlite"
11
+ ERAD_DB = TEST_PATH / DATA_FOLDER_NAME / DB_FILENAME
12
+ BUCKET_NAME = "erad_v2_dataset"
13
+
14
+ if not ERAD_DB.exists():
15
+ logger.info("Erad database not found. Downloading from Google Cloud.")
16
+ ERAD_DB.parent.mkdir(parents=True, exist_ok=True)
17
+ url = f"https://storage.googleapis.com/{BUCKET_NAME}/{DB_FILENAME}"
18
+ response = requests.get(url)
19
+
20
+ with open(ERAD_DB, "wb") as f:
21
+ f.write(response.content)
22
+ logger.info("Download complete...")
23
+
24
+ HISTROIC_EARTHQUAKE_TABLE = "historic_earthquakes"
25
+ HISTROIC_HURRICANE_TABLE = "historic_hurricanes"
26
+ HISTROIC_FIRE_TABLE = "historic_fires"
@@ -0,0 +1,93 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from pydantic import field_serializer, field_validator
6
+ from gdm.quantities import Distance
7
+ from shapely.geometry import Point
8
+ import plotly.graph_objects as go
9
+ import pandas as pd
10
+
11
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_EARTHQUAKE_TABLE
12
+ from erad.models.hazard.base_models import BaseDisasterModel
13
+
14
+
15
+ class EarthQuakeModel(BaseDisasterModel):
16
+ timestamp: datetime
17
+ origin: Point
18
+ depth: Distance
19
+ magnitude: float
20
+
21
+ @field_validator("origin", mode="before")
22
+ def deserialize_point(cls, value):
23
+ if isinstance(value, dict) and value.get("type") == "Point":
24
+ coords = value["coordinates"]
25
+ return Point(coords)
26
+ return value
27
+
28
+ @field_serializer("origin")
29
+ def serialize_location(self, point: Point, _info):
30
+ return {"type": "Point", "coordinates": (point.x, point.y)}
31
+
32
+ @classmethod
33
+ def example(cls) -> "EarthQuakeModel":
34
+ return EarthQuakeModel(
35
+ name="earthquake 1",
36
+ timestamp=datetime.now(),
37
+ origin=Point(-120.93036, 36.60144),
38
+ depth=Distance(300, "km"),
39
+ magnitude=5.0,
40
+ )
41
+
42
+ @classmethod
43
+ def from_earthquake_code(cls, earthquake_code: str) -> "EarthQuakeModel":
44
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
45
+
46
+ conn = sqlite3.connect(ERAD_DB)
47
+ earthquake_data = pd.read_sql(
48
+ f"SELECT * FROM {HISTROIC_EARTHQUAKE_TABLE} WHERE ID = '{earthquake_code}'", conn
49
+ )
50
+ conn.close()
51
+
52
+ assert not earthquake_data.empty, f"No earthquake {earthquake_code} found in the database"
53
+
54
+ earthquake_data["Date"] = pd.to_datetime(earthquake_data["Date"])
55
+ earthquake_data["Time"] = pd.to_datetime(earthquake_data["Time"])
56
+ earthquake_data["DateTime"] = earthquake_data.apply(
57
+ lambda row: datetime.combine(row["Date"].date(), row["Time"].time()), axis=1
58
+ )
59
+
60
+ long = earthquake_data.Longitude.values[0]
61
+ lat = earthquake_data.Latitude.values[0]
62
+
63
+ return cls(
64
+ name=earthquake_code,
65
+ timestamp=earthquake_data.DateTime.values[0].astype("datetime64[ms]").astype(datetime),
66
+ origin=Point(long, lat),
67
+ depth=Distance(earthquake_data.Depth.values[0], "km"),
68
+ magnitude=earthquake_data.Magnitude.values[0],
69
+ )
70
+
71
+ def plot(
72
+ self,
73
+ time_index: int = 0,
74
+ figure: go.Figure = go.Figure(),
75
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
76
+ ) -> int:
77
+ figure.add_trace(
78
+ map_obj(
79
+ lat=[self.origin.y],
80
+ lon=[self.origin.x],
81
+ mode="markers",
82
+ marker=dict(size=[self.magnitude * 10], color=[self.depth.magnitude], opacity=0.4),
83
+ name="Earthquake",
84
+ hovertext=[
85
+ f"""
86
+ <br> <b>Earthquake depth:</b> {self.depth}
87
+ <br> <b>Earthquake magnitude:</b> {self.magnitude}
88
+ """
89
+ ],
90
+ visible=(time_index == 0),
91
+ )
92
+ )
93
+ return 1
@@ -0,0 +1,83 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import field_serializer, field_validator
4
+ from shapely.geometry import Polygon, Point
5
+ from gdm.quantities import Distance
6
+ import plotly.graph_objects as go
7
+ from infrasys import Component
8
+
9
+ from erad.models.hazard.base_models import BaseDisasterModel
10
+ from erad.quantities import Speed
11
+
12
+
13
+ class FloodModelArea(Component):
14
+ name: str = ""
15
+ affected_area: Polygon
16
+ water_velocity: Speed
17
+ water_elevation: Distance
18
+
19
+ @field_validator("affected_area", mode="before")
20
+ def deserialize_polygon(cls, value):
21
+ if isinstance(value, dict) and value.get("type") == "Polygon":
22
+ points = [Point(c) for c in value["coordinates"]]
23
+ return Polygon(points)
24
+ return value
25
+
26
+ @field_serializer("affected_area")
27
+ def serialize_polygon(self, poly: Polygon, _info):
28
+ return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
29
+
30
+ @classmethod
31
+ def example(cls) -> "FloodModelArea":
32
+ return FloodModelArea(
33
+ affected_area=Polygon(
34
+ [
35
+ (-120.93036, 36.60144),
36
+ (-120.91072, 36.60206),
37
+ (-120.91127, 36.5712),
38
+ (-120.93405, 36.58100),
39
+ ]
40
+ ),
41
+ water_velocity=Speed(50, "meter/second"),
42
+ water_elevation=Distance(10, "feet"),
43
+ )
44
+
45
+
46
+ class FloodModel(BaseDisasterModel):
47
+ timestamp: datetime
48
+ affected_areas: list[FloodModelArea]
49
+
50
+ @classmethod
51
+ def example(cls) -> "FloodModel":
52
+ return FloodModel(
53
+ name="flood 1",
54
+ timestamp=datetime.now(),
55
+ affected_areas=[FloodModelArea.example()],
56
+ )
57
+
58
+ def plot(
59
+ self,
60
+ time_index: int = 0,
61
+ figure: go.Figure = go.Figure(),
62
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
63
+ ) -> int:
64
+ for area in self.affected_areas:
65
+ lon, lat = area.affected_area.exterior.xy # returns x and y sequences
66
+ figure.add_trace(
67
+ map_obj(
68
+ lon=lon.tolist(),
69
+ lat=lat.tolist(),
70
+ mode="markers+lines+text",
71
+ fill="toself",
72
+ marker={
73
+ "size": 10,
74
+ "color": [area.water_elevation.magnitude],
75
+ },
76
+ hovertemplate=f"""
77
+ <br> <b>Water velocity:</b> {area.water_velocity}
78
+ <br> <b>Water elevation:</b> {area.water_elevation}
79
+ """,
80
+ visible=(time_index == 0),
81
+ )
82
+ )
83
+ return len(self.affected_areas)
@@ -0,0 +1,121 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from shapely.geometry import MultiPolygon, Polygon, Point
6
+ from pydantic import field_serializer, field_validator
7
+ import plotly.graph_objects as go
8
+ from gdm.quantities import Angle
9
+ from infrasys import Component
10
+ from shapely import wkb
11
+ import pandas as pd
12
+
13
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_FIRE_TABLE
14
+ from erad.models.hazard.base_models import BaseDisasterModel
15
+ from erad.quantities import Speed
16
+
17
+
18
+ class FireModelArea(Component):
19
+ name: str = ""
20
+ affected_area: Polygon
21
+ wind_speed: Speed
22
+ wind_direction: Angle
23
+
24
+ @field_validator("affected_area", mode="before")
25
+ def deserialize_polygon(cls, value):
26
+ if isinstance(value, dict) and value.get("type") == "Polygon":
27
+ points = [Point(c) for c in value["coordinates"]]
28
+ return Polygon(points)
29
+ return value
30
+
31
+ @field_serializer("affected_area")
32
+ def serialize_polygon(self, poly: Polygon, _info):
33
+ return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
34
+
35
+ @classmethod
36
+ def example(cls) -> "FireModelArea":
37
+ return FireModelArea(
38
+ affected_area=Polygon(
39
+ [
40
+ (-120.93036, 36.60144),
41
+ (-120.91072, 36.60206),
42
+ (-120.91127, 36.5712),
43
+ (-120.93405, 36.58100),
44
+ ]
45
+ ),
46
+ wind_speed=Speed(50, "miles/hour"),
47
+ wind_direction=Angle(45, "deg"),
48
+ )
49
+
50
+
51
+ class FireModel(BaseDisasterModel):
52
+ timestamp: datetime
53
+ affected_areas: list[FireModelArea]
54
+
55
+ @classmethod
56
+ def example(cls) -> "FireModel":
57
+ return FireModel(
58
+ name="fire 1",
59
+ timestamp=datetime.now(),
60
+ affected_areas=[FireModelArea.example()],
61
+ )
62
+
63
+ @classmethod
64
+ def from_wildfire_name(cls, wildfire_name: str) -> "FireModel":
65
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
66
+ conn = sqlite3.connect(ERAD_DB)
67
+ fire_data = pd.read_sql(
68
+ f"SELECT * FROM {HISTROIC_FIRE_TABLE} WHERE firename = '{wildfire_name}';", conn
69
+ )
70
+ if fire_data.empty:
71
+ raise ValueError(
72
+ f"Fire '{wildfire_name}' not found in table '{HISTROIC_FIRE_TABLE}' in the database"
73
+ )
74
+ conn.close()
75
+ fire_data["discoverydatetime"] = pd.to_datetime(fire_data["discoverydatetime"])
76
+ geometry: MultiPolygon = [wkb.loads(g) for g in fire_data.GEOMETRY][0]
77
+ areas = []
78
+ for i, poly in enumerate(geometry.geoms):
79
+ areas.append(
80
+ FireModelArea(
81
+ affected_area=poly,
82
+ wind_speed=Speed(-999, "miles/hour"),
83
+ wind_direction=Angle(0, "deg"),
84
+ )
85
+ )
86
+ return cls(
87
+ name=wildfire_name,
88
+ timestamp=fire_data["discoverydatetime"]
89
+ .values[0]
90
+ .astype("datetime64[ms]")
91
+ .astype(datetime),
92
+ affected_areas=areas,
93
+ )
94
+
95
+ def plot(
96
+ self,
97
+ time_index: int = 0,
98
+ figure: go.Figure = go.Figure(),
99
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
100
+ ) -> int:
101
+ for area in self.affected_areas:
102
+ lon, lat = area.affected_area.exterior.xy # returns x and y sequences
103
+ figure.add_trace(
104
+ map_obj(
105
+ lon=lon.tolist(),
106
+ lat=lat.tolist(),
107
+ mode="markers+lines+text",
108
+ fill="toself",
109
+ marker={
110
+ "size": 10,
111
+ "color": [area.wind_speed.magnitude],
112
+ },
113
+ hovertemplate=f"""
114
+ <br> <b>Wind speed:</b> {area.wind_speed}
115
+ <br> <b>Wind direction:</b> {area.wind_direction}
116
+ """,
117
+ hoverinfo="text",
118
+ visible=(time_index == 0),
119
+ )
120
+ )
121
+ return len(self.affected_areas)
@@ -0,0 +1,143 @@
1
+ from datetime import datetime
2
+ import sqlite3
3
+ import os
4
+
5
+ from pydantic import field_serializer, field_validator
6
+ from infrasys.quantities import Distance
7
+ from shapely.geometry import Point
8
+ import plotly.graph_objects as go
9
+ import geopandas as gpd
10
+ import pandas as pd
11
+
12
+ from erad.models.hazard.common import ERAD_DB, HISTROIC_HURRICANE_TABLE
13
+ from erad.models.hazard.base_models import BaseDisasterModel
14
+ from erad.quantities import Speed, Pressure
15
+
16
+
17
+ class WindModel(BaseDisasterModel):
18
+ timestamp: datetime
19
+ center: Point
20
+ max_wind_speed: Speed
21
+ radius_of_max_wind: Distance
22
+ radius_of_closest_isobar: Distance
23
+ air_pressure: Pressure
24
+
25
+ @field_validator("center", mode="before")
26
+ def deserialize_point(cls, value):
27
+ if isinstance(value, dict) and value.get("type") == "Point":
28
+ coords = value["coordinates"]
29
+ return Point(coords)
30
+ return value
31
+
32
+ @field_serializer("center")
33
+ def serialize_location(self, point: Point, _info):
34
+ return {"type": "Point", "coordinates": (point.x, point.y)}
35
+
36
+ @classmethod
37
+ def example(cls) -> "WindModel":
38
+ return WindModel(
39
+ name="hurricane 1",
40
+ timestamp=datetime.now(),
41
+ center=Point(-121.93036, 36.60144),
42
+ max_wind_speed=Speed(50, "miles/hour"),
43
+ air_pressure=Pressure(1013.25, "hPa"),
44
+ radius_of_max_wind=Distance(50, "miles"),
45
+ radius_of_closest_isobar=Distance(300, "miles"),
46
+ )
47
+
48
+ @classmethod
49
+ def from_hurricane_sid(cls, hurricane_sid: str) -> list["WindModel"]:
50
+ assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
51
+ conn = sqlite3.connect(ERAD_DB)
52
+ hurricane_data = pd.read_sql(
53
+ f"SELECT * FROM {HISTROIC_HURRICANE_TABLE} WHERE `SID ` = '{hurricane_sid}';", conn
54
+ )
55
+ cols = [
56
+ "LAT (degrees_north)",
57
+ "LON (degrees_east)",
58
+ "USA_WIND (kts)",
59
+ "USA_ROCI (nmile)",
60
+ "USA_RMW (nmile)",
61
+ "USA_POCI (mb)",
62
+ "ISO_TIME ",
63
+ ]
64
+ hurricane_data = hurricane_data[cols]
65
+ for col in cols:
66
+ hurricane_data = hurricane_data[hurricane_data[col] != " "]
67
+ if hurricane_data.empty:
68
+ raise ValueError(
69
+ f"Hurricane '{hurricane_sid}' not found in column 'SID', table '{HISTROIC_HURRICANE_TABLE}' in the database"
70
+ )
71
+ conn.close()
72
+ geometry = [
73
+ Point(lat, lon)
74
+ for lat, lon in zip(
75
+ hurricane_data["LAT (degrees_north)"], hurricane_data["LON (degrees_east)"]
76
+ )
77
+ ]
78
+ hurricane_data["ISO_TIME "] = pd.to_datetime(hurricane_data["ISO_TIME "])
79
+ hurricane_data = gpd.GeoDataFrame(hurricane_data, geometry=geometry)
80
+ hurricane_data.set_crs("epsg:4326")
81
+ track = []
82
+ for idx, row in hurricane_data.iterrows():
83
+ track.append(
84
+ WindModel(
85
+ name=hurricane_sid,
86
+ timestamp=row["ISO_TIME "],
87
+ center=row["geometry"],
88
+ max_wind_speed=Speed(float(row["USA_WIND (kts)"]), "knots"),
89
+ radius_of_max_wind=Distance(float(row["USA_RMW (nmile)"]), "nautical_mile"),
90
+ radius_of_closest_isobar=Distance(
91
+ float(row["USA_ROCI (nmile)"]), "nautical_mile"
92
+ ),
93
+ air_pressure=Pressure(float(row["USA_POCI (mb)"]), "millibar"),
94
+ )
95
+ )
96
+
97
+ return track
98
+
99
+ def plot(
100
+ self,
101
+ time_index: int = 0,
102
+ figure: go.Figure = go.Figure(),
103
+ map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
104
+ ) -> int:
105
+ figure.add_trace(
106
+ map_obj(
107
+ lat=[self.center.x],
108
+ lon=[self.center.y],
109
+ mode="markers",
110
+ marker=dict(
111
+ size=[self.radius_of_closest_isobar.magnitude / 5],
112
+ color="lightblue",
113
+ opacity=0.4,
114
+ ),
115
+ name="Radius of closest isobar", # Name for the legend
116
+ visible=(time_index == 0),
117
+ )
118
+ )
119
+
120
+ figure.add_trace(
121
+ map_obj(
122
+ lat=[self.center.x],
123
+ lon=[self.center.y],
124
+ mode="markers",
125
+ marker=dict(
126
+ size=[self.radius_of_max_wind.magnitude / 5],
127
+ color=[self.max_wind_speed.magnitude],
128
+ showscale=False,
129
+ opacity=0.4,
130
+ ),
131
+ visible=(time_index == 0),
132
+ hovertext=[
133
+ f"""
134
+ <br> <b>Max wind speed:</b> {self.max_wind_speed}
135
+ <br> <b>Radius of max wind speed:</b> {self.radius_of_max_wind}
136
+ <br> <b>Radius of closest isobar:</b> {self.radius_of_closest_isobar}
137
+ <br> <b>Air pressure:</b> {self.air_pressure}
138
+ """
139
+ ],
140
+ name="Radius of max wind", # Name for the legend
141
+ )
142
+ )
143
+ return 2
@@ -0,0 +1,76 @@
1
+ from typing import Annotated
2
+
3
+ from gdm.quantities import Distance
4
+ from infrasys import Component
5
+ from pydantic import Field
6
+ from abc import ABC
7
+
8
+ from erad.quantities import Speed, Acceleration, Temperature
9
+
10
+
11
+ class BaseProbabilityModel(Component, ABC):
12
+ name: str = ""
13
+ survival_probability: Annotated[
14
+ float,
15
+ Field(1.0, ge=0, le=1, description="Asset survival probability"),
16
+ ]
17
+
18
+
19
+ class SpeedProbability(BaseProbabilityModel):
20
+ speed: Annotated[
21
+ Speed,
22
+ Field(
23
+ ...,
24
+ description="Represents the speed of a scenario parameter experienced by the asset e.g speed of wind",
25
+ ),
26
+ ]
27
+
28
+ @classmethod
29
+ def example(cls) -> "SpeedProbability":
30
+ return SpeedProbability(
31
+ speed=Speed(50, "m/s"),
32
+ survival_probability=1.0,
33
+ )
34
+
35
+
36
+ class TemperatureProbability(BaseProbabilityModel):
37
+ temperature: Annotated[
38
+ Temperature,
39
+ Field(..., description="Temperature of the asset"),
40
+ ]
41
+
42
+ @classmethod
43
+ def example(cls) -> "TemperatureProbability":
44
+ return TemperatureProbability(
45
+ temperature=Temperature(0, "degC"),
46
+ survival_probability=1.0,
47
+ )
48
+
49
+
50
+ class DistanceProbability(BaseProbabilityModel):
51
+ distance: Annotated[
52
+ Distance,
53
+ Field(
54
+ Distance(-9999, "m"),
55
+ description="Distance of asset from the source / boundary of a disaster event",
56
+ ),
57
+ ]
58
+
59
+ @classmethod
60
+ def example(cls) -> "DistanceProbability":
61
+ return DistanceProbability(
62
+ distance=Distance(0, "m"),
63
+ survival_probability=1.0,
64
+ )
65
+
66
+
67
+ class AccelerationProbability(BaseProbabilityModel):
68
+ name: str = ""
69
+ acceleration: Acceleration = Acceleration(0, "m/s**2")
70
+
71
+ @classmethod
72
+ def example(cls) -> "AccelerationProbability":
73
+ return AccelerationProbability(
74
+ acceleration=Acceleration(0, "m/s**2"),
75
+ survival_probability=1.0,
76
+ )