crc-sdk 0.1.0__tar.gz

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 (76) hide show
  1. crc_sdk-0.1.0/LICENSE +18 -0
  2. crc_sdk-0.1.0/PKG-INFO +374 -0
  3. crc_sdk-0.1.0/README.md +340 -0
  4. crc_sdk-0.1.0/crc_sdk/__init__.py +31 -0
  5. crc_sdk-0.1.0/crc_sdk/connectors/__init__.py +35 -0
  6. crc_sdk-0.1.0/crc_sdk/connectors/adapters.py +285 -0
  7. crc_sdk-0.1.0/crc_sdk/connectors/duckdb/__init__.py +35 -0
  8. crc_sdk-0.1.0/crc_sdk/connectors/duckdb/connection.py +366 -0
  9. crc_sdk-0.1.0/crc_sdk/connectors/duckdb/geotiff.py +617 -0
  10. crc_sdk-0.1.0/crc_sdk/connectors/duckdb/zarr.py +461 -0
  11. crc_sdk-0.1.0/crc_sdk/connectors/parquet.py +323 -0
  12. crc_sdk-0.1.0/crc_sdk/connectors/protocols.py +13 -0
  13. crc_sdk-0.1.0/crc_sdk/core/__init__.py +95 -0
  14. crc_sdk-0.1.0/crc_sdk/fitting/__init__.py +31 -0
  15. crc_sdk-0.1.0/crc_sdk/fitting/workflows.py +4 -0
  16. crc_sdk-0.1.0/crc_sdk/geometry/__init__.py +117 -0
  17. crc_sdk-0.1.0/crc_sdk/geometry/admin.py +462 -0
  18. crc_sdk-0.1.0/crc_sdk/geometry/coverage.py +1201 -0
  19. crc_sdk-0.1.0/crc_sdk/geometry/formats.py +91 -0
  20. crc_sdk-0.1.0/crc_sdk/geometry/h3.py +418 -0
  21. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/__init__.py +49 -0
  22. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/_build.py +185 -0
  23. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/_geojson_sql.py +221 -0
  24. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/_process.py +195 -0
  25. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/archive.py +153 -0
  26. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/binaries.py +38 -0
  27. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/budget.py +243 -0
  28. crc_sdk-0.1.0/crc_sdk/geometry/pmtiles/presets.py +231 -0
  29. crc_sdk-0.1.0/crc_sdk/geometry/vector.py +288 -0
  30. crc_sdk-0.1.0/crc_sdk/impacts/__init__.py +25 -0
  31. crc_sdk-0.1.0/crc_sdk/impacts/custom.py +5 -0
  32. crc_sdk-0.1.0/crc_sdk/providers/__init__.py +21 -0
  33. crc_sdk-0.1.0/crc_sdk/providers/local.py +36 -0
  34. crc_sdk-0.1.0/crc_sdk/providers/metadata.py +1 -0
  35. crc_sdk-0.1.0/crc_sdk/providers/os_climate.py +270 -0
  36. crc_sdk-0.1.0/crc_sdk/providers/protocol.py +22 -0
  37. crc_sdk-0.1.0/crc_sdk/py.typed +1 -0
  38. crc_sdk-0.1.0/crc_sdk/schema/__init__.py +17 -0
  39. crc_sdk-0.1.0/crc_sdk/schema/hazard_field.py +41 -0
  40. crc_sdk-0.1.0/crc_sdk/types/__init__.py +20 -0
  41. crc_sdk-0.1.0/crc_sdk/types/dataset.py +75 -0
  42. crc_sdk-0.1.0/crc_sdk/types/geometry.py +12 -0
  43. crc_sdk-0.1.0/crc_sdk/types/hazard.py +87 -0
  44. crc_sdk-0.1.0/crc_sdk/types/storage.py +11 -0
  45. crc_sdk-0.1.0/crc_sdk/workflows/__init__.py +55 -0
  46. crc_sdk-0.1.0/crc_sdk/workflows/_portfolio.py +407 -0
  47. crc_sdk-0.1.0/crc_sdk/workflows/distributions.py +317 -0
  48. crc_sdk-0.1.0/crc_sdk/workflows/portfolio.py +386 -0
  49. crc_sdk-0.1.0/crc_sdk/workflows/tiling.py +322 -0
  50. crc_sdk-0.1.0/crc_sdk.egg-info/PKG-INFO +374 -0
  51. crc_sdk-0.1.0/crc_sdk.egg-info/SOURCES.txt +74 -0
  52. crc_sdk-0.1.0/crc_sdk.egg-info/dependency_links.txt +1 -0
  53. crc_sdk-0.1.0/crc_sdk.egg-info/requires.txt +24 -0
  54. crc_sdk-0.1.0/crc_sdk.egg-info/top_level.txt +1 -0
  55. crc_sdk-0.1.0/pyproject.toml +68 -0
  56. crc_sdk-0.1.0/setup.cfg +4 -0
  57. crc_sdk-0.1.0/tests/test_admin_lookup.py +206 -0
  58. crc_sdk-0.1.0/tests/test_core.py +16 -0
  59. crc_sdk-0.1.0/tests/test_coverage_sql.py +463 -0
  60. crc_sdk-0.1.0/tests/test_distribution_workflows.py +745 -0
  61. crc_sdk-0.1.0/tests/test_duckdb_secrets.py +54 -0
  62. crc_sdk-0.1.0/tests/test_geometry_h3.py +259 -0
  63. crc_sdk-0.1.0/tests/test_geotiff.py +285 -0
  64. crc_sdk-0.1.0/tests/test_imports.py +24 -0
  65. crc_sdk-0.1.0/tests/test_os_climate.py +370 -0
  66. crc_sdk-0.1.0/tests/test_parquet.py +229 -0
  67. crc_sdk-0.1.0/tests/test_pmtiles_archive.py +245 -0
  68. crc_sdk-0.1.0/tests/test_pmtiles_binaries.py +43 -0
  69. crc_sdk-0.1.0/tests/test_pmtiles_budget.py +173 -0
  70. crc_sdk-0.1.0/tests/test_pmtiles_geojson_sql.py +242 -0
  71. crc_sdk-0.1.0/tests/test_pmtiles_presets.py +188 -0
  72. crc_sdk-0.1.0/tests/test_runtime_resources.py +90 -0
  73. crc_sdk-0.1.0/tests/test_schema.py +24 -0
  74. crc_sdk-0.1.0/tests/test_types.py +141 -0
  75. crc_sdk-0.1.0/tests/test_vector_polyfill.py +172 -0
  76. crc_sdk-0.1.0/tests/test_workflows_tiling.py +789 -0
