flood-adapt 0.3.1__py3-none-any.whl → 0.3.3__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 (43) hide show
  1. flood_adapt/__init__.py +1 -1
  2. flood_adapt/adapter/fiat_adapter.py +35 -1
  3. flood_adapt/config/config.py +58 -36
  4. flood_adapt/config/fiat.py +7 -7
  5. flood_adapt/config/gui.py +189 -82
  6. flood_adapt/config/site.py +2 -2
  7. flood_adapt/database_builder/database_builder.py +100 -65
  8. flood_adapt/dbs_classes/database.py +16 -53
  9. flood_adapt/dbs_classes/dbs_event.py +4 -1
  10. flood_adapt/dbs_classes/dbs_measure.py +7 -7
  11. flood_adapt/dbs_classes/dbs_projection.py +4 -1
  12. flood_adapt/dbs_classes/dbs_scenario.py +17 -8
  13. flood_adapt/dbs_classes/dbs_static.py +3 -5
  14. flood_adapt/dbs_classes/dbs_strategy.py +7 -5
  15. flood_adapt/dbs_classes/dbs_template.py +21 -22
  16. flood_adapt/dbs_classes/interface/database.py +0 -8
  17. flood_adapt/dbs_classes/interface/element.py +1 -1
  18. flood_adapt/flood_adapt.py +135 -273
  19. flood_adapt/objects/__init__.py +40 -17
  20. flood_adapt/objects/benefits/benefits.py +6 -6
  21. flood_adapt/objects/events/event_set.py +4 -4
  22. flood_adapt/objects/events/events.py +17 -4
  23. flood_adapt/objects/events/historical.py +11 -8
  24. flood_adapt/objects/events/hurricane.py +11 -8
  25. flood_adapt/objects/events/synthetic.py +9 -7
  26. flood_adapt/objects/forcing/forcing.py +9 -1
  27. flood_adapt/objects/forcing/plotting.py +1 -0
  28. flood_adapt/objects/forcing/tide_gauge.py +14 -14
  29. flood_adapt/objects/forcing/time_frame.py +13 -0
  30. flood_adapt/objects/measures/measures.py +38 -15
  31. flood_adapt/objects/object_model.py +2 -2
  32. flood_adapt/objects/projections/projections.py +18 -18
  33. flood_adapt/objects/strategies/strategies.py +22 -1
  34. flood_adapt/workflows/benefit_runner.py +5 -2
  35. flood_adapt/workflows/scenario_runner.py +8 -7
  36. flood_adapt-0.3.3.dist-info/LICENSE +674 -0
  37. flood_adapt-0.3.3.dist-info/METADATA +859 -0
  38. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/RECORD +41 -41
  39. flood_adapt-0.3.1.dist-info/LICENSE +0 -21
  40. flood_adapt-0.3.1.dist-info/METADATA +0 -183
  41. /flood_adapt/database_builder/templates/{mapbox_layers → output_layers}/bin_colors.toml +0 -0
  42. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/WHEEL +0 -0
  43. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/top_level.txt +0 -0
@@ -34,12 +34,16 @@ from flood_adapt.config.fiat import (
34
34
  SVIModel,
35
35
  )
36
36
  from flood_adapt.config.gui import (
37
+ AggregationDmgLayer,
38
+ BenefitsLayer,
39
+ FloodMapLayer,
40
+ FootprintsDmgLayer,
37
41
  GuiModel,
38
42
  GuiUnitModel,
39
- MapboxLayersModel,
43
+ OutputLayers,
40
44
  PlottingModel,
41
45
  SyntheticTideModel,
42
- VisualizationLayersModel,
46
+ VisualizationLayers,
43
47
  )
