Exposure#
The exposure module integrates building-level inventories with the damage
probability outputs generated by CHEER-Safe, enabling loss estimation across
multiple spatial resolutions, including individual structures and aggregated
geographic zones. It maps user inventory columns onto the structural archetype
M and the resistance configuration C, assigns per-event wind and flood
intensities from hazard control points to building centroids, converts them to
the package's continuous 1-based hazard IDs (Wind_Tr, Flood_Tr), assigns a
zone id per building and writes the STARR-compatible export consumed by
Loss Estimation.
The module replaces the standalone Inventory-Loss Link scripts (one
Inventory_Loss class that resolved every file from a fixed folder tree under
the working directory) with four dataclasses that take explicit path fields,
follow Config.AUTO_LOAD_DATASETS / Config.AUTO_EXECUTE_METHODS, and return
their results so callers can assign them.
Classes#
| Page | Use when |
|---|---|
| BaseExposure | You need to load the inventory layer, the per-event hazard control-point layers, an optional zone layer and the discrete tables, or to describe your inventory columns with InventorySchema. |
| ArchetypeAssigner | You need the archetype M (1-8) and a seeded resistance configuration C (1-192) for every building. |
| HazardMapper | You need per-event wind and flood hazard IDs at building centroids, or the grid helpers behind the Eastern North Carolina hazard maps. |
| Exposure | You want zone ids and the STARR export (Inventory.csv, Wind_Tr.npy, Flood_Tr.npy), or an in-memory hand-off to LossEstimation. |
The classes form one inheritance chain, BaseExposure -> ArchetypeAssigner ->
HazardMapper -> Exposure; constructing Exposure gives access to every
loader and calculation of the chain.
Pipeline#
flowchart LR
I[(Inventory layer)] --> N[Normalized attributes<br>InventorySchema]
N --> A[Archetype M<br>Configuration C]
H[(Hazard control points<br>one layer per event)] --> M[Intensity at centroids<br>Nearest or Inverse_d4]
A --> M
M --> T[Continuous hazard IDs<br>Wind_Tr and Flood_Tr]
Z[(Zone layer)] --> S[Zone id per building]
T --> S
S --> X[STARR export<br>Inventory.csv, Wind_Tr.npy, Flood_Tr.npy]
X --> L[Loss Estimation]
Shared Inputs#
| File | Shape | Units or indexing | Used by |
|---|---|---|---|
Inventory layer (inventory_path, default Inputs/Inventory.parquet) |
one row per building | Columns named by InventorySchema; polygon or point geometry with a CRS. GeoParquet, shapefile or GeoPackage. |
load_inventory |
Hazard control-point layers (hazard_dir, default Inputs/Hazards, pattern *.parquet) |
n_points rows per event file |
Wind in m/s, Flood in m, -9999 nodata; identical point geometry and order in every file; event order from the integer embedded in the file name. |
load_hazard_events, HazardMapper |
Zone layer (zones_path, optional) |
polygons | LEAPHI columns COUNTYFP00, zone, i_lh503, i_lh1509, i_lh708, i_lh740; any CRS (reprojected on the fly). |
load_zones, Exposure |
Inputs/WindSpeed.json |
[25, 3] |
id, mph, m/s; the mph column is the knot table for wind IDs. | HazardMapper |
Inputs/FloodDepth.json |
[136, 3] |
id, inches, m; the inch column is the knot table for flood IDs. | HazardMapper |
Inputs/ComponentCombination_All.json |
[192, 6] |
RC, RF, RTW, O, W, F states; row C defines configuration C. |
ArchetypeAssigner |
Paths left as None resolve under Config.INPUTS_PATH; output_dir resolves
to <Config.OUTPUTS_PATH>/exposure. The
input manual lists
the layer schemas and grid facts in detail.
Synthetic Test Case#
SyntheticExposureData generates a complete, small case whose columns, labels,
value ranges, CRS (EPSG:5070) and 1 km hazard grid follow the Eastern North
Carolina source data without shipping any of it. The generators are
parameterized from the measured demo distributions, not sampled from real rows,
and every draw goes through cheersafe.tools.Sampling, so a seed reproduces a
case exactly. The test suite and the example notebooks run on it.
| Method | Returns | Notes |
|---|---|---|
make_inventory(n=40, seed=None, ..., spread_full_window=False) |
GeoDataFrame in the demo schema (bldgtype, roof_shape_1, Story_AI, Garage, PRIM_OCC, med_yr_blt, Coast_D (miles), val_struct, val_cont, fd_id_nsi, is_vacant, H_TEN_syn, geometry) |
10 m square footprints inside the 1.5 km strip covered by make_zones (or across the whole grid window with spread_full_window=True); year built 1950-2020 so all three construction periods occur; coast distance on both sides of 2 miles; val_cont = 0.5 x val_struct. With include_edge_cases=True, five rows with fd_id_nsi >= 900000 exercise a missing roof shape, bldgtype='M', PRIM_OCC='Multi Family', a centroid outside the hazard bounding box and a centroid outside every zone. |
make_hazard_events(event_names=("h1", "h2", "h10"), nx=3, ny=3, seed=None, ...) |
dict[name -> GeoDataFrame] with cell_ID, x, y, Wind, Flood, geometry |
Points exactly on the grid, cell_ID = ix * 350 + iy + 149103; Wind 9.5-44.5 m/s; Flood 0-2 m with about half the points dry and the first point always dry; nodata_cells sets -9999 at chosen rows. |
make_zones(...) |
Two polygons with all six LEAPHI columns | i_lh740 = [1, 2], both nesting into i_lh503 = 1; the north-eastern corner of the window is left unassigned. |
make_damage_library(keys=((1, 1), (2, 101), (8, 192)), n_wind=25, n_flood=136, n_damage_states=50, seed=None, unnormalized_rows=1) |
dict[(m, c) -> float32 (25, 136, 50)] |
Rows sum to one and rise monotonically with hazard; the zero-hazard row is a point mass on the first state; unnormalized_rows rows are scaled by 0.9 to exercise the builder's warning. |
make_hurricane_probabilities(n_events=3, seed=None, annual_rate=0.32) |
(n_events, 2) ids and probabilities |
Sum equals annual_rate, mirroring the 0.3235 per year of the source catalogue. |
save_inputs(directory, inventory, hazard_events, zones, hurricane_probabilities, wind_speeds, flood_depths, damage_states, component_combinations, damage_library=None, ...) |
dict of written paths |
Writes <directory>/Inputs/{Inventory.parquet, Hazards/<event>.parquet, zones.gpkg, WindSpeed.json, FloodDepth.json, DamageState.json, ComponentCombination_All.json, Hurricane_ProbWindFlood.json[, damage_library.pkl]}. |
Configuration Behavior#
Config.AUTO_LOAD_DATASETS=True makes __post_init__ run the five loaders
(load_hazard_tables, load_component_combinations, load_inventory,
load_hazard_events, load_zones) and set datasets_loaded. With False,
call them yourself before any calculation.
Config.AUTO_EXECUTE_METHODS=True then runs the calculation chain of the class
you constructed and assigns the results: archetype_inventory
(ArchetypeAssigner), clipped_inventory, neighbor_index,
neighbor_distance, wind_ids, flood_ids (HazardMapper), zone_ids and
starr_inventory (Exposure). With False, call the calculate_* methods and
assign their return values; nothing is written to disk until you call a
save_* method. update_outputs() reloads every input and reruns the chain.
Config.RANDOM_SEED is the default seed of the configuration draw when the
seed field is None. Config.INPUTS_PATH and Config.OUTPUTS_PATH resolve
the path fields left as None.
Minimal Examples#
Automatic initialization (the default flags), with the inputs under
Config.INPUTS_PATH:
from cheersafe.config import Config
from cheersafe.exposure import Exposure
Config.AUTO_LOAD_DATASETS = True
Config.AUTO_EXECUTE_METHODS = True
Config.INPUTS_PATH = "Inputs" # Inventory.parquet, Hazards/, zones.gpkg and the JSON tables
Config.OUTPUTS_PATH = "Outputs"
exposure = Exposure(zones_path="Inputs/zones.gpkg", seed=1)
table, wind_ids, flood_ids, event_names = exposure.calculate_starr_export()
paths = exposure.save_starr_inputs() # Outputs/exposure/Inventory.csv, Wind_Tr.npy, ...
Manual dependency injection, where the caller assigns every returned value:
import numpy as np
from cheersafe.config import Config
from cheersafe.exposure import Exposure
Config.AUTO_LOAD_DATASETS = False
Config.AUTO_EXECUTE_METHODS = False
exposure = Exposure(
inventory_path="Inputs/Inventory.parquet",
hazard_dir="Inputs/Hazards",
zones_path="Inputs/zones.gpkg",
average_mode="Nearest",
seed=1,
)
exposure.load_hazard_tables()
exposure.load_component_combinations()
exposure.load_inventory()
exposure.load_hazard_events()
exposure.load_zones()
exposure.archetype_inventory = exposure.calculate_archetypes(exposure.inventory)
exposure.clipped_inventory = exposure.calculate_clipped_inventory(exposure.archetype_inventory)
centroids = exposure.clipped_inventory.geometry.centroid
exposure.neighbor_index, exposure.neighbor_distance = exposure.calculate_neighbor_match(
np.column_stack([centroids.x, centroids.y]), exposure.hazard_points_xy, k=1
)
exposure.wind_ids, exposure.flood_ids = exposure.calculate_hazard_ids(
exposure.neighbor_index, exposure.neighbor_distance, exposure.hazard_wind, exposure.hazard_flood
)
exposure.zone_ids = exposure.calculate_zone_assignment(
exposure.clipped_inventory, exposure.zones, "i_lh740"
)
exposure.starr_inventory = exposure.calculate_starr_inventory(
exposure.clipped_inventory, exposure.zone_ids
)
paths = exposure.save_starr_inputs("Outputs/exposure")
The quickstart
runs the same chain on a synthetic case and hands it to LossEstimation
without touching the disk.
Differences From The Verified Scripts#
Deliberate departures
The defaults reproduce the verified Eastern North Carolina run; the following points differ by design and are documented here so results can be compared knowingly.
- The configuration draw
Cis seeded and vectorized throughcheersafe.tools.Sampling(labelexposure.component_configuration), whereas the scripts used an unseedednp.random.choice. Archetype tables therefore differ row by row from the frozen 07/07/2026 export even though the marginal distributions are identical. - The building-type (
bldgtype) and occupancy (PRIM_OCC) filters were no-ops in the verified run: every building of the source inventory passed both. They stay off by default (drop_unmatched_occupancy=False) and only flag rows inis_wood_frame/is_single_family; set the flag to actually drop non-wood or non-single-family rows. - Hazard columns are selected by name (
wind_col,flood_col), not by position, and every path is an explicit field instead of the nine-folder tree under the working directory. - Control-point geometry is checked across all event files and a
mismatch raises
ValueError; the scripts assumed identical geometry. - The
linearindex convention of the loss module interpolates over the full ID range; the scripts' interpolation clamp never reached the last bin. - Damage-library rows that do not sum to one are counted and reported;
(m, c)pairs missing from the library yieldNaN, not zero, loss ratios. Hurricane_name.npyis saved as a unicode array, so it loads withoutallow_pickle; no compatibility unpickler is needed for the library.- Nodata (
-9999) and missing hazard values are set to zero before inverse-distance averaging exactly as in the scripts; this behaviour is replicated but has not been verified against published outputs.
References#
- Peng, J. (2013). Modeling natural disaster risk management: Integrating the roles of insurance and retrofit and multiple stakeholder perspectives. PhD thesis, University of Delaware. Source of the archetype table and of the component-state probabilities by construction period and coast distance.
- Apivatanagul, P., Davidson, R., Blanton, B., Nozick, L. (2011). Long-term regional hurricane hazard analysis for wind and storm surge. Coastal Engineering, 58. Source of the hazard event set and its annual probabilities.
- CHEER-Hub, Inventory-Loss Link documentation (repository
Inventory-Loss-link). Describes the supersededInventory_Lossinterfaces and folder layout; the input descriptions remain useful, the calls do not.
Related References#
- API: Exposure
- Input manual: Exposure And Building-Level Loss Inputs
- Downstream: Loss Estimation
- Related notebook: Exposure Workflow
- Related notebook: Eastern North Carolina Loss Figures