crc_sdk-0.1.0/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ CRC SDK
2
+ Copyright (C) 2026 Riskthinking.AI
3
+
4
+ This program is free software: you can redistribute it and/or modify it
5
+ under the terms of the GNU Affero General Public License as published by
6
+ the Free Software Foundation, either version 3 of the License, or (at your
7
+ option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful, but
10
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12
+ License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ The complete license text is available at:
18
+ <https://www.gnu.org/licenses/agpl-3.0.txt>
crc_sdk-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: crc-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Climate Risk Commons data access and workflows
5
+ Author: Riskthinking.AI
6
+ License-Expression: AGPL-3.0-or-later
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Typing :: Typed
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: crc-framework<0.3,>=0.2
14
+ Requires-Dist: pydantic<3,>=2
15
+ Requires-Dist: duckdb<2,>=1
16
+ Requires-Dist: pyarrow>=14
17
+ Requires-Dist: psutil>=5
18
+ Requires-Dist: fsspec>=2024
19
+ Requires-Dist: s3fs>=2024
20
+ Requires-Dist: gcsfs>=2024
21
+ Provides-Extra: zarr
22
+ Requires-Dist: zarr<3,>=2.18; extra == "zarr"
23
+ Provides-Extra: raster
24
+ Requires-Dist: rasterio>=1.4; extra == "raster"
25
+ Provides-Extra: geometry
26
+ Requires-Dist: h3>=4; extra == "geometry"
27
+ Requires-Dist: h3ronpy>=0.19; extra == "geometry"
28
+ Requires-Dist: shapely>=2; extra == "geometry"
29
+ Provides-Extra: test
30
+ Requires-Dist: mypy>=1.10; extra == "test"
31
+ Requires-Dist: pytest>=8; extra == "test"
32
+ Requires-Dist: ruff>=0.5; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # CRC SDK
36
+
37
+ CRC SDK is the higher-level Python interface for Climate Risk Commons data
38
+ access, storage providers, geometry utilities, and analytical workflows.
39
+ Numerical distributions, curve fitting, impact transforms, and risk metrics are
40
+ provided by the versioned
41
+ [`crc-framework`](https://pypi.org/project/crc-framework/) dependency.
42
+
43
+ ## Development
44
+
45
+ uv is the recommended tool for managing the development environment:
46
+
47
+ ```shell
48
+ uv sync --all-extras
49
+
50
+ uv run pytest
51
+ uv run mypy
52
+ uv run ruff check .
53
+ ```
54
+
55
+ Or simply:
56
+
57
+ ```shell
58
+ python -m venv .venv
59
+ .venv/bin/python -m pip install -e ".[zarr,raster,geometry,test]"
60
+ .venv/bin/python -m pytest
61
+ .venv/bin/python -m mypy
62
+ .venv/bin/python -m ruff check .
63
+ ```
64
+
65
+ ## Dependencies
66
+
67
+ DuckDB, Arrow (`pyarrow`), `psutil` (resource detection), and remote-storage
68
+ transport (`fsspec`, `s3fs`, `gcsfs`) are baseline dependencies — every
69
+ connector and workflow in this SDK is built on that stack, so gating it
70
+ behind an extra would just move the same install onto every real caller.
71
+
72
+ Everything else is a specific data-format adapter or a pure-geometry
73
+ dependency, opted into only by the callers that need it:
74
+
75
+ | Extra | Adds | Used by |
76
+ |---|---|---|
77
+ | `zarr` | `zarr` | `OSClimateProvider`/`ZarrRaster` (OS-Climate Zarr raster ingest) |
78
+ | `raster` | `rasterio` | `GeoTiffRaster` (GeoTIFF/COG ingest, streamed via GDAL VSI) |
79
+ | `geometry` | `h3`, `h3ronpy`, `shapely` | `H3Indexer`, `intersecting_cells`, `cell_polygon`, other abstract H3/geometry math, vectorized batch H3 ops on Arrow data (`polyfill_wkb`, `expand_polygon_candidates`, raster-to-H3 sampling) |
80
+ | `test` | `mypy`, `pytest`, `ruff` | Development only |
81
+
82
+ Every function that needs an extra-gated dependency imports it lazily and
83
+ raises a clear `ImportError` naming the extra to install if it's missing —
84
+ importing `crc_sdk` (or any of its subpackages) itself never requires more
85
+ than the baseline dependencies.
86
+
87
+ **OS-level dependency (not a pip extra):** `tippecanoe` and `tile-join`
88
+ (https://github.com/felt/tippecanoe) must be present on `PATH` for
89
+ `crc_sdk.geometry.pmtiles` — they're assumed to already be installed on the
90
+ runtime image, not `pip install`-able, so there's no extra for them. Verify
91
+ availability with `require_tippecanoe()`/`require_tile_join()`, which raise a
92
+ friendly, actionable error (with install instructions) if either is missing.
93
+
94
+ ## Package boundaries
95
+
96
+ - `crc_sdk.core`, `crc_sdk.fitting`, and `crc_sdk.impacts` expose the stable
97
+ public API of `crc_framework`.
98
+ - `crc_sdk.connectors` handles external formats and query engines: DuckDB
99
+ connection helpers (`DuckDBConnection`, `RuntimeResources`, streaming
100
+ Parquet writes), OS-Climate Zarr ingest (`zarr` extra), and GeoTIFF/COG
101
+ ingest (`GeoTiffRaster`, `raster` extra) — the latter streams directly
102
+ from local paths or `gs://`/`s3://`/`http(s)://` URIs via GDAL's own
103
+ range-request support, with no local download by default.
104
+ - `crc_sdk.providers` describes storage and dataset discovery.
105
+ - `crc_sdk.geometry` contains geometry conversion, DuckDB-native H3 polyfill
106
+ (`H3Indexer`), Arrow batch polyfill (`polyfill_wkb`, `geometry`
107
+ extra for h3ronpy), raster-to-H3 sampling primitives
108
+ (`pixel_grid_resolution`, `sample_grid_to_h3`), exploded coverage writers
109
+ (`write_exploded_coverage`), optional nested lookup derivation
110
+ (`LookupCatalog`, `write_lookup_contract`, `write_partitioned_lookup`), and
111
+ PMTiles generation (`crc_sdk.geometry.pmtiles` — also reachable flattened
112
+ as `crc_sdk.geometry.PMTilesBuild`, etc.): `PMTilesBuild` streams a
113
+ GeoParquet source (a single file, or a Hive-partitioned dataset glob) into
114
+ one `.pmtiles` archive in one tiling pass, building the GeoParquet ->
115
+ GeoJSON bridge itself in DuckDB `spatial`-extension SQL
116
+ (`ST_AsGeoJSON`/`ST_ReducePrecision`/`ST_Transform`) rather than shelling
117
+ out to an external converter, streamed via the same Arrow-batched-reader
118
+ pattern used elsewhere in this SDK. `tippecanoe_threads`/`duckdb_threads`
119
+ default to every detected core (no conservative per-thread cap, unlike
120
+ DuckDB's own GEOS-throttled default) since tippecanoe's tile-building has
121
+ no documented per-thread memory ceiling. A pre-flight budget check raises
122
+ a clear, actionable error if a source is estimated to exceed available
123
+ scratch disk, rather than silently degrading into a slower multi-batch
124
+ fallback — provisioning more disk or narrowing the run's scope is left to
125
+ the caller.
126
+ - `crc_sdk.schema` defines columnar data contracts.
127
+ - `crc_sdk.types` contains SDK-owned Pydantic configuration and metadata.
128
+ - `crc_sdk.workflows` coordinates data access and computation.
129
+
130
+ DuckDB resource limits are detected when requested
131
+ (`RuntimeResources.detect` / `DuckDBConnection.for_analytics`) and relayed
132
+ through the connection `config` mapping. Thread count is
133
+ `min(cpus, usable_RAM / GiB_per_thread)` with usable RAM ≈ 60% of detected
134
+ memory and a default of ~2.5 GiB/thread (GEOS spatial work often slows when
135
+ over-threaded). `memory_limit` and `max_temp_directory_size` remain hard
136
+ process caps. Override with `CRC_DUCKDB_THREADS`, `CRC_DUCKDB_MEMORY`, and/or
137
+ `CRC_DUCKDB_BYTES_PER_THREAD_GIB`, or pass an explicit `config` dict. Set
138
+ `CRC_DUCKDB_PROFILE=1` to enable detailed query profiling around
139
+ enrich/coverage stages in h3geo.
140
+
141
+ Constructors with no natural caller-supplied directory of their own
142
+ (`OSClimateProvider`, `ZarrRaster`, `H3Indexer`) build a resource-tuned
143
+ connection by default — via `DuckDBConnection.for_analytics` — instead of a
144
+ bare, untuned one, so this scales out of the box with no configuration.
145
+ Passing an explicit `connection`/`con` always wins and skips this entirely.
146
+ Otherwise the spill/temp directory defaults to a stable location under the
147
+ system temp directory (`default_work_dir()`, not a fresh one per call), and
148
+ can be set per-call via each constructor's own `work_dir` parameter, or
149
+ globally via `CRC_DUCKDB_WORK_DIR`.
150
+
151
+ Private/authenticated remote sources (a non-public GCS/S3 bucket) are
152
+ configured the same idiomatic-DuckDB way as everything else here: raw
153
+ `CREATE OR REPLACE SECRET` SQL, passed as `setup_sql=(...)` to
154
+ `DuckDBConnection`/`DuckDBConnection.for_analytics` to have it run
155
+ automatically on `.connect()`, right after extensions load. There is
156
+ deliberately no secret-builder type in the SDK — DuckDB's own secret DDL
157
+ (https://duckdb.org/docs/configuration/secrets_manager) is already the
158
+ documented interface, and a caller's own connection-setup module is a more
159
+ natural home for its specific credentials than a generic wrapper trying to
160
+ track every provider/type DuckDB supports. `sql_quote`/`sql_identifier` are
161
+ exported for safely building that SQL; the one DuckDB quirk worth knowing is
162
+ that `PROVIDER` is a bare keyword (`config`, `credential_chain`, ...), not a
163
+ quoted string literal, unlike every other secret option:
164
+
165
+ ```python
166
+ import os
167
+ from crc_sdk.connectors.duckdb import DuckDBConnection, sql_quote
168
+
169
+ setup_sql = []
170
+ key_id, secret = os.getenv("GCS_ACCESS_KEY"), os.getenv("GCS_ACCESS_SECRET")
171
+ if key_id and secret:
172
+ setup_sql.append(
173
+ f"CREATE OR REPLACE SECRET gcs (TYPE GCS, KEY_ID {sql_quote(key_id)}, "
174
+ f"SECRET {sql_quote(secret)})"
175
+ )
176
+ con = DuckDBConnection.for_analytics(work_dir, setup_sql=setup_sql).connect()
177
+ ```
178
+
179
+ ## Canonical hazard datasets
180
+
181
+ The SDK internalizes fitted hazards as one versioned Arrow/Parquet contract.
182
+ Rows contain a canonical unsigned H3 `cell_index`, stable `source_id`, optional
183
+ source WKB, scenario dimensions, and the parameters needed to reconstruct
184
+ either a `crc_framework.FittedDistribution` or `HurdleDistribution`.
185
+ `curve_shape` is nullable because Gumbel families do not use a shape parameter;
186
+ atom probability and location are present only when `curve_kind` is `hurdle`.
187
+
188
+ The logical row key is
189
+ `(hazard_name, horizon, pathway, cell_index, source_id)`. `cell_index` is the
190
+ spatial join key, not a globally unique identifier. Canonical files are sorted
191
+ by that row key for predicate pruning and merge joins.
192
+
193
+ Dataset-wide facts are stored once as a complete JSON payload under the
194
+ `crc.hazard.metadata` Parquet key: schema version, one uncompacted H3
195
+ resolution, non-exceedance probability convention, value unit and semantics,
196
+ WKB CRS, producer, source provenance, and creation version.
197
+
198
+ Each dataset is one self-describing Parquet file, expanded by H3 cell for
199
+ spatial joins. The caller chooses its full destination path and filename.
200
+ Writes use DuckDB, and an optional configured DuckDB connection allows the same
201
+ API to use its local or cloud filesystems, extensions, secrets, and settings.
202
+ Source knots and fit diagnostics are transient ingest inputs, not a second
203
+ persisted data contract.
204
+
205
+ External connectors remain source-format readers. Ingest adapters perform the
206
+ explicit conversion:
207
+
208
+ ```text
209
+ external raster/table -> source curves and geometry -> selected family fit
210
+ -> conservative intersecting H3 cells -> canonical Arrow -> Parquet
211
+ ```
212
+
213
+ Boundary candidate generation uses H3 overlap coverage, not center polyfill.
214
+ This makes the integer join a conservative superset before an exact
215
+ `ST_Contains(source_geometry, asset_point)` refinement. Resolution estimates
216
+ report measured coverage error and expanded row count, while ingest policy
217
+ selects and records the dataset resolution.
218
+
219
+ OS-Climate return-period rasters can be canonicalized with
220
+ `OSClimateIngestPolicy` and `canonicalize_os_climate`. The caller must choose
221
+ the distribution family and, for zero-heavy hazards, provide an explicit
222
+ `HurdleFitPolicy`; the SDK does not infer an exact point mass from sparse
223
+ knots. Plain curves use `fit_quantiles`, while hurdle curves use
224
+ `fit_hurdle_quantiles`. `LocalProvider` queries persisted hazard rows through
225
+ `HazardQuery`.
226
+
227
+ ### Evaluating asset portfolios at return periods
228
+
229
+ Canonical curve parameters can be evaluated for a portfolio without returning
230
+ to the external source format or refitting the data. The workflow joins every
231
+ asset to its canonical curve and writes one row per asset, hazard, horizon, and
232
+ pathway, with one value column per requested return period:
233
+
234
+ ```python
235
+ import pyarrow as pa
236
+
237
+ from crc_sdk.workflows import HazardDataset
238
+
239
+ assets = pa.table(
240
+ {
241
+ "asset_id": ["warehouse-a", "warehouse-b"],
242
+ "longitude": [6.9603, 7.5010],
243
+ "latitude": [50.9375, 51.0030],
244
+ "sector": ["logistics", "manufacturing"],
245
+ }
246
+ )
247
+
248
+ result = (
249
+ HazardDataset.local("flood.parquet")
250
+ .for_assets(assets)
251
+ .select(horizons=[2050], pathways=["ssp585"])
252
+ .return_periods([25, 50, 100, 250, 500, 1000])
253
+ .write_parquet("portfolio-flood.parquet")
254
+ )
255
+ ```
256
+
257
+ The resulting value columns are `value_rp25`, `value_rp50`, `value_rp100`,
258
+ `value_rp250`, `value_rp500`, and `value_rp1000`. For upper-tail hazards, each
259
+ return period `RP` is evaluated at non-exceedance probability `1 - 1/RP`.
260
+ Value unit, value semantics, and the complete return-period/probability/column
261
+ mapping are stored under `crc.hazard.evaluation` in Parquet metadata.
262
+
263
+ An impact function can replace the sampled hazard values with event-aligned
264
+ impact values before writing:
265
+
266
+ ```python
267
+ import numpy as np
268
+
269
+ impact_result = (
270
+ HazardDataset.local("flood.parquet")
271
+ .for_assets(assets)
272
+ .return_periods([25, 100, 250])
273
+ .impact(
274
+ lambda depth: np.clip(depth / 2.0, 0.0, 1.0),
275
+ name="depth_damage_ratio",
276
+ value_unit="fraction",
277
+ value_semantics="damage ratio",
278
+ )
279
+ .write_parquet(
280
+ "portfolio-impact.parquet",
281
+ execution=ExecutionOptions(max_workers=1),
282
+ )
283
+ )
284
+ ```
285
+
286
+ The SDK first samples each hazard return period and then calls
287
+ `impact.evaluate(...)` on that row's value vector. Therefore
288
+ `value_rp100 = impact(hazard_rp100)`: the return period continues to identify
289
+ the source hazard event. This differs intentionally from transforming a full
290
+ distribution and then taking an impact quantile, which can reorder decreasing
291
+ or non-monotonic impacts. Use the distribution interface in `crc-framework`
292
+ for that risk-analysis interpretation.
293
+
294
+ Built-in and registry-backed framework impacts use the same fluent method:
295
+
296
+ ```python
297
+ from crc_sdk.impacts import PiecewiseLinearImpact, impacts
298
+ from crc_sdk.workflows import ImpactContextColumns
299
+
300
+ damage_curve = PiecewiseLinearImpact(
301
+ exposure=[0.0, 0.2, 1.0, 2.0],
302
+ impact=[0.0, 0.0, 0.25, 1.0],
303
+ )
304
+
305
+ request = request.impact(
306
+ damage_curve,
307
+ name="flood_damage_ratio",
308
+ value_unit="fraction",
309
+ value_semantics="damage ratio",
310
+ )
311
+
312
+ registry_request = request.impact(
313
+ impacts.for_factor("inundation"),
314
+ context=ImpactContextColumns(
315
+ country="country",
316
+ continent="continent",
317
+ building_type="building_type",
318
+ historic_mean="historic_mean",
319
+ ),
320
+ name="inundation_impact",
321
+ value_unit="fraction",
322
+ value_semantics="damage ratio",
323
+ )
324
+ ```
325
+
326
+ The generated H3 `cell_index` is always supplied to the framework impact
327
+ context. Configured context columns are read from each asset, including when
328
+ they are not retained as output passthrough columns. Stored registry context
329
+ provides fallback values for fields without an asset value. Impact metadata
330
+ records the event-aligned interpretation, source hazard units and semantics,
331
+ output units and semantics, function name/type, and context-column mapping.
332
+
333
+ Framework impact objects and top-level Python callables can run in the existing
334
+ process pool. Lambdas and closures are not picklable, so they run serially when
335
+ the worker count is implicit; explicitly requesting more than one worker for
336
+ one raises an error.
337
+
338
+ Point assets are converted to the H3 resolution recorded by the canonical
339
+ dataset. The H3 join is refined with `ST_Covers(source_geometry, asset_point)`
340
+ when source WKB is present; rows without WKB retain cell-level precision.
341
+ `source_id` and `spatial_match` (`exact_geometry` or `h3_cell`) remain in the
342
+ output. Multiple source curves for one asset/hazard/horizon/pathway raise
343
+ instead of being silently selected or aggregated, and missing asset/scenario
344
+ matches raise rather than being dropped from the output.
345
+
346
+ When assets already contain canonical H3 indexes, use
347
+ `cell_index_column="cell_index"` instead of longitude/latitude columns. This
348
+ avoids point conversion and exact source-geometry refinement:
349
+
350
+ ```python
351
+ (
352
+ HazardDataset.local("flood.parquet")
353
+ .for_assets(assets_with_cells)
354
+ .return_periods([25, 50, 100, 250, 500, 1000])
355
+ .write_parquet("portfolio-flood.parquet")
356
+ )
357
+ ```
358
+
359
+ Arrow tables can be registered directly as shown above. A `Path` reads an
360
+ asset Parquet file, while a string is treated as caller-supplied DuckDB SQL.
361
+ Output evaluation is streamed in bounded Arrow batches to compressed Parquet.
362
+ For already selected canonical rows, `distribution_from_hazard_row` remains
363
+ available as the low-level curve reconstruction utility.
364
+
365
+ The common column names `asset_id`, `longitude`/`latitude`, and `cell_index`
366
+ are inferred. Use `AssetPortfolio`, `PointColumns`, or `CellColumn` only for a
367
+ nonstandard asset schema. Worker, batch, and connection controls are grouped
368
+ under `ExecutionOptions` on `write_parquet`, keeping execution tuning out of
369
+ the normal workflow.
370
+
371
+ ## License
372
+
373
+ CRC SDK is licensed under the GNU Affero General Public License, version 3 or
374
+ later.