44
48
  from flood_adapt.config.sfincs import (
45
49
  Cstype,
@@ -379,6 +383,7 @@ class DatabaseBuilder:
379
383
 
380
384
  _has_roads: bool = False
381
385
  _aggregation_areas: Optional[list] = None
386
+ _probabilistic_set_name: Optional[str] = None
382
387
 
383
388
  def __init__(self, config: ConfigModel, overwrite: bool = True):
384
389
  self.config = config
@@ -579,19 +584,26 @@ class DatabaseBuilder:
579
584
  ### FIAT ###
580
585
  def create_fiat_model(self) -> FiatModel:
581
586
  fiat = FiatModel(
582
- risk=self.create_risk_model(),
583
587
  config=self.create_fiat_config(),
584
588
  benefits=self.create_benefit_config(),
589
+ risk=self.create_risk_model(),
585
590
  )
586
591
  return fiat
587
592
 
588
- def create_risk_model(self) -> RiskModel:
593
+ def create_risk_model(self) -> Optional[RiskModel]:
589
594
  # Check if return periods are provided
590
595
  if not self.config.return_periods:
591
- risk = RiskModel()
592
- self.logger.warning(
593
- f"Return periods for risk calculations not provided. Default values of {risk.return_periods} will be used."
594
- )
596
+ if self._probabilistic_set_name:
597
+ risk = RiskModel()
598
+ self.logger.warning(
599
+ f"No return periods provided, but a probabilistic set is available. Using default return periods {risk.return_periods}."
600
+ )
601
+ return risk
602
+ else:
603
+ self.logger.warning(
604
+ "No return periods provided and no probabilistic set available. Risk calculations will not be performed."
605
+ )
606
+ return None
595
607
  else:
596
608
  risk = RiskModel(return_periods=self.config.return_periods)
597
609
  return risk
@@ -637,6 +649,8 @@ class DatabaseBuilder:
637
649
  # Update elevations
638
650
  self.update_fiat_elevation()
639
651
 
652
+ self._svi = self.create_svi()
653
+
640
654
  config = FiatConfigModel(
641
655
  exposure_crs=self.fiat_model.exposure.crs,
642
656
  floodmap_type=self.read_floodmap_type(),
@@ -649,7 +663,7 @@ class DatabaseBuilder:
649
663
  save_simulation=False, # TODO
650
664
  infographics=self.config.infographics,
651
665
  aggregation=self._aggregation_areas,
652
- svi=self.create_svi(),
666
+ svi=self._svi,
653
667
  )
654
668
 
655
669
  # Update output geoms names
@@ -1481,23 +1495,28 @@ class DatabaseBuilder:
1481
1495
 
1482
1496
  ### SITE ###
1483
1497
  def create_site_config(self) -> Site:
1484
- # call this before fiat to ensure the dem is where its expected
1485
- sfincs = self.create_sfincs_config()
1486
-
1487
- # call this after sfincs to get waterlevel references
1488
- gui = self.create_gui_config()
1498
+ """Create the site configuration for the FloodAdapt model.
1499
+
1500
+ The order of these functions is important!
1501
+ 1. Create the SFINCS model.
1502
+ needs: water level references
1503
+ provides: updated water level references with optional tide gauge
1504
+ 2. Create the FIAT model.
1505
+ needs: water level references and optional probabilistic event set
1506
+ provides: svi and exposure geometries
1507
+ 3. Create the GUI model. (requires water level references and FIAT model to be updated)
1508
+ needs: water level references and FIAT model to be updated
1509
+ provides: gui model with output layers, visualization layers and plotting.
1489
1510
 
1490
- # set the probabilistic event set if provided
1511
+ """
1512
+ sfincs = self.create_sfincs_config()
1491
1513
  self.add_probabilistic_set()
1492
-
1493
- # Create the FIAT configuration
1494
1514
  fiat = self.create_fiat_model()
1495
- lon, lat = self.read_location() # Get centroid of site
1515
+ gui = self.create_gui_config()
1496
1516
 
1497
- # Set standard objects
1517
+ # Order doesnt matter from here
1518
+ lon, lat = self.read_location()
1498
1519
  std_objs = self.set_standard_objects()
1499
-
1500
- # Description of site
1501
1520
  description = (
1502
1521
  self.config.description if self.config.description else self.config.name
1503
1522
  )
@@ -1530,7 +1549,7 @@ class DatabaseBuilder:
1530
1549
  gui = GuiModel(
1531
1550
  units=self.unit_system,
1532
1551
  plotting=self.create_hazard_plotting_config(),
1533
- mapbox_layers=self.create_mapbox_layers_config(),
1552
+ output_layers=self.create_output_layers_config(),
1534
1553
  visualization_layers=self.create_visualization_layers(),
1535
1554
  )
1536
1555
 
@@ -1546,57 +1565,73 @@ class DatabaseBuilder:
1546
1565
  f"Unit system {self.config.unit_system} not recognized. Please choose 'imperial' or 'metric'."
1547
1566
  )
