crc-framework 0.5.0__tar.gz → 0.5.2__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 (136) hide show
  1. {crc_framework-0.5.0 → crc_framework-0.5.2}/Cargo.lock +3 -3
  2. {crc_framework-0.5.0 → crc_framework-0.5.2}/Cargo.toml +1 -1
  3. {crc_framework-0.5.0 → crc_framework-0.5.2}/PKG-INFO +2 -2
  4. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/registry.rs +2 -2
  5. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/lib.rs +2 -1
  6. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/impact_registry_conformance.rs +11 -5
  7. {crc_framework-0.5.0 → crc_framework-0.5.2}/pyproject.toml +1 -1
  8. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/__init__.py +4 -0
  9. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/_core.pyi +1 -0
  10. crc_framework-0.5.2/python/crc_framework/contracts/impact/catalogs/hazard-exposures.json +26 -0
  11. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/definitions.json +3 -3
  12. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/manifest.json +7 -2
  13. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/impacts/__init__.py +128 -18
  14. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/transforms/__init__.py +4 -0
  15. {crc_framework-0.5.0 → crc_framework-0.5.2}/src/impact.rs +7 -1
  16. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_impact_expression.py +169 -0
  17. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/impact_contracts/cases.py +1 -1
  18. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/impact_contracts/generate.py +31 -0
  19. {crc_framework-0.5.0 → crc_framework-0.5.2}/uv.lock +177 -322
  20. {crc_framework-0.5.0 → crc_framework-0.5.2}/.all-contributorsrc +0 -0
  21. {crc_framework-0.5.0 → crc_framework-0.5.2}/.github/workflows/npm.yaml +0 -0
  22. {crc_framework-0.5.0 → crc_framework-0.5.2}/.github/workflows/release.yaml +0 -0
  23. {crc_framework-0.5.0 → crc_framework-0.5.2}/.gitignore +0 -0
  24. {crc_framework-0.5.0 → crc_framework-0.5.2}/.python-version +0 -0
  25. {crc_framework-0.5.0 → crc_framework-0.5.2}/LICENSE +0 -0
  26. {crc_framework-0.5.0 → crc_framework-0.5.2}/README.md +0 -0
  27. {crc_framework-0.5.0 → crc_framework-0.5.2}/THIRD_PARTY_NOTICES.md +0 -0
  28. {crc_framework-0.5.0 → crc_framework-0.5.2}/build.rs +0 -0
  29. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/Cargo.toml +0 -0
  30. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/canonical.rs +0 -0
  31. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/constants.rs +0 -0
  32. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/distribution/lmoments.rs +0 -0
  33. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/distribution.rs +0 -0
  34. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/error.rs +0 -0
  35. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/definition.rs +0 -0
  36. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/error.rs +0 -0
  37. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/expectation.rs +0 -0
  38. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/json.rs +0 -0
  39. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/kernel.rs +0 -0
  40. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/mod.rs +0 -0
  41. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/pushforward.rs +0 -0
  42. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/quadrature.rs +0 -0
  43. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/record.rs +0 -0
  44. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/reference.rs +0 -0
  45. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/impact/source.rs +0 -0
  46. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/metrics.rs +0 -0
  47. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/ag_params.rs +0 -0
  48. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/energy_params.rs +0 -0
  49. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/flood_params.rs +0 -0
  50. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/mod.rs +0 -0
  51. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/precipitation_thresholds.rs +0 -0
  52. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/spei_params.rs +0 -0
  53. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/climate/wind_params.rs +0 -0
  54. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/curve_fitting.rs +0 -0
  55. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/mod.rs +0 -0
  56. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/spatial/continent_r4.rs +0 -0
  57. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/spatial/geography_r5.rs +0 -0
  58. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/spatial/ipcc.rs +0 -0
  59. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/reference/spatial/mod.rs +0 -0
  60. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/risk.rs +0 -0
  61. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/spatial.rs +0 -0
  62. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/src/transform.rs +0 -0
  63. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/canonical_consumer.rs +0 -0
  64. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/censored_quantile_fit.rs +0 -0
  65. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/impact_contract_canonical.rs +0 -0
  66. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/impact_definition_conformance.rs +0 -0
  67. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/impact_operations.rs +0 -0
  68. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/quantile_fit_golden.rs +0 -0
  69. {crc_framework-0.5.0 → crc_framework-0.5.2}/crates/core/tests/quantile_fit_golden.txt +0 -0
  70. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/.nvmrc +0 -0
  71. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/README.md +0 -0
  72. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/build.mjs +0 -0
  73. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/package-lock.json +0 -0
  74. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/package.json +0 -0
  75. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/publish.mjs +0 -0
  76. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/src/index.d.ts +0 -0
  77. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/src/index.js +0 -0
  78. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/benchmark.mjs +0 -0
  79. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/browser.html +0 -0
  80. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/browser.mjs +0 -0
  81. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/consumer.mjs +0 -0
  82. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/parity.test.mjs +0 -0
  83. {crc_framework-0.5.0 → crc_framework-0.5.2}/js/test/publish.test.mjs +0 -0
  84. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/constants.py +0 -0
  85. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/__init__.py +0 -0
  86. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/error-codes.json +0 -0
  87. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/native-capabilities.json +0 -0
  88. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/registry-overrides.json +0 -0
  89. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/semantics-constants.json +0 -0
  90. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/unit-aliases.json +0 -0
  91. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/canonical-json.json +0 -0
  92. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/evaluation-records.json +0 -0
  93. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/monotonicity.json +0 -0
  94. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/operations.json +0 -0
  95. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/point-evaluations.json +0 -0
  96. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/registry-references.json +0 -0
  97. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/spatial-policies.json +0 -0
  98. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/spatial-records.json +0 -0
  99. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/impact-definition-1.0.schema.json +0 -0
  100. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/impact-evaluation-record-1.0.schema.json +0 -0
  101. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/registry-impact-reference-1.0.schema.json +0 -0
  102. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/spatial-combination-policy-1.0.schema.json +0 -0
  103. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/spatial-impact-evaluation-record-1.0.schema.json +0 -0
  104. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/__init__.py +0 -0
  105. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/base.py +0 -0
  106. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/empirical.py +0 -0
  107. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/fitted.py +0 -0
  108. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/fitting.py +0 -0
  109. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/hurdle.py +0 -0
  110. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/point_mass.py +0 -0
  111. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/distributions/tabulated.py +0 -0
  112. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/metrics/__init__.py +0 -0
  113. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/metrics/distribution.py +0 -0
  114. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/metrics/microscores.py +0 -0
  115. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/metrics/risk.py +0 -0
  116. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/metrics/spanning.py +0 -0
  117. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/models.py +0 -0
  118. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/py.typed +0 -0
  119. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/spatial.py +0 -0
  120. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/transforms/base.py +0 -0
  121. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/transforms/builtin.py +0 -0
  122. {crc_framework-0.5.0 → crc_framework-0.5.2}/python/crc_framework/transforms/climate.py +0 -0
  123. {crc_framework-0.5.0 → crc_framework-0.5.2}/src/lib.rs +0 -0
  124. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/fixtures/canonical-quantiles.json +0 -0
  125. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_consumer_parity.py +0 -0
  126. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_impact_contracts.py +0 -0
  127. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_impacts.py +0 -0
  128. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_lmoments.py +0 -0
  129. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/test_sdk.py +0 -0
  130. {crc_framework-0.5.0 → crc_framework-0.5.2}/tests/typing_smoke.py +0 -0
  131. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/benchmark_lmoments.py +0 -0
  132. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/benchmarks/lmoments-audit-m2-pro.json +0 -0
  133. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/generate_consumer_fixtures.py +0 -0
  134. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/impact_contracts/reference.py +0 -0
  135. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/impact_contracts/schemas.py +0 -0
  136. {crc_framework-0.5.0 → crc_framework-0.5.2}/tools/requirements-lmoments-benchmark.txt +0 -0
