crc-framework 0.5.1__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.1 → crc_framework-0.5.2}/Cargo.lock +3 -3
  2. {crc_framework-0.5.1 → crc_framework-0.5.2}/Cargo.toml +1 -1
  3. {crc_framework-0.5.1 → crc_framework-0.5.2}/PKG-INFO +1 -1
  4. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/registry.rs +2 -2
  5. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/impact_registry_conformance.rs +11 -5
  6. crc_framework-0.5.2/python/crc_framework/contracts/impact/catalogs/hazard-exposures.json +26 -0
  7. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/definitions.json +3 -3
  8. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/manifest.json +7 -2
  9. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_impact_expression.py +33 -2
  10. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/impact_contracts/cases.py +1 -1
  11. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/impact_contracts/generate.py +31 -0
  12. {crc_framework-0.5.1 → crc_framework-0.5.2}/.all-contributorsrc +0 -0
  13. {crc_framework-0.5.1 → crc_framework-0.5.2}/.github/workflows/npm.yaml +0 -0
  14. {crc_framework-0.5.1 → crc_framework-0.5.2}/.github/workflows/release.yaml +0 -0
  15. {crc_framework-0.5.1 → crc_framework-0.5.2}/.gitignore +0 -0
  16. {crc_framework-0.5.1 → crc_framework-0.5.2}/.python-version +0 -0
  17. {crc_framework-0.5.1 → crc_framework-0.5.2}/LICENSE +0 -0
  18. {crc_framework-0.5.1 → crc_framework-0.5.2}/README.md +0 -0
  19. {crc_framework-0.5.1 → crc_framework-0.5.2}/THIRD_PARTY_NOTICES.md +0 -0
  20. {crc_framework-0.5.1 → crc_framework-0.5.2}/build.rs +0 -0
  21. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/Cargo.toml +0 -0
  22. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/canonical.rs +0 -0
  23. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/constants.rs +0 -0
  24. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/distribution/lmoments.rs +0 -0
  25. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/distribution.rs +0 -0
  26. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/error.rs +0 -0
  27. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/definition.rs +0 -0
  28. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/error.rs +0 -0
  29. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/expectation.rs +0 -0
  30. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/json.rs +0 -0
  31. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/kernel.rs +0 -0
  32. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/mod.rs +0 -0
  33. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/pushforward.rs +0 -0
  34. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/quadrature.rs +0 -0
  35. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/record.rs +0 -0
  36. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/reference.rs +0 -0
  37. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/impact/source.rs +0 -0
  38. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/lib.rs +0 -0
  39. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/metrics.rs +0 -0
  40. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/ag_params.rs +0 -0
  41. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/energy_params.rs +0 -0
  42. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/flood_params.rs +0 -0
  43. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/mod.rs +0 -0
  44. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/precipitation_thresholds.rs +0 -0
  45. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/spei_params.rs +0 -0
  46. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/climate/wind_params.rs +0 -0
  47. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/curve_fitting.rs +0 -0
  48. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/mod.rs +0 -0
  49. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/spatial/continent_r4.rs +0 -0
  50. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/spatial/geography_r5.rs +0 -0
  51. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/spatial/ipcc.rs +0 -0
  52. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/reference/spatial/mod.rs +0 -0
  53. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/risk.rs +0 -0
  54. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/spatial.rs +0 -0
  55. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/src/transform.rs +0 -0
  56. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/canonical_consumer.rs +0 -0
  57. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/censored_quantile_fit.rs +0 -0
  58. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/impact_contract_canonical.rs +0 -0
  59. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/impact_definition_conformance.rs +0 -0
  60. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/impact_operations.rs +0 -0
  61. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/quantile_fit_golden.rs +0 -0
  62. {crc_framework-0.5.1 → crc_framework-0.5.2}/crates/core/tests/quantile_fit_golden.txt +0 -0
  63. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/.nvmrc +0 -0
  64. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/README.md +0 -0
  65. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/build.mjs +0 -0
  66. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/package-lock.json +0 -0
  67. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/package.json +0 -0
  68. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/publish.mjs +0 -0
  69. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/src/index.d.ts +0 -0
  70. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/src/index.js +0 -0
  71. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/benchmark.mjs +0 -0
  72. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/browser.html +0 -0
  73. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/browser.mjs +0 -0
  74. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/consumer.mjs +0 -0
  75. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/parity.test.mjs +0 -0
  76. {crc_framework-0.5.1 → crc_framework-0.5.2}/js/test/publish.test.mjs +0 -0
  77. {crc_framework-0.5.1 → crc_framework-0.5.2}/pyproject.toml +0 -0
  78. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/__init__.py +0 -0
  79. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/_core.pyi +0 -0
  80. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/constants.py +0 -0
  81. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/__init__.py +0 -0
  82. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/error-codes.json +0 -0
  83. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/native-capabilities.json +0 -0
  84. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/registry-overrides.json +0 -0
  85. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/semantics-constants.json +0 -0
  86. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/catalogs/unit-aliases.json +0 -0
  87. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/canonical-json.json +0 -0
  88. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/evaluation-records.json +0 -0
  89. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/monotonicity.json +0 -0
  90. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/operations.json +0 -0
  91. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/point-evaluations.json +0 -0
  92. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/registry-references.json +0 -0
  93. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/spatial-policies.json +0 -0
  94. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/fixtures/spatial-records.json +0 -0
  95. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/impact-definition-1.0.schema.json +0 -0
  96. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/impact-evaluation-record-1.0.schema.json +0 -0
  97. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/registry-impact-reference-1.0.schema.json +0 -0
  98. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/spatial-combination-policy-1.0.schema.json +0 -0
  99. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/contracts/impact/schemas/spatial-impact-evaluation-record-1.0.schema.json +0 -0
  100. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/__init__.py +0 -0
  101. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/base.py +0 -0
  102. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/empirical.py +0 -0
  103. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/fitted.py +0 -0
  104. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/fitting.py +0 -0
  105. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/hurdle.py +0 -0
  106. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/point_mass.py +0 -0
  107. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/distributions/tabulated.py +0 -0
  108. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/impacts/__init__.py +0 -0
  109. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/metrics/__init__.py +0 -0
  110. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/metrics/distribution.py +0 -0
  111. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/metrics/microscores.py +0 -0
  112. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/metrics/risk.py +0 -0
  113. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/metrics/spanning.py +0 -0
  114. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/models.py +0 -0
  115. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/py.typed +0 -0
  116. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/spatial.py +0 -0
  117. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/transforms/__init__.py +0 -0
  118. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/transforms/base.py +0 -0
  119. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/transforms/builtin.py +0 -0
  120. {crc_framework-0.5.1 → crc_framework-0.5.2}/python/crc_framework/transforms/climate.py +0 -0
  121. {crc_framework-0.5.1 → crc_framework-0.5.2}/src/impact.rs +0 -0
  122. {crc_framework-0.5.1 → crc_framework-0.5.2}/src/lib.rs +0 -0
  123. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/fixtures/canonical-quantiles.json +0 -0
  124. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_consumer_parity.py +0 -0
  125. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_impact_contracts.py +0 -0
  126. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_impacts.py +0 -0
  127. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_lmoments.py +0 -0
  128. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/test_sdk.py +0 -0
  129. {crc_framework-0.5.1 → crc_framework-0.5.2}/tests/typing_smoke.py +0 -0
  130. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/benchmark_lmoments.py +0 -0
  131. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/benchmarks/lmoments-audit-m2-pro.json +0 -0
  132. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/generate_consumer_fixtures.py +0 -0
  133. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/impact_contracts/reference.py +0 -0
  134. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/impact_contracts/schemas.py +0 -0
  135. {crc_framework-0.5.1 → crc_framework-0.5.2}/tools/requirements-lmoments-benchmark.txt +0 -0
  136. {crc_framework-0.5.1 → crc_framework-0.5.2}/uv.lock +0 -0