1548
1567
 
1549
- def create_visualization_layers(self) -> VisualizationLayersModel:
1550
- visualization_layers = VisualizationLayersModel(
1551
- default_bin_number=4,
1552
- default_colors=["#FFFFFF", "#FEE9CE", "#E03720", "#860000"],
1553
- layer_names=[],
1554
- layer_long_names=[],
1555
- layer_paths=[],
1556
- field_names=[],
1557
- bins=[],
1558
- colors=[],
1559
- )
1560
-
1568
+ def create_visualization_layers(self) -> VisualizationLayers:
1569
+ visualization_layers = VisualizationLayers()
1570
+ if self._svi is not None:
1571
+ visualization_layers.add_layer(
1572
+ name="svi",
1573
+ long_name="Social Vulnerability Index (SVI)",
1574
+ path=str(self.static_path / self._svi.geom),
1575
+ database_path=self.root,
1576
+ field_name="SVI",
1577
+ bins=[0.05, 0.2, 0.4, 0.6, 0.8],
1578
+ )
1561
1579
  return visualization_layers
1562
1580
 
1563
- def create_mapbox_layers_config(self) -> MapboxLayersModel:
1581
+ def create_output_layers_config(self) -> OutputLayers:
1564
1582
  # Read default colors from template
1565
1583
  fd_max = self.config.gui.max_flood_depth
1566
1584
  ad_max = self.config.gui.max_aggr_dmg
1567
1585
  ftd_max = self.config.gui.max_footprint_dmg
1568
1586
  b_max = self.config.gui.max_benefits
1569
1587
 
1570
- svi_bins = None
1571
- if self.config.svi is not None:
1572
- svi_bins = [0.05, 0.2, 0.4, 0.6, 0.8]
1573
-
1574
- mapbox_layers = MapboxLayersModel(
1575
- flood_map_depth_min=0.0, # mask areas with flood depth lower than this (zero = all depths shown) # TODO How to define this?
1576
- flood_map_zbmax=-9999, # mask areas with elevation lower than this (very negative = show all calculated flood depths) # TODO How to define this?,
1577
- flood_map_bins=[0.2 * fd_max, 0.6 * fd_max, fd_max],
1578
- damage_decimals=0,
1579
- footprints_dmg_type="absolute",
1580
- aggregation_dmg_bins=[
1581
- 0.00001,
1582
- 0.1 * ad_max,
1583
- 0.25 * ad_max,
1584
- 0.5 * ad_max,
1585
- ad_max,
1586
- ],
1587
- footprints_dmg_bins=[
1588
- 0.00001,
1589
- 0.06 * ftd_max,
1590
- 0.2 * ftd_max,
1591
- 0.4 * ftd_max,
1592
- ftd_max,
1593
- ],
1594
- benefits_bins=[0, 0.01, 0.02 * b_max, 0.2 * b_max, b_max],
1595
- svi_bins=svi_bins,
1596
- **self._get_bin_colors(),
1597
- )
1588
+ benefits_layer = None
1589
+ if self.config.probabilistic_set is not None:
1590
+ benefits_layer = BenefitsLayer(
1591
+ bins=[0, 0.01, 0.02 * b_max, 0.2 * b_max, b_max],
1592
+ colors=[
1593
+ "#FF7D7D",
1594
+ "#FFFFFF",
1595
+ "#DCEDC8",
1596
+ "#AED581",
1597
+ "#7CB342",
1598
+ "#33691E",
1599
+ ],
1600
+ threshold=0.0,
1601
+ )
1598
1602
 
