phase-10.5: screener enhancements

This commit is contained in:
Kazuma
2026-06-11 19:18:19 -04:00
parent f0c794f0c0
commit bf2a85b5c4
51 changed files with 3745 additions and 36 deletions
+76
View File
@@ -0,0 +1,76 @@
import { DatabaseConnection } from '../shared/db/index';
import { QueryBuilder } from '../shared/utils/QueryBuilder';
import type { NewsArticleRow } from '../shared/types';
/**
* Persistence for the free-tier news pipeline (FREE-DATA-STACK §3).
* Pure data access — all filtering/dedupe decisions live in NewsPipeline.
*/
export class NewsRepository {
constructor(private readonly db: DatabaseConnection) {}
/** Returns true if the row was inserted (false = duplicate url_hash). */
insertArticle(a: {
urlHash: string;
titleHash: string;
tickers: string[];
headline: string;
body: string | null;
source: string;
catalyst: string | null;
url: string;
publishedAt: string;
}): boolean {
const qb = new QueryBuilder('NEWS_QUERIES.INSERT_ARTICLE', [
a.urlHash,
a.titleHash,
JSON.stringify(a.tickers),
a.headline,
a.body,
a.source,
a.catalyst,
a.url,
a.publishedAt,
new Date().toISOString(),
]);
return this.db.run(qb) > 0;
}
titleSeenSince(titleHash: string, sinceIso: string): boolean {
const qb = new QueryBuilder('NEWS_QUERIES.TITLE_SEEN_SINCE', [titleHash, sinceIso]);
return this.db.get(qb) != null;
}
linkTicker(ticker: string, day: string, urlHash: string): void {
const qb = new QueryBuilder('NEWS_QUERIES.INSERT_CATALYST_LINK', [ticker, day, urlHash]);
this.db.run(qb);
}
countTickerDay(ticker: string, day: string): number {
const qb = new QueryBuilder('NEWS_QUERIES.COUNT_TICKER_DAY', [ticker, day]);
return this.db.get<{ n: number }>(qb)?.n ?? 0;
}
newsForTicker(ticker: string, sinceDay: string): NewsArticleRow[] {
const qb = new QueryBuilder('NEWS_QUERIES.SELECT_TICKER_NEWS', [
ticker.toUpperCase(),
sinceDay,
]);
return this.db.all<NewsArticleRow>(qb);
}
recent(limit: number): NewsArticleRow[] {
const qb = new QueryBuilder('NEWS_QUERIES.SELECT_RECENT', [limit]);
return this.db.all<NewsArticleRow>(qb);
}
/** Retention: null out bodies older than cutoff. Returns rows changed. */
purgeBodiesBefore(cutoffIso: string): number {
return this.db.run(new QueryBuilder('NEWS_QUERIES.PURGE_BODIES_BEFORE', [cutoffIso]));
}
/** Retention: delete old rows no ticker references. Returns rows deleted. */
deleteUnreferencedBefore(cutoffIso: string): number {
return this.db.run(new QueryBuilder('NEWS_QUERIES.DELETE_UNREFERENCED_BEFORE', [cutoffIso]));
}
}