From 96590bc16d5cfe17b668e5dc0f233cb309b24b6d Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 27 Aug 2026 22:21:26 -0400 Subject: [PATCH] test: add news summarizer intel parser tests + impl --- news/summerizer/intel.py | 90 +++++++++++++++++++++++++++++ news/summerizer/tests/test_intel.py | 42 ++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 news/summerizer/intel.py create mode 100644 news/summerizer/tests/test_intel.py diff --git a/news/summerizer/intel.py b/news/summerizer/intel.py new file mode 100644 index 0000000..d4bb1b7 --- /dev/null +++ b/news/summerizer/intel.py @@ -0,0 +1,90 @@ +"""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"}) +_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 + + +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: + out = [] + for row in rows: + if row.get("importance") not in _KEEP: + continue + headline = _trimmed_headline(row, TICKER_HEADLINE_MAX) + if not headline: + continue + item = dict(row) + item["headline"] = headline + out.append(item) + if len(out) >= TICKER_CAP: + break + return out + + +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) + return out diff --git a/news/summerizer/tests/test_intel.py b/news/summerizer/tests/test_intel.py new file mode 100644 index 0000000..74f74c3 --- /dev/null +++ b/news/summerizer/tests/test_intel.py @@ -0,0 +1,42 @@ +from intel import parse_reduce_json, clamp_coords, select_ticker, select_map + +FENCED = """```json +{"summary_en": "Brief.", "ticker": [ + {"headline": "Blast in Kyiv", "importance": "critical", "url": "https://ex", "location_name": "Kyiv"} +], "map_items": [ + {"headline": "Blast in Kyiv", "importance": "critical", "location_name": "Kyiv, Ukraine", + "lat": 50.45, "lon": 30.52, "location_confidence": "city", "category": "military/conflict", "url": "https://ex"} +]} +```""" + +def test_parse_strips_fence_and_think_tags(): + raw = "nope\n" + FENCED + out = parse_reduce_json(raw) + assert out["summary_en"] == "Brief." + assert len(out["ticker"]) == 1 + +def test_parse_empty_and_garbage_returns_empty_struct(): + assert parse_reduce_json("")["summary_en"] == "" + assert parse_reduce_json("not json")["ticker"] == [] + +def test_clamp_coords_drops_out_of_range_and_unknown(): + assert clamp_coords(50.45, 30.52) == (50.45, 30.52) + assert clamp_coords(95.0, 10.0) is None + assert clamp_coords(None, 10.0) is None + assert clamp_coords("50.45", "30.52") == (50.45, 30.52) + +def test_select_ticker_keeps_critical_high_caps_12(): + rows = [{"headline": f"h{i}", "importance": "critical"} for i in range(15)] + rows.append({"headline": "skip", "importance": "low"}) + out = select_ticker(rows) + assert len(out) == 12 + assert all(r["importance"] in ("critical", "high") for r in out) + +def test_select_map_requires_valid_coords_and_flag(): + items = [ + {"headline": "A", "importance": "critical", "lat": 50.45, "lon": 30.52, "location_name": "Kyiv"}, + {"headline": "B", "importance": "critical", "lat": None, "lon": None, "location_name": "Unknown"}, + {"headline": "C", "importance": "low", "lat": 1.0, "lon": 2.0, "location_name": "x"}, + ] + out = select_map(items) + assert [r["headline"] for r in out] == ["A"]