51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { DatabaseConnection } from '../shared/db/index';
|
||
import { QueryBuilder } from '../shared/utils/QueryBuilder';
|
||
|
||
/**
|
||
* The tracked-ticker universe (FREE-DATA-STACK §4.1):
|
||
* watchlist ∪ holdings ∪ tickers screened in the last 30 days.
|
||
*
|
||
* This is the news pipeline's first and biggest filter — stories about
|
||
* tickers outside the universe are never stored. Cached for 10 minutes;
|
||
* the universe changes slowly.
|
||
*/
|
||
export class UniverseProvider {
|
||
private static readonly CACHE_TTL_MS = 10 * 60 * 1000;
|
||
private static readonly SNAPSHOT_LOOKBACK_DAYS = 30;
|
||
|
||
private cache: { universe: Set<string>; expiresAt: number } = {
|
||
universe: new Set(),
|
||
expiresAt: 0,
|
||
};
|
||
|
||
constructor(private readonly db: DatabaseConnection) {}
|
||
|
||
getUniverse(): Set<string> {
|
||
if (Date.now() < this.cache.expiresAt) return this.cache.universe;
|
||
|
||
const sinceDay = new Date(
|
||
Date.now() - UniverseProvider.SNAPSHOT_LOOKBACK_DAYS * 24 * 60 * 60 * 1000,
|
||
)
|
||
.toISOString()
|
||
.slice(0, 10);
|
||
|
||
const tickers = new Set<string>();
|
||
const add = (rows: { ticker: string }[]) =>
|
||
rows.forEach((r) => tickers.add(r.ticker.toUpperCase()));
|
||
|
||
add(this.db.all(new QueryBuilder('UNIVERSE_QUERIES.DISTINCT_WATCHLIST_TICKERS')));
|
||
add(this.db.all(new QueryBuilder('UNIVERSE_QUERIES.DISTINCT_HOLDING_TICKERS')));
|
||
add(
|
||
this.db.all(new QueryBuilder('UNIVERSE_QUERIES.DISTINCT_SNAPSHOT_TICKERS_SINCE', [sinceDay])),
|
||
);
|
||
|
||
this.cache = { universe: tickers, expiresAt: Date.now() + UniverseProvider.CACHE_TTL_MS };
|
||
return tickers;
|
||
}
|
||
|
||
/** Force next getUniverse() to re-read (e.g. after a watchlist change). */
|
||
invalidate(): void {
|
||
this.cache.expiresAt = 0;
|
||
}
|
||
}
|