phase-10.5: screener enhancements
This commit is contained in:
@@ -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]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user