osint-dashboard/news/summerizer/intel.py
Sirius DevOps c48788d4b6
fix(map): geofence delete, vessel snapshots, sentinel cache, news briefs
Geofences could be drawn but not removed. VesselAPI Hormuz dots vanished
on restart and DVR skipped between the 5 daily polls. Sentinel-1 re-hit
STAC on every pan and often painted a neighbouring swath. Executive
briefs truncated; ticker stayed empty unless something was critical.

- Layer-panel list + polygon popup DELETE /api/geofences/{id}
- Persist VesselAPI polls to vessels (UTC-day purge, DVR as-of, boot hydrate)
- Cache Sentinel-1 by 2° cell; pick covering scene; clip Leaflet tiles
- Retry truncated LLM JSON; ticker falls back to medium/low; 3-min HUD poll
2026-08-29 20:40:27 -04:00

104 lines
3 KiB
Python

"""Pure parser for the news-summarizer reduce JSON / geo / importance contract."""
from __future__ import annotations
import json
import re
_EMPTY = {"summary_en": "", "ticker": [], "map_items": []}
_KEEP = frozenset({"critical", "high"})
_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3}
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL)
_FENCE_RE = re.compile(r"```(?:json)?", re.IGNORECASE)
TICKER_HEADLINE_MAX = 140
MAP_HEADLINE_MAX = 160
TICKER_CAP = 12
MAP_CAP = 20
def parse_reduce_json(raw: str) -> dict:
try:
text = _THINK_RE.sub("", raw or "")
text = _FENCE_RE.sub("", text)
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1 or end < start:
return dict(_EMPTY)
data = json.loads(text[start : end + 1])
if not isinstance(data, dict):
return dict(_EMPTY)
summary = data.get("summary_en", "")
ticker = data.get("ticker", [])
map_items = data.get("map_items", [])
return {
"summary_en": summary if isinstance(summary, str) else "",
"ticker": ticker if isinstance(ticker, list) else [],
"map_items": map_items if isinstance(map_items, list) else [],
}
except Exception:
return dict(_EMPTY)
def clamp_coords(lat, lon) -> tuple[float, float] | None:
try:
lat_f = float(lat)
lon_f = float(lon)
except (TypeError, ValueError):
return None
if not (-90 <= lat_f <= 90 and -180 <= lon_f <= 180):
return None
return (lat_f, lon_f)
def _trimmed_headline(row: dict, limit: int) -> str:
headline = row.get("headline") or ""
if not isinstance(headline, str):
headline = str(headline)
return headline.strip()[:limit]
def select_ticker(rows: list) -> list:
flagged = []
medium = []
low = []
for row in rows:
imp = row.get("importance")
if imp not in _RANK:
continue
headline = _trimmed_headline(row, TICKER_HEADLINE_MAX)
if not headline:
continue
item = dict(row)
item["headline"] = headline
if imp in _KEEP:
flagged.append(item)
elif imp == "medium":
medium.append(item)
else:
low.append(item)
if len(flagged) >= TICKER_CAP:
break
if flagged:
return flagged[:TICKER_CAP]
return (medium + low)[:TICKER_CAP]
def select_map(items: list) -> list:
out = []
for row in items:
if row.get("importance") not in _KEEP:
continue
headline = _trimmed_headline(row, MAP_HEADLINE_MAX)
if not headline:
continue
coords = clamp_coords(row.get("lat"), row.get("lon"))
if coords is None:
continue
item = dict(row)
item["headline"] = headline
item["lat"], item["lon"] = coords
out.append(item)
if len(out) >= MAP_CAP:
break
return out