feat: Nous Portal news summarizer, ticker flags, map pins #8
7 changed files with 20 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue