Curated static catalogue of 13 conflict theatres (Ukraine, Gaza, Sudan,
Myanmar, DRC, Yemen, Syria, Lebanon, Sahel, Somalia, Red Sea, Taiwan
Strait, Korean DMZ) with war|high|elevated severity and short factual
descriptions. GET /api/conflicts returns { zones: [{id,label,severity,
lat,lon,description,eventCount,lastUpdated}], timestamp } where
eventCount rolls up pre-existing geocoded news/GDELT//api/news/map rows
inside each zone bbox (0 when the tables are empty). Adds the conflicts
entry to overlay_catalog(). No upstream scraping, no jittered coords.
168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
"""Curated OSINT conflict-zone catalog + point-in-bbox event counting.
|
||
|
||
A static, human-curated list of active conflict theatres (war / high /
|
||
elevated). Purely descriptive — this is a catalog, not a live feed and not a
|
||
scrape of LiveUAMap or any other source. Severity and descriptions are
|
||
editorial judgement kept short and factual.
|
||
|
||
Each zone carries an internal ``bbox`` (``min_lat, min_lon, max_lat, max_lon``)
|
||
used only to count pre-existing geocoded news/GDELT/``/api/news/map`` rows that
|
||
fall inside it. The bbox is not part of the API response; callers get the
|
||
``eventCount`` roll-up instead.
|
||
|
||
Never call an upstream API from here — event counts come from rows already in
|
||
the local database (``events`` with geocoords + ``news_items`` map pins).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
# id → zone. ``lat``/``lon`` is the fly-to anchor; ``bbox`` is the internal
|
||
# count window in ``min_lat, min_lon, max_lat, max_lon`` order.
|
||
_ZONES: tuple[dict, ...] = (
|
||
{
|
||
"id": "ukraine",
|
||
"label": "Ukraine",
|
||
"severity": "war",
|
||
"lat": 48.5,
|
||
"lon": 31.0,
|
||
"description": "Full-scale Russian invasion since 2022; active front lines in the east and south.",
|
||
"bbox": (44.3, 22.1, 52.4, 40.2),
|
||
},
|
||
{
|
||
"id": "gaza",
|
||
"label": "Gaza",
|
||
"severity": "war",
|
||
"lat": 31.4,
|
||
"lon": 34.4,
|
||
"description": "Israel–Hamas war; sustained fighting and a severe humanitarian crisis in the Gaza Strip.",
|
||
"bbox": (31.0, 34.1, 31.8, 34.7),
|
||
},
|
||
{
|
||
"id": "sudan",
|
||
"label": "Sudan",
|
||
"severity": "war",
|
||
"lat": 15.5,
|
||
"lon": 30.0,
|
||
"description": "Civil war between the SAF and RSF since 2023, with mass displacement across the country.",
|
||
"bbox": (8.7, 21.8, 22.0, 38.6),
|
||
},
|
||
{
|
||
"id": "myanmar",
|
||
"label": "Myanmar",
|
||
"severity": "war",
|
||
"lat": 21.5,
|
||
"lon": 96.0,
|
||
"description": "Post-2021 coup conflict pitting the junta against resistance and ethnic armed groups.",
|
||
"bbox": (9.5, 92.2, 28.5, 101.2),
|
||
},
|
||
{
|
||
"id": "drc",
|
||
"label": "DR Congo",
|
||
"severity": "war",
|
||
"lat": -1.5,
|
||
"lon": 28.0,
|
||
"description": "Eastern DRC conflict involving M23 and other armed groups; heavy displacement around Goma.",
|
||
"bbox": (-5.0, 26.0, 3.0, 31.0),
|
||
},
|
||
{
|
||
"id": "yemen",
|
||
"label": "Yemen",
|
||
"severity": "war",
|
||
"lat": 15.5,
|
||
"lon": 47.5,
|
||
"description": "Protracted Houthi–government/coalition war with one of the world's worst humanitarian emergencies.",
|
||
"bbox": (12.6, 42.5, 19.0, 54.0),
|
||
},
|
||
{
|
||
"id": "syria",
|
||
"label": "Syria",
|
||
"severity": "war",
|
||
"lat": 34.5,
|
||
"lon": 38.5,
|
||
"description": "Multi-sided civil war; government, opposition, and external actors continue to engage.",
|
||
"bbox": (32.3, 35.7, 37.3, 42.4),
|
||
},
|
||
{
|
||
"id": "lebanon",
|
||
"label": "Lebanon",
|
||
"severity": "high",
|
||
"lat": 33.9,
|
||
"lon": 35.9,
|
||
"description": "Israel–Hezbollah hostilities with periodic escalation along the southern border.",
|
||
"bbox": (33.0, 35.0, 34.7, 36.6),
|
||
},
|
||
{
|
||
"id": "sahel",
|
||
"label": "Sahel",
|
||
"severity": "high",
|
||
"lat": 14.5,
|
||
"lon": 0.0,
|
||
"description": "Jihadist insurgencies across Mali, Burkina Faso, and Niger destabilising the central Sahel.",
|
||
"bbox": (10.0, -10.0, 20.0, 12.0),
|
||
},
|
||
{
|
||
"id": "somalia",
|
||
"label": "Somalia",
|
||
"severity": "high",
|
||
"lat": 6.0,
|
||
"lon": 45.0,
|
||
"description": "Al-Shabaab insurgency against the federal government and security forces.",
|
||
"bbox": (-2.0, 41.0, 12.0, 51.5),
|
||
},
|
||
{
|
||
"id": "red_sea",
|
||
"label": "Red Sea",
|
||
"severity": "high",
|
||
"lat": 18.0,
|
||
"lon": 40.0,
|
||
"description": "Houthi attacks on commercial shipping transiting the Red Sea corridor.",
|
||
"bbox": (12.0, 34.0, 22.0, 44.0),
|
||
},
|
||
{
|
||
"id": "taiwan_strait",
|
||
"label": "Taiwan Strait",
|
||
"severity": "elevated",
|
||
"lat": 24.5,
|
||
"lon": 119.5,
|
||
"description": "Heightened military standoff between China and Taiwan, including deterrence patrols.",
|
||
"bbox": (21.9, 117.0, 26.5, 122.0),
|
||
},
|
||
{
|
||
"id": "korean_dmz",
|
||
"label": "Korean DMZ",
|
||
"severity": "elevated",
|
||
"lat": 38.3,
|
||
"lon": 127.0,
|
||
"description": "Heavily fortified inter-Korean border with periodic tensions and military drills.",
|
||
"bbox": (37.5, 126.0, 39.0, 128.5),
|
||
},
|
||
)
|
||
|
||
SEVERITIES: frozenset[str] = frozenset({"war", "high", "elevated"})
|
||
|
||
|
||
def conflict_zones() -> list[dict]:
|
||
"""Return a fresh shallow copy of the catalog (callers must not mutate)."""
|
||
return [dict(z) for z in _ZONES]
|
||
|
||
|
||
def zone_event_stats(
|
||
points: list[tuple[float, float, datetime | None]],
|
||
bbox: tuple[float, float, float, float],
|
||
) -> tuple[int, datetime | None]:
|
||
"""Count points inside ``bbox`` and return (count, latest timestamp).
|
||
|
||
``points`` is an iterable of ``(lat, lon, ts)``; ``ts`` may be ``None``.
|
||
``bbox`` is ``(min_lat, min_lon, max_lat, max_lon)``.
|
||
"""
|
||
min_lat, min_lon, max_lat, max_lon = bbox
|
||
count = 0
|
||
latest: datetime | None = None
|
||
for lat, lon, ts in points:
|
||
if min_lat <= lat <= max_lat and min_lon <= lon <= max_lon:
|
||
count += 1
|
||
if ts is not None and (latest is None or ts > latest):
|
||
latest = ts
|
||
return count, latest
|