@@ -67,7 +67,7 @@ dependencies = [
67
67
 
68
68
  [[package]]
69
69
  name = "crc-framework-core"
70
- version = "0.5.0"
70
+ version = "0.5.2"
71
71
  dependencies = [
72
72
  "h3o",
73
73
  "phf",
@@ -79,7 +79,7 @@ dependencies = [
79
79
 
80
80
  [[package]]
81
81
  name = "crc-framework-rs"
82
- version = "0.5.0"
82
+ version = "0.5.2"
83
83
  dependencies = [
84
84
  "crc-framework-core",
85
85
  "h3o",
@@ -88,7 +88,7 @@ dependencies = [
88
88
 
89
89
  [[package]]
90
90
  name = "crc-framework-wasm"
91
- version = "0.5.0"
91
+ version = "0.5.2"
92
92
  dependencies = [
93
93
  "crc-framework-core",
94
94
  "js-sys",
@@ -12,7 +12,7 @@ readme = "README.md"
12
12
  members = ["crates/core"]
13
13
 
14
14
  [workspace.package]
15
- version = "0.5.0"
15
+ version = "0.5.2"
16
16
 
17
17
  [lib]
18
18
  name = "_core"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crc-framework
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
6
6
  Classifier: Operating System :: OS Independent
@@ -19,7 +19,7 @@ Summary: Rust computation core and Python bindings for the Climate Risk Commons
19
19
  Keywords: climate-risk,probability-distributions,risk-metrics,rust
20
20
  Author: Riskthinking.AI
21
21
  License-Expression: AGPL-3.0-or-later
22
- Requires-Python: >=3.9
22
+ Requires-Python: >=3.11
23
23
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
24
24
  Project-URL: Homepage, https://github.com/RiskThinking/crc-framework-rs
25
25
  Project-URL: Issues, https://github.com/RiskThinking/crc-framework-rs/issues
@@ -14,7 +14,7 @@ use crate::reference::climate::{
14
14
  };
15
15
 
16
16
  const REGISTRY_ID: &str = "crc-climate-impacts";
17
- const REGISTRY_REVISION: &str = "2026.10.0";
17
+ const REGISTRY_REVISION: &str = "2026.10.1";
18
18
  const RESOLVER_VERSION: &str = "crc-impact-registry/1";
19
19
  const FWI_MIDPOINT: f64 = 35.65;
20
20
  const FWI_STEEPNESS: f64 = 0.2051873853077659;
@@ -114,7 +114,7 @@ const FACTORS: &[FactorMeta] = &[
114
114
  FactorMeta {
115
115
  factor: "rx1day",
116
116
  context: &["cell"],
117
- calibration: ["rx1day", "mm", "damage_ratio", "1"],
117
+ calibration: ["rx1day", "mm/day", "damage_ratio", "1"],
118
118
  branches: &[],
119
119
  },
120
120
  FactorMeta {
@@ -22,7 +22,8 @@ pub use impact::{
22
22
  DiscreteDistribution, Expectation, GaussKronrodOptions, HistogramExposure, ImpactAtom,
23
23
  ImpactContractError, ImpactDefinition, ImpactDistribution, ImpactEvaluationRecord,
24
24
  ImpactSource, MethodRequest, RecordedSource, RegistryImpactReference, RequestedMethod,
25
- ResolutionContext, ResolvedMethod, SnapshotRef, array_content_sha256, registry_identity,
25
+ ResolutionContext, ResolvedMethod, SnapshotRef, array_content_sha256, manifest_json,
26
+ registry_identity,
26
27
  };
27
28
  pub use metrics::{
28
29
  BinaryOutcome, DistributionStatistics, Microscore, MicroscoreSuite, ScenarioMetadata,
@@ -7,7 +7,10 @@ use std::str::FromStr;
7
7
 
8
8
  use crc_framework_core::{
9
9
  ImpactContext, ImpactDefinition, ImpactRegistry,
10
- impact::{RegistryImpactReference, ResolutionContext, manifest_json, manifest_sha256},
10
+ impact::{
11
+ RegistryImpactReference, ResolutionContext, manifest_json, manifest_sha256,
12
+ registry_identity,
13
+ },
11
14
  };
12
15
  use h3o::{CellIndex, Resolution};
13
16
  use serde_json::{Value, json};
@@ -29,7 +32,7 @@ fn reference(
29
32
  let text = json!({
30
33
  "schema_version": "1.0",
31
34
  "registry_id": "crc-climate-impacts",
32
- "registry_revision": "2026.10.0",
35
+ "registry_revision": registry_identity().1,
33
36
  "registry_manifest_sha256": manifest_sha256(),
34
37
  "resolver_semantics_version": "crc-impact-registry/1",
35
38
  "factor": factor,
@@ -157,7 +160,10 @@ fn installed_manifest_is_stable() {
157
160
  );
158
161
  assert_eq!(manifest_sha256(), manifest_sha256());
159
162
  assert!(manifest_json().contains("\"registry_id\":\"crc-climate-impacts\""));
160
- assert!(manifest_json().contains("\"registry_revision\":\"2026.10.0\""));
163
+ assert!(manifest_json().contains(&format!(
164
+ "\"registry_revision\":\"{}\"",
165
+ registry_identity().1
166
+ )));
161
167
  assert!(!manifest_json().contains(' '));
162
168
  let manifest: Value = serde_json::from_str(manifest_json()).unwrap();
163
169
  let cooling = manifest["factors"]
@@ -275,7 +281,7 @@ fn resolution_checks_identity_context_overrides_and_fallbacks() {
275
281
  assert_eq!(error.code, "registry_unavailable");
276
282
  assert_eq!(error.path, "/registry_revision");
277
283
 
278
- wrong_revision["registry_revision"] = json!("2026.10.0");
284
+ wrong_revision["registry_revision"] = json!(registry_identity().1);
279
285
  wrong_revision["registry_id"] = json!("other-registry");
280
286
  let error = RegistryImpactReference::from_json(&wrong_revision.to_string())
281
287
  .unwrap()
@@ -318,7 +324,7 @@ fn resolution_checks_identity_context_overrides_and_fallbacks() {
318
324
  &json!({
319
325
  "schema_version": "1.0",
320
326
  "registry_id": "crc-climate-impacts",
321
- "registry_revision": "2026.10.0",
327
+ "registry_revision": registry_identity().1,
322
328
  "registry_manifest_sha256": manifest_sha256(),
323
329
  "resolver_semantics_version": "crc-impact-registry/1",
324
330
  "factor": "spei",
@@ -6,7 +6,7 @@ build-backend = "maturin"
6
6
  name = "crc-framework"
7
7
  description = "Rust computation core and Python bindings for the Climate Risk Commons Framework"
8
8
  readme = "README.md"
9
- requires-python = ">=3.9"
9
+ requires-python = ">=3.11"
10
10
  dependencies = ["numpy>=1.23"]
11
11
  authors = [{ name = "Riskthinking.AI" }]
12
12
  license = "AGPL-3.0-or-later"
@@ -51,7 +51,9 @@ from .transforms import (
51
51
  ImpactRegistry,
52
52
  LinearImpact,
53
53
  PiecewiseLinearImpact,
54
+ RegistryImpactReference,
54
55
  SigmoidImpact,
56
+ impact_registry_manifest,
55
57
  impacts,
56
58
  )
57
59
 
@@ -93,6 +95,7 @@ __all__ = [
93
95
  "PointMassDistribution",
94
96
  "QuantileFitDiagnostics",
95
97
  "QuantileFitResult",
98
+ "RegistryImpactReference",
96
99
  "RiskFactor",
97
100
  "RiskResult",
98
101
  "ScenarioMetadata",
@@ -107,6 +110,7 @@ __all__ = [
107
110
  "fit_hurdle_quantiles",
108
111
  "fit_quantiles",
109
112
  "generate_microscores",
113
+ "impact_registry_manifest",
110
114
  "impacts",
111
115
  "lookup_continent",
112
116
  "lookup_geography",
@@ -328,5 +328,6 @@ class NativeImpactReference:
328
328
  ) -> NativeImpactExpression: ...
329
329
 
330
330
  def impact_registry_identity() -> Tuple[str, str, str, str]: ...
331
+ def impact_registry_manifest_json() -> str: ...
331
332
  def impact_engine() -> Tuple[str, str]: ...
332
333
  def impact_array_digest(shape: Sequence[int], values: Sequence[float]) -> str: ...
@@ -0,0 +1,26 @@
1
+ {
2
+ "description": "External hazard identifiers that denote a registry factor's exposure. Readers take the exposure variable and unit from that factor's calibration in the installed registry manifest. Names that are absent are unknown.",
3
+ "entries": [
4
+ {
5
+ "id": "rx1day",
6
+ "aliases": [
7
+ "Rx1day"
8
+ ],
9
+ "factor": "rx1day"
10
+ },
11
+ {
12
+ "id": "fd",
13
+ "aliases": [
14
+ "FD"
15
+ ],
16
+ "factor": "frost_days"
17
+ },
18
+ {
19
+ "id": "cdd",
20
+ "aliases": [
21
+ "CDD"
22
+ ],
23
+ "factor": "maximum_consecutive_dry_days"
24
+ }
25
+ ]
26
+ }
@@ -175,9 +175,9 @@
175
175
  {
176
176
  "name": "origin-registry-legacy-fallback",
177
177
  "description": "Registry origin with composite lookup keys, a recorded fallback, a null override and full context.",
178
- "input_json": "{\"schema_version\":\"1.0\",\"semantics_version\":\"crc-impact/1\",\"calibration\":{\"exposure_variable\":\"rx1day\",\"exposure_unit\":\"mm\",\"impact_metric\":\"damage_ratio\",\"impact_unit\":\"1\"},\"model\":{\"kind\":\"sigmoid\",\"midpoint\":682.5,\"steepness\":0.007,\"origin_adjustment\":\"subtract_at_zero\"},\"provenance\":{\"origin\":{\"kind\":\"registry\",\"registry_id\":\"crc-climate-impacts\",\"registry_revision\":\"2026.10.0\",\"registry_manifest_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"resolver_semantics_version\":\"crc-impact-registry/1\",\"factor\":\"rx1day\",\"context\":{\"cell\":\"8a2a1072b59ffff\",\"country\":null,\"continent\":null,\"building_type\":null,\"historic_mean\":null},\"overrides\":{\"zero_below\":null,\"steepness\":0.007},\"fallback_policy\":\"legacy_defaults\",\"lookups\":[{\"table_id\":\"precipitation.rx1day_threshold\",\"table_sha256\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"requested_key\":{\"h3_resolution\":\"4\",\"cell\":\"0842a107fffffff\"},\"resolved_key\":\"global_default\",\"status\":\"fallback\"}]}}}",
179
- "canonical_json": "{\"calibration\":{\"exposure_unit\":\"mm\",\"exposure_variable\":\"rx1day\",\"impact_metric\":\"damage_ratio\",\"impact_unit\":\"1\"},\"clamp\":null,\"model\":{\"amplitude\":1.0,\"kind\":\"sigmoid\",\"midpoint\":682.5,\"offset\":0.0,\"origin_adjustment\":\"subtract_at_zero\",\"steepness\":0.007,\"zero_below\":null},\"provenance\":{\"derived_from\":[],\"origin\":{\"context\":{\"building_type\":null,\"cell\":\"8a2a1072b59ffff\",\"continent\":null,\"country\":null,\"historic_mean\":null},\"factor\":\"rx1day\",\"fallback_policy\":\"legacy_defaults\",\"kind\":\"registry\",\"lookups\":[{\"requested_key\":{\"cell\":\"0842a107fffffff\",\"h3_resolution\":\"4\"},\"resolved_key\":\"global_default\",\"status\":\"fallback\",\"table_id\":\"precipitation.rx1day_threshold\",\"table_sha256\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"overrides\":{\"steepness\":0.007,\"zero_below\":null},\"registry_id\":\"crc-climate-impacts\",\"registry_manifest_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"registry_revision\":\"2026.10.0\",\"resolver_semantics_version\":\"crc-impact-registry/1\"},\"sources\":[]},\"schema_version\":\"1.0\",\"semantics_version\":\"crc-impact/1\"}",
180
- "content_hash": "sha256:6f09aa8623198c6721d5046aade2318235a7aeaf3bd20051d139fe7254bfcf8f",
178
+ "input_json": "{\"schema_version\":\"1.0\",\"semantics_version\":\"crc-impact/1\",\"calibration\":{\"exposure_variable\":\"rx1day\",\"exposure_unit\":\"mm/day\",\"impact_metric\":\"damage_ratio\",\"impact_unit\":\"1\"},\"model\":{\"kind\":\"sigmoid\",\"midpoint\":682.5,\"steepness\":0.007,\"origin_adjustment\":\"subtract_at_zero\"},\"provenance\":{\"origin\":{\"kind\":\"registry\",\"registry_id\":\"crc-climate-impacts\",\"registry_revision\":\"2026.10.0\",\"registry_manifest_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"resolver_semantics_version\":\"crc-impact-registry/1\",\"factor\":\"rx1day\",\"context\":{\"cell\":\"8a2a1072b59ffff\",\"country\":null,\"continent\":null,\"building_type\":null,\"historic_mean\":null},\"overrides\":{\"zero_below\":null,\"steepness\":0.007},\"fallback_policy\":\"legacy_defaults\",\"lookups\":[{\"table_id\":\"precipitation.rx1day_threshold\",\"table_sha256\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"requested_key\":{\"h3_resolution\":\"4\",\"cell\":\"0842a107fffffff\"},\"resolved_key\":\"global_default\",\"status\":\"fallback\"}]}}}",
179
+ "canonical_json": "{\"calibration\":{\"exposure_unit\":\"mm/day\",\"exposure_variable\":\"rx1day\",\"impact_metric\":\"damage_ratio\",\"impact_unit\":\"1\"},\"clamp\":null,\"model\":{\"amplitude\":1.0,\"kind\":\"sigmoid\",\"midpoint\":682.5,\"offset\":0.0,\"origin_adjustment\":\"subtract_at_zero\",\"steepness\":0.007,\"zero_below\":null},\"provenance\":{\"derived_from\":[],\"origin\":{\"context\":{\"building_type\":null,\"cell\":\"8a2a1072b59ffff\",\"continent\":null,\"country\":null,\"historic_mean\":null},\"factor\":\"rx1day\",\"fallback_policy\":\"legacy_defaults\",\"kind\":\"registry\",\"lookups\":[{\"requested_key\":{\"cell\":\"0842a107fffffff\",\"h3_resolution\":\"4\"},\"resolved_key\":\"global_default\",\"status\":\"fallback\",\"table_id\":\"precipitation.rx1day_threshold\",\"table_sha256\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"overrides\":{\"steepness\":0.007,\"zero_below\":null},\"registry_id\":\"crc-climate-impacts\",\"registry_manifest_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"registry_revision\":\"2026.10.0\",\"resolver_semantics_version\":\"crc-impact-registry/1\"},\"sources\":[]},\"schema_version\":\"1.0\",\"semantics_version\":\"crc-impact/1\"}",
180
+ "content_hash": "sha256:dedd30a0a381474285018a0095c5cb29526305ca98041d8489f89ffe736fd442",
181
181
  "schema_valid": true
182
182
  },
183
183
  {
@@ -40,6 +40,11 @@
40
40
  "sha256": "0f34f334aafa2b56bb1897fe24c824888f3f88daec345ef84bbe7ae802733af7",
41
41
  "bytes": 4843
42
42
  },
43
+ {
44
+ "path": "catalogs/hazard-exposures.json",
45
+ "sha256": "114a9e2dc76e4b4f4d2fbb1f03bab21a5a35db28b003f566e2253074165128a4",
46
+ "bytes": 585
47
+ },
43
48
  {
44
49
  "path": "catalogs/native-capabilities.json",
45
50
  "sha256": "6f28fde56d640b4b21b2e9549061ff58fc9a0872624294891bf849050efa82ec",
@@ -67,8 +72,8 @@
67
72
  },
68
73
  {
69
74
  "path": "fixtures/definitions.json",
70
- "sha256": "54460a41ab7446b017c6a9d353da6615037cbabaa9f223703ba4db9bad7085c4",
71
- "bytes": 91026
75
+ "sha256": "970e1d95c574978e49233038b1db398cfc04ec5639cb1d6e697fd53defc77ea3",
76
+ "bytes": 91034
72
77
  },
73
78
  {
74
79
  "path": "fixtures/evaluation-records.json",
@@ -62,8 +62,36 @@ def _freeze(value: Any) -> Any:
62
62
  return value
63
63
 
64
64
 
65
+ def _thaw(value: Any) -> Any:
66
+ """Return plain lists and dicts so ``json.dumps`` can encode frozen configs."""
67
+ if isinstance(value, Mapping):
68
+ return {key: _thaw(item) for key, item in value.items()}
69
+ if isinstance(value, (list, tuple)):
70
+ return [_thaw(item) for item in value]
71
+ return value
72
+
73
+
65
74
  def _dumps(value: Mapping[str, Any]) -> str:
66
- return json.dumps(value, allow_nan=False, separators=(",", ":"))
75
+ return json.dumps(_thaw(value), allow_nan=False, separators=(",", ":"))
76
+
77
+
78
+ _CONTEXT_KEYS = frozenset({"cell", "country", "continent", "building_type", "historic_mean"})
79
+ _POINT_KEYS = frozenset({"x", "y"})
80
+
81
+
82
+ def _unknown_sort_key(key: Any) -> bytes:
83
+ if isinstance(key, str):
84
+ return key.encode("utf-8")
85
+ return str(key).encode("utf-8")
86
+
87
+
88
+ def _reject_unknown_keys(mapping: Mapping[str, Any], allowed: frozenset[str], prefix: str) -> None:
89
+ unknown = [key for key in mapping if not isinstance(key, str) or key not in allowed]
90
+ if not unknown:
91
+ return
92
+ key = min(unknown, key=_unknown_sort_key)
93
+ location = f"{prefix}/{key}" if prefix else f"/{key}"
94
+ raise _impact_error("unknown_field", location, "unknown field")
67
95
 
68
96
 
69
97
  def _model_definition(
@@ -88,8 +116,9 @@ def _model_definition(
88
116
 
89
117
  def _points(points: Sequence[Union[tuple[float, float], Mapping[str, float]]]) -> list[dict[str, float]]:
90
118
  converted: list[dict[str, float]] = []
91
- for point in points:
119
+ for index, point in enumerate(points):
92
120
  if isinstance(point, Mapping):
121
+ _reject_unknown_keys(point, _POINT_KEYS, f"/model/points/{index}")
93
122
  converted.append({"x": float(point["x"]), "y": float(point["y"])})
94
123
  else:
95
124
  converted.append({"x": float(point[0]), "y": float(point[1])})
@@ -104,6 +133,7 @@ def _cell(value: Union[int, str, None]) -> Optional[str]:
104
133
 
105
134
  def _context_fields(
106
135
  context: Union[TransformContext, Mapping[str, Any], None],
136
+ prefix: str = "/context",
107
137
  ) -> tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[float]]:
108
138
  if context is None:
109
139
  return None, None, None, None, None
@@ -116,6 +146,7 @@ def _context_fields(
116
146
  context.historic_mean,
117
147
  )
118
148
  if isinstance(context, Mapping):
149
+ _reject_unknown_keys(context, _CONTEXT_KEYS, prefix)
119
150
  historic = context.get("historic_mean")
120
151
  return (
121
152
  _cell(cast(Union[int, str, None], context.get("cell"))),
@@ -127,6 +158,27 @@ def _context_fields(
127
158
  raise TypeError("context must be a TransformContext or mapping")
128
159
 
129
160
 
161
+ def _context_document(
162
+ context: Union[TransformContext, Mapping[str, Any], None],
163
+ prefix: str,
164
+ ) -> Optional[dict[str, Any]]:
165
+ if context is None:
166
+ return None
167
+ cell, country, continent, building_type, historic_mean = _context_fields(context, prefix)
168
+ document: dict[str, Any] = {}
169
+ if cell is not None:
170
+ document["cell"] = cell
171
+ if country is not None:
172
+ document["country"] = country
173
+ if continent is not None:
174
+ document["continent"] = continent
175
+ if building_type is not None:
176
+ document["building_type"] = building_type
177
+ if historic_mean is not None:
178
+ document["historic_mean"] = historic_mean
179
+ return document
180
+
181
+
130
182
  def _method_arguments(
131
183
  method: Union[str, "GaussKronrodOptions"],
132
184
  ) -> tuple[str, Optional[float], Optional[float], Optional[int]]:
@@ -312,6 +364,71 @@ class ImpactDistribution:
312
364
  )
313
365
 
314
366
 
367
+ class RegistryImpactReference:
368
+ """Pinned registry selection. ``resolve`` merges call context over its defaults."""
369
+
370
+ def __init__(self, native: _core.NativeImpactReference) -> None:
371
+ self._native = native
372
+
373
+ @classmethod
374
+ def from_json(cls, text: str) -> "RegistryImpactReference":
375
+ return cls(_core.NativeImpactReference.from_json(text))
376
+
377
+ @classmethod
378
+ def for_factor(
379
+ cls,
380
+ factor: Union[RiskFactor, str],
381
+ *,
382
+ context_defaults: Union[TransformContext, Mapping[str, Any], None] = None,
383
+ overrides: Optional[Mapping[str, Optional[float]]] = None,
384
+ fallback_policy: str = "error",
385
+ registry_revision: Optional[str] = None,
386
+ registry_manifest_sha256: Optional[str] = None,
387
+ ) -> "RegistryImpactReference":
388
+ """Pin the installed registry. The reference keeps its identity until resolved."""
389
+ registry_id, revision, resolver, manifest = _core.impact_registry_identity()
390
+ payload: dict[str, Any] = {
391
+ "schema_version": "1.0",
392
+ "registry_id": registry_id,
393
+ "registry_revision": registry_revision or revision,
394
+ "registry_manifest_sha256": registry_manifest_sha256 or manifest,
395
+ "resolver_semantics_version": resolver,
396
+ "factor": factor.value if isinstance(factor, RiskFactor) else factor,
397
+ "fallback_policy": fallback_policy,
398
+ }
399
+ if overrides is not None:
400
+ payload["overrides"] = dict(overrides)
401
+ defaults = _context_document(context_defaults, "/context_defaults")
402
+ if defaults is not None:
403
+ payload["context_defaults"] = defaults
404
+ return cls.from_json(_dumps(payload))
405
+
406
+ def to_json(self) -> str:
407
+ return self._native.canonical_json()
408
+
409
+ @property
410
+ def reference_hash(self) -> str:
411
+ return self._native.reference_hash
412
+
413
+ def resolve(
414
+ self,
415
+ context: Union[TransformContext, Mapping[str, Any], None] = None,
416
+ ) -> "ImpactExpression":
417
+ cell, country, continent, building_type, historic_mean = _context_fields(context)
418
+ return ImpactExpression(
419
+ self._native.resolve(cell, country, continent, building_type, historic_mean)
420
+ )
421
+
422
+ def __reduce__(self) -> tuple[Any, tuple[str]]:
423
+ return (self.from_json, (self.to_json(),))
424
+
425
+
426
+ def impact_registry_manifest() -> Mapping[str, Any]:
427
+ """Frozen installed registry manifest, including factor calibrations."""
428
+ loaded = json.loads(_core.impact_registry_manifest_json())
429
+ return cast(Mapping[str, Any], _freeze(loaded))
430
+
431
+
315
432
  class ImpactExpression:
316
433
  """Immutable compiled impact definition."""
317
434
 
@@ -432,28 +549,21 @@ class ImpactExpression:
432
549
  factor: Union[RiskFactor, str],
433
550
  *,
434
551
  context: Union[TransformContext, Mapping[str, Any], None] = None,
552
+ context_defaults: Union[TransformContext, Mapping[str, Any], None] = None,
435
553
  overrides: Optional[Mapping[str, Optional[float]]] = None,
436
554
  fallback_policy: str = "error",
437
555
  registry_revision: Optional[str] = None,
438
556
  registry_manifest_sha256: Optional[str] = None,
439
557
  ) -> "ImpactExpression":
440
- registry_id, revision, resolver, manifest = _core.impact_registry_identity()
441
- payload: dict[str, Any] = {
442
- "schema_version": "1.0",
443
- "registry_id": registry_id,
444
- "registry_revision": registry_revision or revision,
445
- "registry_manifest_sha256": registry_manifest_sha256 or manifest,
446
- "resolver_semantics_version": resolver,
447
- "factor": factor.value if isinstance(factor, RiskFactor) else factor,
448
- "fallback_policy": fallback_policy,
449
- }
450
- if overrides is not None:
451
- payload["overrides"] = dict(overrides)
452
- reference = _core.NativeImpactReference.from_json(_dumps(payload))
453
- cell, country, continent, building_type, historic_mean = _context_fields(context)
454
- return cls(
455
- reference.resolve(cell, country, continent, building_type, historic_mean)
558
+ reference = RegistryImpactReference.for_factor(
559
+ factor,
560
+ context_defaults=context_defaults,
561
+ overrides=overrides,
562
+ fallback_policy=fallback_policy,
563
+ registry_revision=registry_revision,
564
+ registry_manifest_sha256=registry_manifest_sha256,
456
565
  )
566
+ return reference.resolve(context)
457
567
 
458
568
  def to_json(self) -> str:
459
569
  return self._native.canonical_json()
@@ -10,6 +10,8 @@ from crc_framework.impacts import (
10
10
  ImpactEvaluationRecord,
11
11
  ImpactEvaluationResult,
12
12
  ImpactExpression,
13
+ RegistryImpactReference,
14
+ impact_registry_manifest,
13
15
  )
14
16
 
15
17
  __all__ = [
@@ -28,7 +30,9 @@ __all__ = [
28
30
  "ImpactRegistry",
29
31
  "LinearImpact",
30
32
  "PiecewiseLinearImpact",
33
+ "RegistryImpactReference",
31
34
  "SigmoidImpact",
32
35
  "Transform",
36
+ "impact_registry_manifest",
33
37
  "impacts",
34
38
  ]
@@ -4,7 +4,7 @@ use crc_framework_core::{
4
4
  CurveParameters, EmpiricalDistribution, GaussKronrodOptions, HistogramExposure,
5
5
  ImpactContractError, ImpactDefinition, ImpactDistribution, ImpactEvaluationRecord,
6
6
  ImpactSource, MethodRequest, RecordedSource, RegistryImpactReference, ResolutionContext,
7
- SnapshotRef, array_content_sha256, registry_identity,
7
+ SnapshotRef, array_content_sha256, manifest_json, registry_identity,
8
8
  };
9
9
  use pyo3::prelude::*;
10
10
 
@@ -582,6 +582,11 @@ fn impact_registry_identity() -> (&'static str, &'static str, &'static str, &'st
582
582
  registry_identity()
583
583
  }
584
584
 
585
+ #[pyfunction]
586
+ fn impact_registry_manifest_json() -> &'static str {
587
+ manifest_json()
588
+ }
589
+
585
590
  #[pyfunction]
586
591
  fn impact_engine() -> (&'static str, &'static str) {
587
592
  engine()
@@ -678,6 +683,7 @@ pub fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
678
683
  module.add_class::<PyRecord>()?;
679
684
  module.add_class::<PyReference>()?;
680
685
  module.add_function(wrap_pyfunction!(impact_registry_identity, module)?)?;
686
+ module.add_function(wrap_pyfunction!(impact_registry_manifest_json, module)?)?;
681
687
  module.add_function(wrap_pyfunction!(impact_engine, module)?)?;
682
688
  module.add_function(wrap_pyfunction!(impact_array_digest, module)?)?;
683
689
  Ok(())