1599
- return mapbox_layers
1603
+ output_layers = OutputLayers(
1604
+ floodmap=FloodMapLayer(
1605
+ bins=[0.2 * fd_max, 0.6 * fd_max, fd_max],
1606
+ colors=["#D7ECFB", "#8ABDDD", "#1C73A4", "#081D58"],
1607
+ zbmax=-9999,
1608
+ depth_min=0.0,
1609
+ ),
1610
+ aggregation_dmg=AggregationDmgLayer(
1611
+ bins=[0.00001, 0.1 * ad_max, 0.25 * ad_max, 0.5 * ad_max, ad_max],
1612
+ colors=[
1613
+ "#FFFFFF",
1614
+ "#FEE9CE",
1615
+ "#FDBB84",
1616
+ "#FC844E",
1617
+ "#E03720",
1618
+ "#860000",
1619
+ ],
1620
+ ),
1621
+ footprints_dmg=FootprintsDmgLayer(
1622
+ bins=[0.00001, 0.06 * ftd_max, 0.2 * ftd_max, 0.4 * ftd_max, ftd_max],
1623
+ colors=[
1624
+ "#FFFFFF",
1625
+ "#FEE9CE",
1626
+ "#FDBB84",
1627
+ "#FC844E",
1628
+ "#E03720",
1629
+ "#860000",
1630
+ ],
1631
+ ),
1632
+ benefits=benefits_layer,
1633
+ )
1634
+ return output_layers
1600
1635
 
1601
1636
  def create_hazard_plotting_config(self) -> PlottingModel:
1602
1637
  datum_names = [datum.name for datum in self.water_level_references.datums]
@@ -2151,7 +2186,7 @@ class DatabaseBuilder:
2151
2186
  """
2152
2187
  templates_path = Path(__file__).parent.resolve().joinpath("templates")
2153
2188
  with open(
2154
- templates_path.joinpath("mapbox_layers", "bin_colors.toml"), "rb"
2189
+ templates_path.joinpath("output_layers", "bin_colors.toml"), "rb"
2155
2190
  ) as f:
2156
2191
  bin_colors = tomli.load(f)
2157
2192
  return bin_colors
@@ -381,14 +381,6 @@ class Database(IDatabase):
381
381
  runner = BenefitRunner(self, benefit=benefit)
382
382
  runner.run_cost_benefit()
383
383
 
384
- def update(self) -> None:
385
- self.projections_list = self._projections.list_objects()
386
- self.events_list = self._events.list_objects()
387
- self.measures_list = self._measures.list_objects()
388
- self.strategies_list = self._strategies.list_objects()
389
- self.scenarios_list = self._scenarios.list_objects()
390
- self.benefits_list = self._benefits.list_objects()
391
-
392
384
  def get_outputs(self) -> dict[str, Any]:
393
385
  """Return a dictionary with info on the outputs that currently exist in the database.
394
386
 
@@ -397,7 +389,7 @@ class Database(IDatabase):
397
389
  dict[str, Any]
398
390
  Includes 'name', 'path', 'last_modification_date' and "finished" info
399
391
  """
400
- all_scenarios = pd.DataFrame(self._scenarios.list_objects())
392
+ all_scenarios = pd.DataFrame(self._scenarios.summarize_objects())
401
393
  if len(all_scenarios) > 0:
402
394
  df = all_scenarios[all_scenarios["finished"]]
403
395
  else:
@@ -585,17 +577,17 @@ class Database(IDatabase):
585
577
  """
586
578
  match object_type:
587
579
  case "projections":
588
- return self.projections.list_objects()
580
+ return self.projections.summarize_objects()
589
581
  case "events":
590
- return self.events.list_objects()
582
+ return self.events.summarize_objects()
591
583
  case "measures":
592
- return self.measures.list_objects()
584
+ return self.measures.summarize_objects()
593
585
  case "strategies":
594
- return self.strategies.list_objects()
586
+ return self.strategies.summarize_objects()
595
587
  case "scenarios":
