Skip to content

Commit 2ffb032

Browse files
chore(glide): Update hazard codes according to UNDRR 2025
1 parent 988b308 commit 2ffb032

File tree

5 files changed

+167
-72
lines changed

5 files changed

+167
-72
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ jobs:
3838
- name: Sync
3939
run: uv sync --all-extras
4040
- name: Test
41-
run: uv run pytest tests
41+
run: uv run pytest -k test_glide

pystac_monty/sources/glide.py

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,34 +133,38 @@ def parse_row_data(row: dict):
133133
self.transform_summary.mark_as_complete()
134134

135135
def get_hazard_codes(self, hazard: str) -> List[str]:
136+
similar_hazard_mapping = {
137+
"SL": "LS",
138+
"WV": "SS",
139+
}
140+
hazard = similar_hazard_mapping.get(hazard, hazard)
136141
hazard_mapping = {
137-
"EQ": ["GH0001", "GH0002", "GH0003", "GH0004", "GH0005"],
138-
"TC": ["MH0030", "MH0031", "MH0032"],
139-
"FL": ["nat-hyd-flo-flo"],
140-
"DR": ["MH0035"],
141-
"WF": ["EN0013"],
142-
"VO": ["GH009", "GH0013", "GH0014", "GH0015", "GH0016"],
143-
"TS": ["MH0029", "GH0006"],
144-
"CW": ["MH0040"],
145-
"EP": ["nat-bio-epi-dis"],
146-
"EC": ["MH0031"],
142+
"EQ": ["GH0101", "nat-geo-ear-gro", "EQ"],
143+
"TC": ["MH0309", "nat-met-sto-tro", "TC"],
144+
"FL": ["MH0600", "nat-hyd-flo-flo", "FL"],
145+
"DR": ["MH0401", "nat-cli-dro-dro", "DR"],
146+
"WF": ["EN0205", "nat-cli-wil-wil", "WF"],
147+
"VO": ["GH0205", "nat-geo-vol-vol", "VO"],
148+
"TS": ["MH0705", "nat-geo-ear-tsu", "TS"],
149+
"CW": ["MH0502", "nat-met-ext-col", "CW"],
150+
"EP": ["BI0101", "nat-bio-epi-dis", "OT"],
151+
"EC": ["MH0307", "nat-met-sto-ext", "EC"],
147152
"ET": ["nat-met-ext-col", "nat-met-ext-hea", "nat-met-ext-sev"],
148-
"FR": ["tec-ind-fir-fir"],
149-
"FF": ["MH0006"],
150-
"HT": ["MH0047"],
151-
"IN": ["BI0002", "BI0003"],
152-
"LS": ["nat-hyd-mmw-lan"],
153-
"MS": ["MH0051"],
154-
"ST": ["MH0003"],
155-
"SL": ["nat-hyd-mmw-lan"],
156-
"AV": ["nat-geo-mmd-ava"],
157-
"SS": ["MH0027"],
158-
"AC": ["AC"],
159-
"TO": ["MH0059"],
160-
"VW": ["MH0060"],
161-
"WV": ["MH0029", "GH0006"],
153+
"FR": ["TL0032", "tec-ind-fir-fir", "FR"],
154+
"FF": ["MH0603", "nat-hyd-flo-fla", "FF"],
155+
"HT": ["MH0501", "nat-met-ext-hea", "HT"],
156+
"LS": ["GH0300", "nat-geo-mmd-lan", "LS"], # NOTE: Equivalent to SL
157+
"MS": ["GH0303", "nat-hyd-mmw-mud", "MS"],
158+
"ST": ["MH0102", "nat-met-sto-sto", "ST"],
159+
"AV": ["MH0801", "nat-geo-mmd-ava", "AV"],
160+
"SS": ["MH0703", "nat-met-sto-sur", "SS"], # NOTE: Equivalent to WV
161+
"TO": ["MH0305", "nat-met-sto-tor", "TO"],
162+
"VW": ["MH0301", "nat-met-sto-sto", "VW"],
162163
}
163-
return hazard_mapping[hazard]
164+
if hazard not in hazard_mapping:
165+
logger.warning(f"GLIDE disaster type '{hazard}' not found in UNDRR-ISC 2025 mapping.")
166+
167+
return hazard_mapping.get(hazard, [])
164168

