mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-26 01:24:59 +02:00
* feat(market-implications): validate LLM tickers against live Redis symbol set
Add _ticker-validation.mjs with loadTickerSet (reads market:stocks-bootstrap:v1)
and filterInvalidTickers. After static whitelist validation, equity cards are also
gated against the live symbol set from Redis — additive (ALL_ALLOWED_TICKERS union
live symbols), so curated ETF/defense tickers are never incorrectly dropped.
Drops are logged per-card for observability.
* fix(market-implications): move dynamic ticker gate before validation
The previous approach was a logical no-op: validateMarketImplications()
already filtered to ALL_ALLOWED_TICKERS, then the gate re-filtered
against ALL_ALLOWED_TICKERS ∪ liveSet — so every surviving card was
guaranteed to pass and filterInvalidTickers() could never drop anything.
Fix: load the live Redis ticker set first and pass it as the allowed set
into validateMarketImplications() (new allowedTickers parameter). When
Redis has data, effectiveTickers = NON_EQUITY_TICKERS ∪ liveTickerSet,
so hallucinated equity symbols absent from the live symbol set (WMTX,
GOOG, etc.) are dropped at the single validation pass. Falls back to
ALL_ALLOWED_TICKERS when Redis is unavailable. Remove now-dead
filterInvalidTickers export.
* fix(market-implications): use ALL_ALLOWED_TICKERS union liveTickerSet
NON_EQUITY_TICKERS + liveTickerSet incorrectly dropped curated ETF and
defense tickers (SPY, QQQ, XLE, RTX, LMT, NOC) that are in the static
allowlist but not in market:stocks-bootstrap:v1.
Correct model: ALL_ALLOWED_TICKERS is always preserved as the curated
baseline; liveTickerSet extends it with live-priced stocks (NFLX, WMT,
etc.) not in the static list. Hallucinations absent from both sets are
rejected. Remove now-unused NON_EQUITY_TICKERS constant.
* fix(market-implications): strip non-tradeable symbols before live ticker union
market:stocks-bootstrap:v1 contains index symbols (^GSPC, ^DJI, ^IXIC,
^NSEI) and foreign-exchange-suffixed symbols (RELIANCE.NS, TCS.NS) for
display/charting purposes. Unioning the full live set into effectiveTickers
would allow these non-tradeable instruments to pass card validation.
Filter liveTickerSet to /^[A-Z]{1,6}(-[A-Z])?$/ before the union so only
clean US-exchange symbols (NFLX, WMT, BRK-B) extend the static allowlist.
30 lines
1.1 KiB
JavaScript
30 lines
1.1 KiB
JavaScript
// @ts-check
|
|
/** @typedef {{ symbol: string, name?: string, display?: string }} StockSymbol */
|
|
|
|
const STOCKS_BOOTSTRAP_KEY = 'market:stocks-bootstrap:v1';
|
|
|
|
/**
|
|
* Load the set of valid ticker symbols from Redis (market:stocks-bootstrap:v1).
|
|
* Returns an empty Set if the key is missing or malformed — callers must handle gracefully.
|
|
* @param {string} redisUrl
|
|
* @param {string} redisToken
|
|
* @returns {Promise<Set<string>>}
|
|
*/
|
|
export async function loadTickerSet(redisUrl, redisToken) {
|
|
try {
|
|
const resp = await fetch(`${redisUrl}/get/${encodeURIComponent(STOCKS_BOOTSTRAP_KEY)}`, {
|
|
headers: { Authorization: `Bearer ${redisToken}` },
|
|
signal: AbortSignal.timeout(8_000),
|
|
});
|
|
if (!resp.ok) return new Set();
|
|
const data = await resp.json();
|
|
if (!data?.result) return new Set();
|
|
/** @type {{ quotes?: StockSymbol[] } | null} */
|
|
const parsed = (() => { try { return JSON.parse(data.result); } catch { return null; } })();
|
|
if (!Array.isArray(parsed?.quotes)) return new Set();
|
|
return new Set(parsed.quotes.map(s => s.symbol?.toUpperCase()).filter(Boolean));
|
|
} catch {
|
|
return new Set();
|
|
}
|
|
}
|