"""UNHCR displacement data source for world-intel-mcp. Provides refugee and displacement population statistics from the UNHCR Population Statistics API. No API key required. """ import logging from datetime import datetime, timezone from ..fetcher import Fetcher logger = logging.getLogger("world-intel-mcp.sources.displacement") _UNHCR_POPULATION_URL = "https://api.unhcr.org/population/v1/population/" async def fetch_displacement_summary( fetcher: Fetcher, year: int | None = None, ) -> dict: """Fetch refugee/displacement population statistics from the UNHCR API. Aggregates displacement data by country of origin and computes global totals across refugees, asylum seekers, IDPs, stateless persons, and others of concern. Args: fetcher: Shared HTTP fetcher with caching and circuit breaking. year: Reporting year to query. Defaults to the previous calendar year since UNHCR data typically lags by one year. Returns: Dict with top-30 countries by displacement (sorted descending), global totals, year, source, and timestamp. """ now = datetime.now(timezone.utc) # Default to previous year since UNHCR data lags if year is None: year = now.year - 1 else: # Ensure year is an int even if passed as string year = int(year) params = { "limit": 100, "yearFrom": year, "yearTo": year, "cf_type": "ISO", } data = await fetcher.get_json( url=_UNHCR_POPULATION_URL, source="unhcr", cache_key=f"displacement:unhcr:{year}", cache_ttl=43200, params=params, ) if data is None: logger.warning("UNHCR API returned no data") return { "by_origin": [], "global_totals": { "total_refugees": 0, "total_asylum_seekers": 0, "total_idps": 0, "total_stateless": 0, "total_ooc": 0, "grand_total": 0, }, "year": year, "count": 0, "source": "unhcr", "timestamp": now.strftime("%Y-%m-%dT%H:%M:%SZ"), } items = data.get("items", []) # Aggregate by country of origin (coo_name) by_country: dict[str, dict[str, int]] = {} for item in items: coo_name = item.get("coo_name") or "Unknown" refugees = _safe_int(item.get("refugees")) asylum_seekers = _safe_int(item.get("asylum_seekers")) idps = _safe_int(item.get("idps")) stateless = _safe_int(item.get("stateless")) ooc = _safe_int(item.get("ooc")) if coo_name not in by_country: by_country[coo_name] = { "refugees": 0, "asylum_seekers": 0, "idps": 0, "stateless": 0, "ooc": 0, } entry = by_country[coo_name] entry["refugees"] += refugees entry["asylum_seekers"] += asylum_seekers entry["idps"] += idps entry["stateless"] += stateless entry["ooc"] += ooc # Build sorted list with total_displaced by_origin = [] for country, totals in by_country.items(): total_displaced = ( totals["refugees"] + totals["asylum_seekers"] + totals["idps"] + totals["stateless"] + totals["ooc"] ) by_origin.append({ "country": country, "refugees": totals["refugees"], "asylum_seekers": totals["asylum_seekers"], "internally_displaced": totals["idps"], "stateless": totals["stateless"], "others_of_concern": totals["ooc"], "total_displaced": total_displaced, }) # Sort by total_displaced descending, take top 30 by_origin.sort(key=lambda e: e["total_displaced"], reverse=True) by_origin = by_origin[:30] # Compute global totals across all countries (not just top 30) global_refugees = sum(t["refugees"] for t in by_country.values()) global_asylum_seekers = sum(t["asylum_seekers"] for t in by_country.values()) global_idps = sum(t["idps"] for t in by_country.values()) global_stateless = sum(t["stateless"] for t in by_country.values()) global_ooc = sum(t["ooc"] for t in by_country.values()) return { "by_origin": by_origin, "global_totals": { "total_refugees": global_refugees, "total_asylum_seekers": global_asylum_seekers, "total_idps": global_idps, "total_stateless": global_stateless, "total_ooc": global_ooc, "grand_total": ( global_refugees + global_asylum_seekers + global_idps + global_stateless + global_ooc ), }, "year": year, "count": len(by_origin), "source": "unhcr", "timestamp": now.strftime("%Y-%m-%dT%H:%M:%SZ"), } def _safe_int(value: int | float | str | None) -> int: """Convert a value to int, returning 0 for None or unparseable values.""" if value is None: return 0 try: return int(value) except (ValueError, TypeError): return 0