596
- return self.scenarios.list_objects()
588
+ return self.scenarios.summarize_objects()
597
589
  case "benefits":
598
- return self.benefits.list_objects()
590
+ return self.benefits.summarize_objects()
599
591
  case _:
600
592
  raise ValueError(
601
593
  f"Object type '{object_type}' is not valid. Must be one of 'projections', 'events', 'measures', 'strategies' or 'scenarios'."
@@ -611,16 +603,20 @@ class Database(IDatabase):
611
603
  scenario_name : str
612
604
  name of the scenario to check if needs to be rerun for hazard
613
605
  """
614
- scenario = self._scenarios.get(scenario_name)
606
+ scenario = self.scenarios.get(scenario_name)
615
607
  runner = ScenarioRunner(self, scenario=scenario)
616
608
 
617
609
  # Dont do anything if the hazard model has already been run in itself
618
610
  if runner.impacts.hazard.has_run:
619
611
  return
620
612
 
613
+ scenarios = [
614
+ self.scenarios.get(scn)
615
+ for scn in self.scenarios.summarize_objects()["name"]
616
+ ]
621
617
  scns_simulated = [
622
618
  sim
623
- for sim in self.scenarios.list_objects()["objects"]
619
+ for sim in scenarios
624
620
  if self.scenarios.output_path.joinpath(sim.name, "Flooding").is_dir()
625
621
  ]
626
622
 
@@ -630,8 +626,9 @@ class Database(IDatabase):
630
626
  path_new = self.scenarios.output_path.joinpath(
631
627
  scenario.name, "Flooding"
632
628
  )
633
- runner._load_objects(scn)
634
- if runner.impacts.hazard.has_run: # only copy results if the hazard model has actually finished and skip simulation folders
629
+ _runner = ScenarioRunner(self, scenario=scn)
630
+
631
+ if _runner.impacts.hazard.has_run: # only copy results if the hazard model has actually finished and skip simulation folders
635
632
  shutil.copytree(
636
633
  existing,
637
634
  path_new,
@@ -642,40 +639,6 @@ class Database(IDatabase):
642
639
  f"Hazard simulation is used from the '{scn.name}' scenario"
643
640
  )
644
641
 
645
- def run_scenario(self, scenario_name: Union[str, list[str]]) -> None:
646
- """Run a scenario hazard and impacts.
647
-
648
- Parameters
649
- ----------
650
- scenario_name : Union[str, list[str]]
651
- name(s) of the scenarios to run.
652
-
653
- Raises
654
- ------
655
- RuntimeError
656
- If an error occurs while running one of the scenarios
657
- """
658
- if not isinstance(scenario_name, list):
659
- scenario_name = [scenario_name]
660
-
661
- errors = []
662
-
663
- for scn in scenario_name:
664
- try:
665
- self.has_run_hazard(scn)
666
- scenario = self.scenarios.get(scn)
667
- runner = ScenarioRunner(self, scenario=scenario)
668
- runner.run(scenario)
669
- except RuntimeError as e:
670
- errors.append(scn)
671
- self.logger.error(f"Error running scenario {scn}: {e}")
672
- if errors:
673
- raise RuntimeError(
674
- "FloodAdapt failed to run for the following scenarios: "
675
- + ", ".join(errors)
676
- + ". Check the logs for more information."
677
- )
678
-
679
642
  def cleanup(self) -> None:
680
643
  """
681
644
  Remove corrupted scenario output.
@@ -67,7 +67,10 @@ class DbsEvent(DbsTemplate[Event]):
67
67
  list of scenarios that use the event
68
68
  """
69
69
  # Get all the scenarios
70
- scenarios = self._database.scenarios.list_objects()["objects"]
70
+ scenarios = [
71
+ self._database.scenarios.get(scn)
72
+ for scn in self._database.scenarios.summarize_objects()["name"]
73
+ ]
71
74
 
72
75
  # Check if event is used in a scenario
73
76
  used_in_scenario = [
@@ -37,20 +37,17 @@ class DbsMeasure(DbsTemplate[Measure]):
37
37
  # Load and return the object
38
38
  return MeasureFactory.get_measure_object(full_path)
39
39
 
40
- def list_objects(self) -> dict[str, list[Any]]:
40
+ def summarize_objects(self) -> dict[str, list[Any]]:
41
41
  """Return a dictionary with info on the measures that currently exist in the database.
42
42
 
43
43
  Returns
44
44
  -------
45
45
  dict[str, Any]
46
- Includes 'name', 'description', 'path' and 'last_modification_date' info
46
+ Includes 'name', 'description', 'path' and 'last_modification_date' and 'geometry' info
47
47
  """
48
- measures = self._get_object_list()
48
+ measures = self._get_object_summary()
49
49
  objects = [self.get(name) for name in measures["name"]]
50
50
 
51
- measures["description"] = [obj.description for obj in objects]
52
- measures["objects"] = objects
53
-
54
51
  geometries = []
55
52
  for obj in objects:
56
53
  # If polygon is used read the polygon file
@@ -99,7 +96,10 @@ class DbsMeasure(DbsTemplate[Measure]):
99
96
  list of strategies that use the measure
100
97
  """
101
98
  # Get all the strategies
102
- strategies = self._database.strategies.list_objects()["objects"]
99
+ strategies = [
100
+ self._database.strategies.get(strategy)
101
+ for strategy in self._database.strategies.summarize_objects()["name"]
102
+ ]
103
103
 
104
104
  # Check if measure is used in a strategy
105
105
  used_in_strategy = [
@@ -42,7 +42,10 @@ class DbsProjection(DbsTemplate[Projection]):
42
42
  list of scenarios that use the projection
43
43
  """
44
44
  # Get all the scenarios
45
- scenarios = self._database.scenarios.list_objects()["objects"]
45
+ scenarios = [
46
+ self._database.scenarios.get(scn)
47
+ for scn in self._database.scenarios.summarize_objects()["name"]
48
+ ]
46
49
 
47
50
  # Check if projection is used in a scenario
48
51
  used_in_scenario = [
@@ -12,7 +12,7 @@ class DbsScenario(DbsTemplate[Scenario]):
12
12
  display_name = "Scenario"
13
13
  _object_class = Scenario
14
14
 
15
- def list_objects(self) -> dict[str, list[Any]]:
15
+ def summarize_objects(self) -> dict[str, list[Any]]:
16
16
  """Return a dictionary with info on the events that currently exist in the database.
17
17
 
18
18
  Returns
@@ -20,12 +20,18 @@ class DbsScenario(DbsTemplate[Scenario]):
20
20
  dict[str, Any]
21
21
  Includes 'name', 'description', 'path' and 'last_modification_date' info
22
22
  """
23
- scenarios = super().list_objects()
24
- objects = scenarios["objects"]
25
- scenarios["Projection"] = [obj.projection for obj in objects]
26
- scenarios["Event"] = [obj.event for obj in objects]
27
- scenarios["Strategy"] = [obj.strategy for obj in objects]
28
- scenarios["finished"] = [self.has_run_check(obj.name) for obj in objects]
23
+ scenarios = super().summarize_objects()
24
+ scenarios["Projection"] = [
25
+ self._read_variable_in_toml("projection", path)
26
+ for path in scenarios["path"]
27
+ ]
28
+ scenarios["Event"] = [
29
+ self._read_variable_in_toml("event", path) for path in scenarios["path"]
30
+ ]
31
+ scenarios["Strategy"] = [
32
+ self._read_variable_in_toml("strategy", path) for path in scenarios["path"]
33
+ ]
34
+ scenarios["finished"] = [self.has_run_check(scn) for scn in scenarios["name"]]
29
35
 
30
36
  return scenarios
31
37
 
@@ -87,7 +93,10 @@ class DbsScenario(DbsTemplate[Scenario]):
87
93
  list[str]
88
94
  list of benefits that use the scenario
89
95
  """
90
- benefits = self._database.benefits.list_objects()["objects"]
96
+ benefits = [
97
+ self._database.benefits.get(benefit)
98
+ for benefit in self._database.benefits.summarize_objects()["name"]
99
+ ]
91
100
  used_in_benefit = []
92
101
  for benefit in benefits:
93
102
  runner = BenefitRunner(database=self._database, benefit=benefit)
@@ -128,11 +128,9 @@ class DbsStatic(IDbsStatic):
128
128
  """
129
129
  # Read the map
130
130
  full_path = self._database.static_path / path
131
- if full_path.is_file():
132
- return gpd.read_file(full_path, engine="pyogrio").to_crs(4326)
133
-
134
- # If the file is not found, throw an error
135
- raise FileNotFoundError(f"File {full_path} not found")
131
+ if not full_path.is_file():
132
+ raise FileNotFoundError(f"File {full_path} not found")
133
+ return gpd.read_file(full_path, engine="pyogrio").to_crs(4326)
136
134
 
137
135
  @cache_method_wrapper
138
136
  def get_slr_scn_names(self) -> list:
@@ -40,7 +40,7 @@ class DbsStrategy(DbsTemplate[Strategy]):
40
40
  ValueError
41
41
  Raise error if name is already in use.
42
42
  """
43
- object_exists = object_model.name in self.list_objects()["name"]
43
+ object_exists = object_model.name in self.summarize_objects()["name"]
44
44
 
45
45
  # If you want to overwrite the object, and the object already exists, first delete it. If it exists and you
46
46
  # don't want to overwrite, raise an error.
@@ -137,11 +137,13 @@ class DbsStrategy(DbsTemplate[Strategy]):
137
137
  list[str]
138
138
  list of scenarios that use the strategy
139
139
  """
140
- # Check if strategy is used in a scenario
140
+ scenarios = [
141
+ self._database.scenarios.get(scn)
142
+ for scn in self._database.scenarios.summarize_objects()["name"]
143
+ ]
144
+
141
145
  used_in_scenario = [
142
- scenario.name
143
- for scenario in self._database.scenarios.list_objects()["objects"]
144
- if name == scenario.strategy
146
+ scenario.name for scenario in scenarios if name == scenario.strategy
145
147
  ]
146
148
 
147
149
  return used_in_scenario
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  from pathlib import Path
4
4
  from typing import Any, TypeVar
5
5
 
6
+ import tomli
6
7
  import tomli_w
7
8
 
8
9
  from flood_adapt.dbs_classes.interface.database import IDatabase
@@ -47,25 +48,16 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
47
48
  # Load and return the object
48
49
  return self._object_class.load_file(full_path)
49
50
 
50
- def list_objects(self) -> dict[str, list[Any]]:
51
+ def summarize_objects(self) -> dict[str, list[Any]]:
51
52
  """Return a dictionary with info on the objects that currently exist in the database.
52
53
 
53
54
  Returns
54
55
  -------
55
56
  dict[str, list[Any]]
56
- A dictionary that contains the keys: `name`, 'path', 'last_modification_date', 'description', 'objects'
57
+ A dictionary that contains the keys: `name`, `description`, `path` and `last_modification_date`.
57
58
  Each key has a list of the corresponding values, where the index of the values corresponds to the same object.
58
59
  """
59
- # Check if all objects exist
60
- object_list = self._get_object_list()
61
-
62
- # Load all objects
63
- objects = [self.get(name) for name in object_list["name"]]
64
-
65
- # From the loaded objects, get the description and add them to the object_list
66
- object_list["description"] = [obj.description for obj in objects]
67
- object_list["objects"] = objects
68
- return object_list
60
+ return self._get_object_summary()
69
61
 
70
62
  def copy(self, old_name: str, new_name: str, new_description: str):
71
63
  """Copy (duplicate) an existing object, and give it a new name.
@@ -155,7 +147,7 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
155
147
  Raise error if name is already in use.
156
148
  """
157
149
  # Check if the object exists
158
- if object_model.name not in self.list_objects()["name"]:
150
+ if object_model.name not in self.summarize_objects()["name"]:
159
151
  raise ValueError(
160
152
  f"{self.display_name}: '{object_model.name}' does not exist. You cannot edit an {self.display_name.lower()} that does not exist."
161
153
  )
@@ -241,13 +233,13 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
241
233
  # level object. By default, return an empty list
242
234
  return []
243
235
 
244
- def _get_object_list(self) -> dict[str, list[Any]]:
236
+ def _get_object_summary(self) -> dict[str, list[Any]]:
245
237
  """Get a dictionary with all the toml paths and last modification dates that exist in the database of the given object_type.
246
238
 
247
239
  Returns
248
240
  -------
249
241
  dict[str, Any]
250
- A dictionary that contains the keys: `name` to `path` and `last_modification_date`
242
+ A dictionary that contains the keys: `name`, `description`, `path` and `last_modification_date`.
251
243
  Each key has a list of the corresponding values, where the index of the values corresponds to the same object.
252
244
  """
253
245
  # If the toml doesnt exist, we might be in the middle of saving a new object or could be a broken object.
@@ -258,19 +250,30 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
258
250
  if (dir / f"{dir.name}.toml").is_file()
259
251
  ]
260
252
  paths = [Path(dir / f"{dir.name}.toml") for dir in directories]
261
- names = [dir.name for dir in directories]
253
+
254
+ names = [self._read_variable_in_toml("name", path) for path in paths]
255
+ descriptions = [
256
+ self._read_variable_in_toml("description", path) for path in paths
257
+ ]
258
+
262
259
  last_modification_date = [
263
260
  datetime.fromtimestamp(file.stat().st_mtime) for file in paths
264
261
  ]
265
262
 
266
263
  objects = {
267
264
  "name": names,
265
+ "description": descriptions,
268
266
  "path": paths,
269
267
  "last_modification_date": last_modification_date,
270
268
  }
271
-
272
269
  return objects
273
270
 
271
+ @staticmethod
272
+ def _read_variable_in_toml(variable_name: str, toml_path: Path) -> str:
273
+ with open(toml_path, "rb") as f:
274
+ data = tomli.load(f)
275
+ return data.get(variable_name, "")
276
+
274
277
  def _validate_to_save(self, object_model: T_OBJECTMODEL, overwrite: bool) -> None:
275
278
  """Validate if the object can be saved.
276
279
 
@@ -285,11 +288,7 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
285
288
  Raise error if name is already in use.
286
289
  """
287
290
  # Check if the object exists
288
- if object_model.name in self.list_objects()["name"]:
289
- raise ValueError(
290
- f"{self.display_name}: '{object_model.name}' already exists. Choose a different name."
291
- )
292
- object_exists = object_model.name in self.list_objects()["name"]
291
+ object_exists = object_model.name in self.summarize_objects()["name"]
293
292
 
294
293
  # If you want to overwrite the object, and the object already exists, first delete it. If it exists and you
295
294
  # don't want to overwrite, raise an error.
@@ -86,10 +86,6 @@ class IDatabase(ABC):
86
86
  def run_benefit(self, benefit_name: Union[str, list[str]]) -> None:
87
87
  pass
88
88
 
89
- @abstractmethod
90
- def update(self) -> None:
91
- pass
92
-
93
89
  @abstractmethod
94
90
  def get_outputs(self) -> dict[str, Any]:
95
91
  pass
@@ -138,10 +134,6 @@ class IDatabase(ABC):
138
134
  def has_run_hazard(self, scenario_name: str) -> None:
139
135
  pass
140
136
 
141
- @abstractmethod
142
- def run_scenario(self, scenario_name: Union[str, list[str]]) -> None:
143
- pass
144
-
145
137
  @abstractmethod
146
138
  def cleanup(self) -> None:
147
139
  pass
@@ -33,7 +33,7 @@ class AbstractDatabaseElement(ABC, Generic[T_OBJECT_MODEL]):
33
33
  pass
34
34
 
35
35
  @abstractmethod
36
- def list_objects(self) -> dict[str, list[Any]]:
36
+ def summarize_objects(self) -> dict[str, list[Any]]:
37
37
  """Return a dictionary with info on the objects that currently exist in the database.
38
38
 
39
39
  Returns