"""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".*?", 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