165169
def make_source_event_items(self, data: GlideSetValidator) -> Item | None:
166170
"""Create source event items"""
@@ -203,8 +207,14 @@ def make_source_event_items(self, data: GlideSetValidator) -> Item | None:
203207
# in the method monty.compute_and_set_correlation_id(..)
204208
monty.episode_number = 1
205209
monty.hazard_codes = self.get_hazard_codes(data.event)
210+
monty.hazard_codes = self.hazard_profiles.get_canonical_hazard_codes(item)
211+
206212
monty.country_codes = [data.geocode]
207213

214+
hazard_keywords = self.hazard_profiles.get_keywords(monty.hazard_codes)
215+
country_keywords = [data.geocode] if data.geocode else []
216+
item.properties["keywords"] = list(set(hazard_keywords + country_keywords))
217+
208218
monty.compute_and_set_correlation_id(hazard_profiles=self.hazard_profiles)
209219

210220
item.add_link(Link("via", self.data_source.source_url, "application/json", "Glide Event Data"))
@@ -227,20 +237,22 @@ def make_hazard_event_items(self, event_item: Item) -> Item:
227237
item.properties["roles"] = ["source", "hazard"]
228238

229239
monty = MontyExtension.ext(item)
240+
monty.hazard_codes = [self.hazard_profiles.get_undrr_2025_code(hazard_codes=monty.hazard_codes)]
241+
230242
monty.hazard_detail = self.get_hazard_detail(item)
231243

232244
return item
233245

234246
def get_hazard_detail(self, item: Item) -> HazardDetail:
235247
"""Get hazard detail"""
236248
# FIXME: This is not type safe
237-
magnitude = item.properties.get("magnitude")
249+
magnitude_raw = item.properties.get("magnitude")
250+
magnitude = float(magnitude_raw) if magnitude_raw.isnumeric() else -1 # -1 to bypass validation
238251

239252
return HazardDetail(
240-
cluster=self.hazard_profiles.get_cluster_code(item),
241253
# NOTE If the alphanumeric value is present in the magnitude, it is converted to 0 for now.
242254
# But the actual value of the magnitude can be found in the properties.magnitude
243-
severity_value=float(magnitude) if magnitude.isnumeric() else 0,
255+
severity_value=magnitude if magnitude else 1,
244256
severity_unit="glide",
245257
estimate_type=MontyEstimateType.PRIMARY,
246258
)

tests/extensions/cassettes/test_glide/GlideTest.test_transformer_0.yaml

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ interactions:
99
User-Agent:
1010
- Python-urllib/3.12
1111
method: GET
12-
uri: https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json
12+
uri: https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json
1313
response:
1414
body:
1515
string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
16-
\"https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json#\",\n \"title\":
16+
\"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json#\",\n \"title\":
1717
\"Monty Extension\",\n \"description\": \"STAC Monty Extension for STAC Items
1818
and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": \"This
1919
is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"$ref\":
@@ -44,7 +44,7 @@ interactions:
4444
\ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
4545
{\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
4646
\ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
47-
\"array\",\n \"contains\": {\n \"const\": \"https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json\"\n
47+
\"array\",\n \"contains\": {\n \"const\": \"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json\"\n
4848
\ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
4949
\"Monty prefixed fields\",\n \"type\": \"object\",\n \"properties\":
5050
{\n \"monty:episode_number\": {\n \"type\": \"integer\"\n
@@ -56,10 +56,11 @@ interactions:
5656
\"string\",\n \"pattern\": \"^([A-Z]{2}(?:\\\\d{4}$){0,1})|([a-z]{3}-[a-z]{3}-[a-z]{3}-[a-z]{3})|([A-Z]{2})$\"\n
5757
\ }\n },\n \"monty:corr_id\": {\n \"type\":
5858
\"string\"\n },\n \"monty:hazard_detail\": {\n \"type\":
59-
\"object\",\n \"required\": [\n \"cluster\"\n ],\n
60-
\ \"properties\": {\n \"cluster\": {\n \"type\":
61-
\"string\"\n },\n \"severity_value\": {\n \"type\":
62-
\"number\"\n },\n \"severity_unit\": {\n \"type\":
59+
\"object\",\n \"required\": [\n \"severity_value\",\n
60+
\ \"severity_unit\"\n ],\n \"properties\": {\n
61+
\ \"severity_value\": {\n \"type\": \"number\",\n \"description\":
62+
\"The estimated maximum hazard intensity/magnitude/severity value, as a number,
63+
without the units.\"\n },\n \"severity_unit\": {\n \"type\":
6364
\"string\"\n },\n \"severity_label\": {\n \"type\":
6465
\"string\"\n },\n \"estimate_type\": {\n \"$ref\":
6566
\"#/definitions/estimate_type\"\n }\n },\n \"additionalProperties\":
@@ -126,9 +127,26 @@ interactions:
126127
\ }\n }\n },\n \"is_hazard\": {\n \"properties\": {\n
127128
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
128129
1,\n \"contains\": {\n \"const\": \"hazard\"\n }\n
129-
\ },\n \"monty:hazard_codes\": {\n \"maxItems\": 1\n
130-
\ }\n }\n },\n \"is_impact\": {\n \"properties\": {\n
131-
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
130+
\ },\n \"monty:hazard_codes\": {\n \"minItems\": 1,\n
131+
\ \"maxItems\": 3,\n \"uniqueItems\": true,\n \"$comment\":
132+
\"REQUIRED: Exactly 1 UNDRR-ISC 2025 code (format: 2 letters + 4 digits, e.g.,
133+
GH0101, MH0600). OPTIONAL: At most 1 GLIDE code (2 uppercase letters, e.g.,
134+
FL, EQ) and at most 1 EM-DAT code (nat-xxx-xxx-xxx format). LIMITATION: JSON
135+
Schema draft-07 cannot fully enforce 'at most 1 per type' - custom validation
136+
may be needed to prevent multiple codes of the same classification type.\",\n
137+
\ \"contains\": {\n \"type\": \"string\",\n \"pattern\":
138+
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"At least one UNDRR-ISC
139+
2025 code is required\"\n },\n \"items\": {\n \"type\":
140+
\"string\",\n \"anyOf\": [\n {\n \"pattern\":
141+
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"UNDRR-ISC 2025 format:
142+
2 uppercase letters + 4 digits (e.g., GH0101, MH0600)\"\n },\n
143+
\ {\n \"pattern\": \"^[A-Z]{2}$\",\n \"$comment\":
144+
\"GLIDE format: 2 uppercase letters (e.g., FL, EQ, TC)\"\n },\n
145+
\ {\n \"pattern\": \"^[a-z]{3}-[a-z]{3}-[a-z]{3}-[a-z]{3}$\",\n
146+
\ \"$comment\": \"EM-DAT format: 4 groups of 3 lowercase letters
147+
separated by dashes (e.g., nat-hyd-flo-flo)\"\n }\n ]\n
148+
\ }\n }\n }\n },\n \"is_impact\": {\n \"properties\":
149+
{\n \"roles\": {\n \"type\": \"array\",\n \"minItems\":
132150
1,\n \"contains\": {\n \"const\": \"impact\"\n }\n
133151
\ }\n }\n }\n }\n}\n"
134152
headers:
@@ -137,21 +155,21 @@ interactions:
137155
Access-Control-Allow-Origin:
138156
- '*'
139157
Age:
140-
- '407'
158+
- '0'
141159
Cache-Control:
142160
- max-age=600
143161
Connection:
144162
- close
145163
Content-Length:
146-
- '8990'
164+
- '10393'
147165
Content-Type:
148166
- application/json; charset=utf-8
149167
Date:
150-
- Thu, 05 Jun 2025 06:30:36 GMT
168+
- Wed, 12 Nov 2025 05:59:51 GMT
151169
ETag:
152-
- '"68362515-231e"'
170+
- '"690c54cc-2899"'
153171
Last-Modified:
154-
- Tue, 27 May 2025 20:48:21 GMT
172+
- Thu, 06 Nov 2025 07:57:00 GMT
155173
Server:
156174
- GitHub.com
157175
Vary:
@@ -161,17 +179,17 @@ interactions:
161179
X-Cache:
162180
- HIT
163181
X-Cache-Hits:
164-
- '3'
182+
- '1'
165183
X-Fastly-Request-ID:
166-
- be7f23d6f3c72e9267bbfee752be77484e2d5f5d
184+
- b2ddb6d8cb39c92ad83a967a2f9ebf4e0e6b7ce9
167185
X-GitHub-Request-Id:
168-
- 4E22:64F80:87F27:A7133:684137F4
186+
- 9A3E:2F62DB:5BE57:72BDD:69141429
169187
X-Served-By:
170-
- cache-maa10248-MAA
188+
- cache-del21728-DEL
171189
X-Timer:
172-
- S1749105037.564670,VS0,VE0
190+
- S1762927191.191765,VS0,VE1
173191
expires:
174-
- Thu, 05 Jun 2025 06:33:49 GMT
192+
- Wed, 12 Nov 2025 05:09:22 GMT
175193
x-proxy-cache:
176194
- MISS
177195
status:

tests/extensions/cassettes/test_glide/GlideTest.test_transformer_with_file_data_0.yaml

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ interactions:
99
User-Agent:
1010
- Python-urllib/3.12
1111
method: GET
12-
uri: https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json
12+
uri: https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json
1313
response:
1414
body:
1515
string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
16-
\"https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json#\",\n \"title\":
16+
\"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json#\",\n \"title\":
1717
\"Monty Extension\",\n \"description\": \"STAC Monty Extension for STAC Items
1818
and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": \"This
1919
is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"$ref\":
@@ -44,7 +44,7 @@ interactions:
4444
\ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
4545
{\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
4646
\ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
47-
\"array\",\n \"contains\": {\n \"const\": \"https://ifrcgo.org/monty-stac-extension/v1.0.0/schema.json\"\n
47+
\"array\",\n \"contains\": {\n \"const\": \"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json\"\n
4848
\ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
4949
\"Monty prefixed fields\",\n \"type\": \"object\",\n \"properties\":
5050
{\n \"monty:episode_number\": {\n \"type\": \"integer\"\n
@@ -56,10 +56,11 @@ interactions:
5656
\"string\",\n \"pattern\": \"^([A-Z]{2}(?:\\\\d{4}$){0,1})|([a-z]{3}-[a-z]{3}-[a-z]{3}-[a-z]{3})|([A-Z]{2})$\"\n
5757
\ }\n },\n \"monty:corr_id\": {\n \"type\":
5858
\"string\"\n },\n \"monty:hazard_detail\": {\n \"type\":
59-
\"object\",\n \"required\": [\n \"cluster\"\n ],\n
60-
\ \"properties\": {\n \"cluster\": {\n \"type\":
61-
\"string\"\n },\n \"severity_value\": {\n \"type\":
62-
\"number\"\n },\n \"severity_unit\": {\n \"type\":
59+
\"object\",\n \"required\": [\n \"severity_value\",\n
60+
\ \"severity_unit\"\n ],\n \"properties\": {\n
61+
\ \"severity_value\": {\n \"type\": \"number\",\n \"description\":
62+
\"The estimated maximum hazard intensity/magnitude/severity value, as a number,
63+
without the units.\"\n },\n \"severity_unit\": {\n \"type\":
6364
\"string\"\n },\n \"severity_label\": {\n \"type\":
6465
\"string\"\n },\n \"estimate_type\": {\n \"$ref\":
6566
\"#/definitions/estimate_type\"\n }\n },\n \"additionalProperties\":
@@ -126,9 +127,26 @@ interactions:
126127
\ }\n }\n },\n \"is_hazard\": {\n \"properties\": {\n
127128
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
128129
1,\n \"contains\": {\n \"const\": \"hazard\"\n }\n
129-
\ },\n \"monty:hazard_codes\": {\n \"maxItems\": 1\n
130-
\ }\n }\n },\n \"is_impact\": {\n \"properties\": {\n
131-
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
130+
\ },\n \"monty:hazard_codes\": {\n \"minItems\": 1,\n
131+
\ \"maxItems\": 3,\n \"uniqueItems\": true,\n \"$comment\":
132+
\"REQUIRED: Exactly 1 UNDRR-ISC 2025 code (format: 2 letters + 4 digits, e.g.,
133+
GH0101, MH0600). OPTIONAL: At most 1 GLIDE code (2 uppercase letters, e.g.,
134+
FL, EQ) and at most 1 EM-DAT code (nat-xxx-xxx-xxx format). LIMITATION: JSON
135+
Schema draft-07 cannot fully enforce 'at most 1 per type' - custom validation
136+
may be needed to prevent multiple codes of the same classification type.\",\n
137+
\ \"contains\": {\n \"type\": \"string\",\n \"pattern\":
138+
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"At least one UNDRR-ISC
139+
2025 code is required\"\n },\n \"items\": {\n \"type\":
140+
\"string\",\n \"anyOf\": [\n {\n \"pattern\":
141+
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"UNDRR-ISC 2025 format:
142+
2 uppercase letters + 4 digits (e.g., GH0101, MH0600)\"\n },\n
143+
\ {\n \"pattern\": \"^[A-Z]{2}$\",\n \"$comment\":
144+
\"GLIDE format: 2 uppercase letters (e.g., FL, EQ, TC)\"\n },\n
145+
\ {\n \"pattern\": \"^[a-z]{3}-[a-z]{3}-[a-z]{3}-[a-z]{3}$\",\n
146+
\ \"$comment\": \"EM-DAT format: 4 groups of 3 lowercase letters
147+
separated by dashes (e.g., nat-hyd-flo-flo)\"\n }\n ]\n
148+
\ }\n }\n }\n },\n \"is_impact\": {\n \"properties\":
149+
{\n \"roles\": {\n \"type\": \"array\",\n \"minItems\":
132150
1,\n \"contains\": {\n \"const\": \"impact\"\n }\n
133151
\ }\n }\n }\n }\n}\n"
134152
headers:
@@ -137,21 +155,21 @@ interactions:
137155
Access-Control-Allow-Origin:
138156
- '*'
139157
Age:
140-
- '408'
158+
- '0'
141159
Cache-Control:
142160
- max-age=600
143161
Connection:
144162
- close
145163
Content-Length:
146-
- '8990'
164+
- '10393'
147165
Content-Type:
148166
- application/json; charset=utf-8
149167
Date:
150-
- Thu, 05 Jun 2025 06:30:36 GMT
168+
- Wed, 12 Nov 2025 05:59:51 GMT
151169
ETag:
152-
- '"68362515-231e"'
170+
- '"690c54cc-2899"'
153171
Last-Modified:
154-
- Tue, 27 May 2025 20:48:21 GMT
172+
- Thu, 06 Nov 2025 07:57:00 GMT
155173
Server:
156174
- GitHub.com
157175
Vary:
@@ -161,17 +179,17 @@ interactions:
161179
X-Cache:
162180
- HIT
163181
X-Cache-Hits:
164-
- '3'
182+
- '1'
165183
X-Fastly-Request-ID:
166-
- becdd2d45a9b24a533697871952bbf1408be8c8e
184+
- d21cf0d761e2ec2a177eeb9f1e8031c8e6dc4241
167185
X-GitHub-Request-Id:
168-
- 4E22:64F80:87F27:A7133:684137F4
186+
- 9A3E:2F62DB:5BE57:72BDD:69141429
169187
X-Served-By:
170-
- cache-maa10242-MAA
188+
- cache-del21749-DEL
171189
X-Timer:
172-
- S1749105037.804198,VS0,VE1
190+
- S1762927191.313823,VS0,VE49
173191
expires:
174-
- Thu, 05 Jun 2025 06:33:49 GMT
192+
- Wed, 12 Nov 2025 05:09:22 GMT
175193
x-proxy-cache:
176194
- MISS
177195
status:

0 commit comments

Comments
 (0)