Skip to content

Commit 0fa4ec9

Browse files
authored
Update hazard code according to UNDRR for GFD
* GFD: update the hazard codes * GFD(Testcase): add the test cases and cassettes * GFD: update the item property
1 parent 2fb65d6 commit 0fa4ec9

File tree

4 files changed

+113
-44
lines changed

4 files changed

+113
-44
lines changed

pystac_monty/sources/gfd.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ def make_source_event_item(self, data: GFDSourceValidator) -> Item:
152152
monty = MontyExtension.ext(item)
153153
monty.episode_number = episode_number
154154
monty.country_codes = properties.cc.split(",")
155-
monty.hazard_codes = ["FL"] # GFD is a Flood related source
155+
monty.hazard_codes = ["MH0600", "nat-hyd-flo-flo", "FL"] # GFD is a Flood related source
156+
monty.hazard_codes = self.hazard_profiles.get_canonical_hazard_codes(item=item)
157+
158+
hazard_keywords = self.hazard_profiles.get_keywords(monty.hazard_codes)
159+
item.properties["keywords"] = list(set(hazard_keywords + monty.country_codes))
160+
156161
monty.compute_and_set_correlation_id(hazard_profiles=self.hazard_profiles)
157162

158163
return item
@@ -166,9 +171,9 @@ def make_hazard_event_item(self, event_item: Item, row: GFDSourceValidator) -> I
166171
hazard_item.set_collection(self.get_hazard_collection())
167172

168173
monty = MontyExtension.ext(hazard_item)
174+
monty.hazard_codes = [self.hazard_profiles.get_undrr_2025_code(hazard_codes=monty.hazard_codes)]
169175
# Hazard Detail
170176
monty.hazard_detail = HazardDetail(
171-
cluster=self.hazard_profiles.get_cluster_code(hazard_item),
172177
severity_value=row.properties.dfo_severity,
173178
severity_unit="GFD Flood Severity Score",
174179
severity_label=None,

tests/extensions/cassettes/test_gfd/GFDTest.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-
- '404'
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:33 GMT
168+
- Tue, 11 Nov 2025 05:17:39 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-
- '1'
182+
- '0'
165183
X-Fastly-Request-ID:
166-
- 82921b180b95ae3736ccbdf50bb1f0311b28c907
184+
- a9677c8df481c43a175df3bd32d1ec355febda59
167185
X-GitHub-Request-Id:
168-
- 4E22:64F80:87F27:A7133:684137F4
186+
- 8FE8:3C902F:4F53A1:584B0A:6912B97C
169187
X-Served-By:
170-
- cache-maa10223-MAA
188+
- cache-del21731-DEL
171189
X-Timer:
172-
- S1749105034.558055,VS0,VE2
190+
- S1762838259.708221,VS0,VE402
173191
expires:
174-
- Thu, 05 Jun 2025 06:33:49 GMT
192+
- Tue, 11 Nov 2025 04:30:12 GMT
175193
x-proxy-cache:
176194
- MISS
177195
status:

tests/extensions/cassettes/test_gfd/GFDTest.test_transformer_from_file_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-
- '405'
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:33 GMT
168+
- Tue, 11 Nov 2025 05:17:39 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-
- 13ab5179798bcddcf18837a7c97a101d7786f0f2
184+
- 5fbbe4e1b0141dfbbfaadec0dd662d6d76b325ca
167185
X-GitHub-Request-Id:
168-
- 4E22:64F80:87F27:A7133:684137F4
186+
- 8FE8:3C902F:4F53A1:584B0A:6912B97C
169187
X-Served-By:
170-
- cache-maa10235-MAA
188+
- cache-del21723-DEL
171189
X-Timer:
172-
- S1749105034.821123,VS0,VE1
190+
- S1762838259.497165,VS0,VE1
173191
expires:
174-
- Thu, 05 Jun 2025 06:33:49 GMT
192+
- Tue, 11 Nov 2025 04:30:12 GMT
175193
x-proxy-cache:
176194
- MISS
177195
status:

tests/extensions/test_gfd.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,31 @@ def test_transformer_from_file(self, transformer: GFDTransformer) -> None:
195195
self.assertIsNotNone(source_event_item)
196196
self.assertIsNotNone(source_hazard_item)
197197
self.assertIsNotNone(source_impact_item)
198+
199+
@parameterized.expand(load_scenarios(scenarios))
200+
@pytest.mark.vcr()
201+
def test_event_item_uses_all_codes(self, transformer: GFDTransformer) -> None:
202+
for item in transformer.get_stac_items():
203+
# write pretty json in a temporary folder
204+
item_path = get_data_file(f"temp/gfd/{item.id}.json")
205+
with open(item_path, "w") as f:
206+
json.dump(item.to_dict(), f, indent=2)
207+
item.validate(validator=self.validator)
208+
monty_item_ext = MontyExtension.ext(item)
209+
if monty_item_ext.is_source_event() and monty_item_ext.hazard_codes:
210+
# Should contain only the first code (UNDRR-ISC 2025)
211+
assert len(monty_item_ext.hazard_codes) == 3
212+
213+
@parameterized.expand(load_scenarios(scenarios))
214+
@pytest.mark.vcr()
215+
def test_hazard_item_uses_2025_code_only(self, transformer: GFDTransformer) -> None:
216+
for item in transformer.get_stac_items():
217+
# write pretty json in a temporary folder
218+
item_path = get_data_file(f"temp/gfd/{item.id}.json")
219+
with open(item_path, "w") as f:
220+
json.dump(item.to_dict(), f, indent=2)
221+
item.validate(validator=self.validator)
222+
monty_item_ext = MontyExtension.ext(item)
223+
if monty_item_ext.is_source_hazard() and monty_item_ext.hazard_codes:
224+
# Should contain only the first code (UNDRR-ISC 2025)
225+
assert len(monty_item_ext.hazard_codes) == 1

0 commit comments

Comments
 (0)