UI enhancemnts
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { DatabaseConnection } from '../db/index';
|
||||
import { QueryBuilder } from '../utils/QueryBuilder';
|
||||
import type { ScoreResult, SignalSnapshotRow } from '../types';
|
||||
|
||||
/**
|
||||
* Signal snapshot ledger (PRODUCT.md P0.1).
|
||||
*
|
||||
* Persists one row per ticker per day on every /api/screen call so the
|
||||
* product builds a verifiable signal track record. This data cannot be
|
||||
* backfilled — the backtest dashboard (Phase 10.5e), thesis review (10.6d),
|
||||
* and calibration features all depend on it accumulating from day one.
|
||||
*
|
||||
* Recording is best-effort: failures are logged by the caller and must never
|
||||
* fail the screen request itself.
|
||||
*/
|
||||
|
||||
export interface SnapshotInput {
|
||||
ticker: string;
|
||||
assetType: string;
|
||||
price: number | null;
|
||||
signal: string;
|
||||
fundamental: ScoreResult;
|
||||
inflated: ScoreResult;
|
||||
rateRegime?: string | null;
|
||||
}
|
||||
|
||||
export class SignalSnapshotRepository {
|
||||
constructor(private readonly db: DatabaseConnection) {}
|
||||
|
||||
/**
|
||||
* Upsert today's snapshot for a batch of screened assets.
|
||||
* Repeated screens on the same day keep the latest result.
|
||||
*/
|
||||
recordBatch(inputs: SnapshotInput[], date = SignalSnapshotRepository.today()): number {
|
||||
let written = 0;
|
||||
for (const input of inputs) {
|
||||
this.record(input, date);
|
||||
written++;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
record(input: SnapshotInput, date = SignalSnapshotRepository.today()): void {
|
||||
const { ticker, assetType, price, signal, fundamental, inflated, rateRegime } = input;
|
||||
const coverage = fundamental.audit?.coverage ?? inflated.audit?.coverage ?? null;
|
||||
const riskFlags = fundamental.audit?.riskFlags ?? inflated.audit?.riskFlags ?? null;
|
||||
|
||||
const qb = new QueryBuilder('SIGNAL_SNAPSHOT_QUERIES.UPSERT', [
|
||||
ticker.toUpperCase(),
|
||||
date,
|
||||
assetType,
|
||||
price,
|
||||
signal,
|
||||
fundamental.tier,
|
||||
fundamental.score,
|
||||
fundamental.label,
|
||||
inflated.tier,
|
||||
inflated.score,
|
||||
inflated.label,
|
||||
coverage?.active ?? null,
|
||||
coverage?.total ?? null,
|
||||
riskFlags ? JSON.stringify(riskFlags) : null,
|
||||
rateRegime ?? null,
|
||||
new Date().toISOString(),
|
||||
]);
|
||||
this.db.run(qb);
|
||||
}
|
||||
|
||||
/** Full history for one ticker, oldest first. */
|
||||
history(ticker: string): SignalSnapshotRow[] {
|
||||
const qb = new QueryBuilder('SIGNAL_SNAPSHOT_QUERIES.SELECT_BY_TICKER', [ticker.toUpperCase()]);
|
||||
return this.db.all<SignalSnapshotRow>(qb);
|
||||
}
|
||||
|
||||
/** All snapshots for a given day (YYYY-MM-DD). */
|
||||
byDate(date: string): SignalSnapshotRow[] {
|
||||
const qb = new QueryBuilder('SIGNAL_SNAPSHOT_QUERIES.SELECT_BY_DATE', [date]);
|
||||
return this.db.all<SignalSnapshotRow>(qb);
|
||||
}
|
||||
|
||||
/** Latest snapshot per ticker strictly before a date — for daily diffing. */
|
||||
latestBefore(date: string): SignalSnapshotRow[] {
|
||||
const qb = new QueryBuilder('SIGNAL_SNAPSHOT_QUERIES.SELECT_LATEST_BEFORE', [date]);
|
||||
return this.db.all<SignalSnapshotRow>(qb);
|
||||
}
|
||||
|
||||
private static today(): string {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user