@@ -67,7 +67,7 @@ dependencies = [
67
67
 
68
68
  [[package]]
69
69
  name = "crc-framework-core"
70
- version = "0.5.1"
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.1"
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.1"
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.1"
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.1
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
@@ -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 {
@@ -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",
@@ -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",
@@ -480,13 +480,13 @@ def test_registry_manifest_digest_matches_identity() -> None:
480
480
  assert manifest["registry_revision"] == revision
481
481
  assert manifest["resolver_semantics_version"] == resolver
482
482
  factors = {item["factor"]: item for item in manifest["factors"]}
483
- assert factors["rx1day"]["calibration"]["exposure_unit"] == "mm"
483
+ assert factors["rx1day"]["calibration"]["exposure_unit"] == "mm/day"
484
484
  agricultural = factors["cooling_degree_days"]["branch_calibrations"]["agricultural"]
485
485
  assert agricultural["impact_metric"] == "crop_yield_change"
486
486
  with pytest.raises(TypeError):
487
487
  manifest["registry_id"] = "other" # type: ignore[index]
488
488
  with pytest.raises(TypeError):
489
- factors["rx1day"]["calibration"]["exposure_unit"] = "mm/day" # type: ignore[index]
489
+ factors["rx1day"]["calibration"]["exposure_unit"] = "cm" # type: ignore[index]
490
490
 
491
491
  impact_module = importlib.import_module("crc_framework.impacts")
492
492
  assert impact_module.impact_registry_manifest is impact_registry_manifest
@@ -494,6 +494,37 @@ def test_registry_manifest_digest_matches_identity() -> None:
494
494
  assert crc_framework.impact_registry_manifest is impact_registry_manifest
495
495
 
496
496
 
497
+ def test_hazard_exposure_catalog_uses_factor_calibrations() -> None:
498
+ catalog = load_impact_contract_file("catalogs/hazard-exposures.json")
499
+ factors = {item["factor"]: item for item in impact_registry_manifest()["factors"]}
500
+ seen: set[str] = set()
501
+ expected = {
502
+ "rx1day": "rx1day",
503
+ "fd": "frost_days",
504
+ "cdd": "maximum_consecutive_dry_days",
505
+ }
506
+ found = {}
507
+ for entry in catalog["entries"]:
508
+ factor = factors[entry["factor"]]
509
+ calibration = factor["calibration"]
510
+ for branch in factor.get("branch_calibrations", {}).values():
511
+ assert branch["exposure_variable"] == calibration["exposure_variable"]
512
+ assert branch["exposure_unit"] == calibration["exposure_unit"]
513
+ for name in (entry["id"], *entry["aliases"]):
514
+ assert name not in seen
515
+ seen.add(name)
516
+ found[entry["id"]] = entry["factor"]
517
+ assert calibration["exposure_variable"]
518
+ assert calibration["exposure_unit"]
519
+ assert found == expected
520
+ assert factors["rx1day"]["calibration"] == {
521
+ "exposure_variable": "rx1day",
522
+ "exposure_unit": "mm/day",
523
+ "impact_metric": "damage_ratio",
524
+ "impact_unit": "1",
525
+ }
526
+
527
+
497
528
  def test_empirical_and_point_mass_expectations_are_exact() -> None:
498
529
  expression = ImpactExpression.linear(2.0, calibration=CALIBRATION)
499
530
  # Empirical quantiles are piecewise linear through the order statistics,
@@ -218,7 +218,7 @@ def definition_cases() -> Dict[str, List[Dict[str, Any]]]:
218
218
  "Registry origin with composite lookup keys, a recorded fallback, a null override and full context.",
219
219
  definition(
220
220
  {"kind": "sigmoid", "midpoint": 682.5, "steepness": 0.007, "origin_adjustment": "subtract_at_zero"},
221
- calibration={"exposure_variable": "rx1day", "exposure_unit": "mm", "impact_metric": "damage_ratio", "impact_unit": "1"},
221
+ calibration={"exposure_variable": "rx1day", "exposure_unit": "mm/day", "impact_metric": "damage_ratio", "impact_unit": "1"},
222
222
  provenance={
223
223
  "origin": {
224
224
  "kind": "registry",
@@ -79,6 +79,22 @@ UNIT_ALIASES = {
79
79
  "aliases": {"metre": "m", "metres": "m", "meter": "m", "meters": "m", "day": "d", "days": "d", "fraction": "1", "ratio": "1", "dimensionless": "1"},
80
80
  }
81
81
 
82
+ # External hazard ids that denote a registry factor. Exposure variable and unit
83
+ # are read from that factor's calibration; this catalog does not copy them.
84
+ # Unlisted hazards are unknown and need an explicit caller declaration.
85
+ HAZARD_EXPOSURES = {
86
+ "description": (
87
+ "External hazard identifiers that denote a registry factor's exposure. "
88
+ "Readers take the exposure variable and unit from that factor's calibration "
89
+ "in the installed registry manifest. Names that are absent are unknown."
90
+ ),
91
+ "entries": [
92
+ {"id": "rx1day", "aliases": ["Rx1day"], "factor": "rx1day"},
93
+ {"id": "fd", "aliases": ["FD"], "factor": "frost_days"},
94
+ {"id": "cdd", "aliases": ["CDD"], "factor": "maximum_consecutive_dry_days"},
95
+ ],
96
+ }
97
+
82
98
  OVERRIDE_MEANINGS = {
83
99
  "sigmoid": "minimum_damage and maximum_damage set curve levels (offset and offset + amplitude), not a final clamp.",
84
100
  "hot_days": "minimum_damage and maximum_damage set the final clamp; null clears a limit.",
@@ -123,6 +139,19 @@ def _override_catalog() -> Dict[str, Any]:
123
139
  return {"resolver_semantics_version": ref.RESOLVER_SEMANTICS_VERSION, "factors": factors}
124
140
 
125
141
 
142
+ def _check_hazard_exposures(catalog: Dict[str, Any]) -> None:
143
+ """Names are unique and every factor is one the reference registry knows."""
144
+ seen: set[str] = set()
145
+ for entry in catalog["entries"]:
146
+ factor = entry["factor"]
147
+ if factor not in ref.REGISTRY_FACTORS:
148
+ raise AssertionError(f"hazard exposure factor {factor} is not a registry factor")
149
+ for name in (entry["id"], *entry["aliases"]):
150
+ if name in seen:
151
+ raise AssertionError(f"hazard exposure name {name} is repeated")
152
+ seen.add(name)
153
+
154
+
126
155
  def _json(value: Any) -> bytes:
127
156
  return (json.dumps(value, indent=2, ensure_ascii=False) + "\n").encode("utf-8")
128
157
 
@@ -218,6 +247,8 @@ def build() -> Dict[str, bytes]:
218
247
  {"codes": {code: {"stage": stage, "meaning": meaning} for code, (stage, meaning) in ERROR_CODES.items()}}
219
248
  )
220
249
  files["catalogs/unit-aliases.json"] = _json(UNIT_ALIASES)
250
+ files["catalogs/hazard-exposures.json"] = _json(HAZARD_EXPOSURES)
251
+ _check_hazard_exposures(HAZARD_EXPOSURES)
221
252
  used = {
222
253
  case["error"]["code"]
223
254
  for path in FIXTURE_CONTRACTS
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes