Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 40 additions & 28 deletions pystac_monty/sources/glide.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,34 +133,38 @@ def parse_row_data(row: dict):
self.transform_summary.mark_as_complete()

def get_hazard_codes(self, hazard: str) -> List[str]:
similar_hazard_mapping = {
"SL": "LS",
"WV": "SS",
}
hazard = similar_hazard_mapping.get(hazard, hazard)
hazard_mapping = {
"EQ": ["GH0001", "GH0002", "GH0003", "GH0004", "GH0005"],
"TC": ["MH0030", "MH0031", "MH0032"],
"FL": ["nat-hyd-flo-flo"],
"DR": ["MH0035"],
"WF": ["EN0013"],
"VO": ["GH009", "GH0013", "GH0014", "GH0015", "GH0016"],
"TS": ["MH0029", "GH0006"],
"CW": ["MH0040"],
"EP": ["nat-bio-epi-dis"],
"EC": ["MH0031"],
"EQ": ["GH0101", "nat-geo-ear-gro", "EQ"],
"TC": ["MH0309", "nat-met-sto-tro", "TC"],
"FL": ["MH0600", "nat-hyd-flo-flo", "FL"],
"DR": ["MH0401", "nat-cli-dro-dro", "DR"],
"WF": ["EN0205", "nat-cli-wil-wil", "WF"],
"VO": ["GH0205", "nat-geo-vol-vol", "VO"],
"TS": ["MH0705", "nat-geo-ear-tsu", "TS"],
"CW": ["MH0502", "nat-met-ext-col", "CW"],
"EP": ["BI0101", "nat-bio-epi-dis", "OT"],
"EC": ["MH0307", "nat-met-sto-ext", "EC"],
"ET": ["nat-met-ext-col", "nat-met-ext-hea", "nat-met-ext-sev"],
"FR": ["tec-ind-fir-fir"],
"FF": ["MH0006"],
"HT": ["MH0047"],
"IN": ["BI0002", "BI0003"],
"LS": ["nat-hyd-mmw-lan"],
"MS": ["MH0051"],
"ST": ["MH0003"],
"SL": ["nat-hyd-mmw-lan"],
"AV": ["nat-geo-mmd-ava"],
"SS": ["MH0027"],
"AC": ["AC"],
"TO": ["MH0059"],
"VW": ["MH0060"],
"WV": ["MH0029", "GH0006"],
"FR": ["TL0032", "tec-ind-fir-fir", "FR"],
"FF": ["MH0603", "nat-hyd-flo-fla", "FF"],
"HT": ["MH0501", "nat-met-ext-hea", "HT"],
"LS": ["GH0300", "nat-geo-mmd-lan", "LS"], # NOTE: Equivalent to SL
"MS": ["GH0303", "nat-hyd-mmw-mud", "MS"],
"ST": ["MH0102", "nat-met-sto-sto", "ST"],
"AV": ["MH0801", "nat-geo-mmd-ava", "AV"],
"SS": ["MH0703", "nat-met-sto-sur", "SS"], # NOTE: Equivalent to WV
"TO": ["MH0305", "nat-met-sto-tor", "TO"],
"VW": ["MH0301", "nat-met-sto-sto", "VW"],
}
return hazard_mapping[hazard]
if hazard not in hazard_mapping:
logger.warning(f"GLIDE disaster type '{hazard}' not found in UNDRR-ISC 2025 mapping.")

return hazard_mapping.get(hazard, [])

def make_source_event_items(self, data: GlideSetValidator) -> Item | None:
"""Create source event items"""
Expand Down Expand Up @@ -203,8 +207,14 @@ def make_source_event_items(self, data: GlideSetValidator) -> Item | None:
# in the method monty.compute_and_set_correlation_id(..)
monty.episode_number = 1
monty.hazard_codes = self.get_hazard_codes(data.event)
monty.hazard_codes = self.hazard_profiles.get_canonical_hazard_codes(item)

monty.country_codes = [data.geocode]

hazard_keywords = self.hazard_profiles.get_keywords(monty.hazard_codes)
country_keywords = [data.geocode] if data.geocode else []
item.properties["keywords"] = list(set(hazard_keywords + country_keywords))

monty.compute_and_set_correlation_id(hazard_profiles=self.hazard_profiles)

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

monty = MontyExtension.ext(item)
monty.hazard_codes = [self.hazard_profiles.get_undrr_2025_code(hazard_codes=monty.hazard_codes)]

monty.hazard_detail = self.get_hazard_detail(item)

return item

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

return HazardDetail(
cluster=self.hazard_profiles.get_cluster_code(item),
# NOTE If the alphanumeric value is present in the magnitude, it is converted to 0 for now.
# But the actual value of the magnitude can be found in the properties.magnitude
severity_value=float(magnitude) if magnitude.isnumeric() else 0,
severity_value=magnitude if magnitude else -1,
severity_unit="glide",
estimate_type=MontyEstimateType.PRIMARY,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
interactions:
- request:
body: null
headers:
Connection:
- close
Host:
- ifrcgo.org
User-Agent:
- Python-urllib/3.12
method: GET
uri: https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json
response:
body:
string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
\"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json#\",\n \"title\":
\"Monty Extension\",\n \"description\": \"STAC Monty Extension for STAC Items
and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\": \"This
is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"$ref\":
\"#/definitions/stac_extensions\"\n },\n {\n \"type\":
\"object\",\n \"required\": [\n \"type\",\n \"properties\"\n
\ ],\n \"properties\": {\n \"type\": {\n \"const\":
\"Feature\"\n },\n \"properties\": {\n \"allOf\":
[\n {\n \"$comment\": \"Require fields here
for Item Properties.\",\n \"required\": [\n \"monty:country_codes\",\n
\ \"monty:hazard_codes\",\n \"monty:corr_id\",\n
\ \"roles\"\n ]\n },\n {\n
\ \"$ref\": \"#/definitions/fields\"\n },\n
\ {\n \"$ref\": \"#/definitions/roles\"\n }\n
\ ]\n }\n }\n }\n ]\n },\n
\ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n
\ \"allOf\": [\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
\ },\n {\n \"type\": \"object\",\n \"required\":
[\n \"type\",\n \"providers\",\n \"license\"\n
\ ],\n \"properties\": {\n \"type\": {\n \"const\":
\"Collection\"\n }\n }\n },\n {\n \"$ref\":
\"#/definitions/roles\"\n }\n ],\n \"anyOf\": [\n {\n
\ \"$comment\": \"This is the schema for the fields in Summaries.
By default, only checks the existence of the properties, but not the schema
of the summaries.\",\n \"required\": [\n \"summaries\"\n
\ ],\n \"properties\": {\n \"summaries\": {\n
\ \"required\": [\n \"monty:country_codes\",\n
\ \"monty:hazard_codes\"\n ]\n }\n }\n
\ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
{\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
\ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
\"array\",\n \"contains\": {\n \"const\": \"https://ifrcgo.org/monty-stac-extension/v1.1.0/schema.json\"\n
\ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
\"Monty prefixed fields\",\n \"type\": \"object\",\n \"properties\":
{\n \"monty:episode_number\": {\n \"type\": \"integer\"\n
\ },\n \"monty:country_codes\": {\n \"type\": \"array\",\n
\ \"items\": {\n \"type\": \"string\",\n \"pattern\":
\"^([A-Z]{3})|AB9$\",\n \"$comment\": \"AB9 is a special code for
the Abyei area used by IDMC\"\n }\n },\n \"monty:hazard_codes\":
{\n \"type\": \"array\",\n \"items\": {\n \"type\":
\"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
\ }\n },\n \"monty:corr_id\": {\n \"type\":
\"string\"\n },\n \"monty:hazard_detail\": {\n \"type\":
\"object\",\n \"required\": [\n \"severity_value\",\n
\ \"severity_unit\"\n ],\n \"properties\": {\n
\ \"severity_value\": {\n \"type\": \"number\",\n \"description\":
\"The estimated maximum hazard intensity/magnitude/severity value, as a number,
without the units.\"\n },\n \"severity_unit\": {\n \"type\":
\"string\"\n },\n \"severity_label\": {\n \"type\":
\"string\"\n },\n \"estimate_type\": {\n \"$ref\":
\"#/definitions/estimate_type\"\n }\n },\n \"additionalProperties\":
true\n },\n \"monty:impact_detail\": {\n \"type\":
\"object\",\n \"properties\": {\n \"category\": {\n \"type\":
\"string\",\n \"enum\": [\n \"people\",\n \"crops\",\n
\ \"women\",\n \"men\",\n \"children_0_4\",\n
\ \"children_5_9\",\n \"children_10_14\",\n \"children_15_19\",\n
\ \"adult_20_24\",\n \"adult_25_29\",\n \"adult_30_34\",\n
\ \"adult_35_39\",\n \"adult_40_44\",\n \"adult_45_49\",\n
\ \"adult_50_54\",\n \"adult_55_59\",\n \"adult_60_64\",\n
\ \"elderly\",\n \"wheelchair_users\",\n \"roads\",\n
\ \"railways\",\n \"vulnerable_employment\",\n
\ \"buildings\",\n \"reconstruction_costs\",\n
\ \"hospitals\",\n \"schools\",\n \"education_centers\",\n
\ \"local_currency\",\n \"global_currency\",\n
\ \"local_currency_adj\",\n \"global_currency_adj\",\n
\ \"usd_uncertain\",\n \"cattle\",\n \"aid_general\",\n
\ \"ifrc_contribution\",\n \"ifrc_requested\",\n
\ \"alertscore\",\n \"total_affected\",\n \"households\"\n
\ ]\n },\n \"type\": {\n \"type\":
\"string\",\n \"enum\": [\n \"unspecified\",\n
\ \"unaffected\",\n \"damaged\",\n \"destroyed\",\n
\ \"potentially_damaged\",\n \"affected_total\",\n
\ \"affected_direct\",\n \"affected_indirect\",\n
\ \"death\",\n \"missing\",\n \"injured\",\n
\ \"evacuated\",\n \"relocated\",\n \"assisted\",\n
\ \"shelter_emergency\",\n \"shelter_temporary\",\n
\ \"shelter_longterm\",\n \"in_need\",\n \"targeted\",\n
\ \"disrupted\",\n \"cost\",\n \"homeless\",\n
\ \"displaced_internal\",\n \"displaced_external\",\n
\ \"displaced_total\",\n \"alertscore\",\n \"potentially_affected\",\n
\ \"highest_risk\"\n ]\n },\n \"value\":
{\n \"type\": \"number\"\n },\n \"unit\":
{\n \"type\": \"string\"\n },\n \"estimate_type\":
{\n \"$ref\": \"#/definitions/estimate_type\"\n },\n
\ \"description\": {\n \"type\": \"string\"\n }\n
\ },\n \"additionalProperties\": false\n }\n },\n
\ \"patternProperties\": {\n \"^(?!monty:)\": {\n \"$comment\":
\"Prevent additional monty prefixed field\"\n }\n },\n \"additionalProperties\":
false\n },\n \"estimate_type\": {\n \"type\": \"string\",\n \"enum\":
[\n \"primary\",\n \"secondary\",\n \"modelled\"\n ]\n
\ },\n \"roles\": {\n \"$comment\": \"Roles field\",\n \"oneOf\":
[\n {\n \"$comment\": \"Reference Event\",\n \"allOf\":
[\n {\n \"$ref\": \"#/definitions/is_event\"\n },\n
\ {\n \"$ref\": \"#/definitions/is_reference\"\n }\n
\ ]\n },\n {\n \"$comment\": \"Source Event\",\n
\ \"allOf\": [\n {\n \"$ref\": \"#/definitions/is_event\"\n
\ },\n {\n \"$ref\": \"#/definitions/is_source\"\n
\ }\n ]\n },\n {\n \"$comment\":
\"Hazard\",\n \"allOf\": [\n {\n \"$ref\":
\"#/definitions/is_hazard\"\n }\n ]\n },\n {\n
\ \"$comment\": \"Impact\",\n \"allOf\": [\n {\n
\ \"$ref\": \"#/definitions/is_impact\"\n }\n ]\n
\ }\n ]\n },\n \"is_event\": {\n \"properties\": {\n
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
2,\n \"contains\": {\n \"const\": \"event\"\n }\n
\ }\n }\n },\n \"is_reference\": {\n \"properties\":
{\n \"roles\": {\n \"type\": \"array\",\n \"minItems\":
2,\n \"contains\": {\n \"const\": \"reference\"\n }\n
\ }\n }\n },\n \"is_source\": {\n \"properties\": {\n
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
2,\n \"contains\": {\n \"const\": \"source\"\n }\n
\ }\n }\n },\n \"is_hazard\": {\n \"properties\": {\n
\ \"roles\": {\n \"type\": \"array\",\n \"minItems\":
1,\n \"contains\": {\n \"const\": \"hazard\"\n }\n
\ },\n \"monty:hazard_codes\": {\n \"minItems\": 1,\n
\ \"maxItems\": 3,\n \"uniqueItems\": true,\n \"$comment\":
\"REQUIRED: Exactly 1 UNDRR-ISC 2025 code (format: 2 letters + 4 digits, e.g.,
GH0101, MH0600). OPTIONAL: At most 1 GLIDE code (2 uppercase letters, e.g.,
FL, EQ) and at most 1 EM-DAT code (nat-xxx-xxx-xxx format). LIMITATION: JSON
Schema draft-07 cannot fully enforce 'at most 1 per type' - custom validation
may be needed to prevent multiple codes of the same classification type.\",\n
\ \"contains\": {\n \"type\": \"string\",\n \"pattern\":
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"At least one UNDRR-ISC
2025 code is required\"\n },\n \"items\": {\n \"type\":
\"string\",\n \"anyOf\": [\n {\n \"pattern\":
\"^[A-Z]{2}\\\\d{4}$\",\n \"$comment\": \"UNDRR-ISC 2025 format:
2 uppercase letters + 4 digits (e.g., GH0101, MH0600)\"\n },\n
\ {\n \"pattern\": \"^[A-Z]{2}$\",\n \"$comment\":
\"GLIDE format: 2 uppercase letters (e.g., FL, EQ, TC)\"\n },\n
\ {\n \"pattern\": \"^[a-z]{3}-[a-z]{3}-[a-z]{3}-[a-z]{3}$\",\n
\ \"$comment\": \"EM-DAT format: 4 groups of 3 lowercase letters
separated by dashes (e.g., nat-hyd-flo-flo)\"\n }\n ]\n
\ }\n }\n }\n },\n \"is_impact\": {\n \"properties\":
{\n \"roles\": {\n \"type\": \"array\",\n \"minItems\":
1,\n \"contains\": {\n \"const\": \"impact\"\n }\n
\ }\n }\n }\n }\n}\n"
headers:
Accept-Ranges:
- bytes
Access-Control-Allow-Origin:
- '*'
Age:
- '374'
Cache-Control:
- max-age=600
Connection:
- close
Content-Length:
- '10393'
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 12 Nov 2025 06:32:47 GMT
ETag:
- '"690c54cc-2899"'
Last-Modified:
- Thu, 06 Nov 2025 07:57:00 GMT
Server:
- GitHub.com
Vary:
- Accept-Encoding
Via:
- 1.1 varnish
X-Cache:
- HIT
X-Cache-Hits:
- '53'
X-Fastly-Request-ID:
- c6f1cbe0585afe5c9fb728812ed46c755abb3974
X-GitHub-Request-Id:
- 9A3E:2F62DB:5BE57:72BDD:69141429
X-Served-By:
- cache-del21721-DEL
X-Timer:
- S1762929167.412124,VS0,VE0
expires:
- Wed, 12 Nov 2025 05:09:22 GMT
x-proxy-cache:
- MISS
status:
code: 200
message: OK
version: 1
Loading
Loading