feat: Nous Portal news summarizer, ticker flags, map pins #8

Merged
sirius merged 13 commits from feat/nous-news-summarizer into master 2026-08-28 17:43:41 -04:00
7 changed files with 20 additions and 5 deletions
Showing only changes of commit d2bf55183c - Show all commits

View file

@ -76,7 +76,10 @@ INGEST_FIRES=1
NOUS_API_KEY=
NOUS_BASE_URL=https://inference-api.nousresearch.com/v1
# Optional LLM knobs
SUMMARY_MODEL=Hermes-4.3-36B
# SUMMARY_MODEL is an optional override. Leave unset so Settings
# (app_settings.SUMMARY_MODEL) can reach the summarizer. Code default
# Hermes-4.3-36B remains after a Postgres miss. Env wins when set.
# SUMMARY_MODEL=
NEWS_BATCH_SIZE=50
SUMMARY_WINDOW_HOURS=1
# Futures/markets coupling from the upstream pipeline is OFF by default

View file

@ -214,7 +214,7 @@ async def get_api_key(name: str) -> str | None:
"""Read a stored key value — used by ingest services, never by the API.
Returns the raw value (or None when unset) so producers can pass it to
external APIs (FIRMS, Gemini, Telegram, ). Reads live from Postgres, so a
external APIs (FIRMS, Nous, Telegram, ). Reads live from Postgres, so a
key set via the dashboard is picked up on the next poll no restart.
"""
await ensure_api_keys_table()

View file

@ -605,7 +605,7 @@ async def list_documents(
async def list_api_keys():
"""List known API keys with set/missing status — masked, never raw.
Registered keys (FIRMS_MAP_KEY, GEMINI_API_KEY, TELEGRAM_TOKEN)
Registered keys (FIRMS_MAP_KEY, NOUS_API_KEY, TELEGRAM_TOKEN)
are always included. Any extra stored keys are appended.
"""
return await list_keys()

View file

@ -226,7 +226,7 @@ services:
DB_NAME: ${DB_NAME:-osint_data}
NOUS_API_KEY: ${NOUS_API_KEY:-}
NOUS_BASE_URL: ${NOUS_BASE_URL:-https://inference-api.nousresearch.com/v1}
SUMMARY_MODEL: ${SUMMARY_MODEL:-Hermes-4.3-36B}
SUMMARY_MODEL: ${SUMMARY_MODEL:-}
OSINT_USER_AGENT: ${OSINT_USER_AGENT:-osint-dashboard-news-summarizer}
BATCH_SIZE: ${NEWS_BATCH_SIZE:-50}
SUMMARY_WINDOW_HOURS: ${SUMMARY_WINDOW_HOURS:-1}

View file

@ -270,7 +270,7 @@ language is gated behind `INCLUDE_FUTURES=1`.
PYTHONPATH=news/summerizer pytest news/summerizer/tests -v
# intel + nous_client tests PASS (no network)
PYTHONPATH=app pytest tests/test_api_news.py tests/test_api_news_intel.py \
PYTHONPATH=app pytest tests/test_api_news.py \
tests/test_api_settings.py tests/test_api_live_layers.py -v
# DB-marked tests skip without Postgres; live_layers must still PASS
# /api/map/layers overlays key set UNCHANGED

View file

@ -13,6 +13,7 @@ _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:
@ -87,4 +88,6 @@ def select_map(items: list) -> list:
item["headline"] = headline
item["lat"], item["lon"] = coords
out.append(item)
if len(out) >= MAP_CAP:
break
return out

View file

@ -40,3 +40,12 @@ def test_select_map_requires_valid_coords_and_flag():
]
out = select_map(items)
assert [r["headline"] for r in out] == ["A"]
def test_select_map_caps_20():
items = [
{"headline": f"h{i}", "importance": "critical", "lat": 1.0, "lon": 2.0}
for i in range(25)
]
out = select_map(items)
assert len(out) == 20
assert all(r["importance"] in ("critical", "high") for r in out)