UI enhancemnts
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
||||
import { ScreenerEngine } from './ScreenerEngine';
|
||||
import { CatalystCache } from '../../domains/shared';
|
||||
import type { LiveAssetResult } from '../../domains/shared';
|
||||
import { CatalystCache, SignalSnapshotRepository } from '../../domains/shared';
|
||||
import type { LiveAssetResult, ScreenerResult } from '../../domains/shared';
|
||||
import { screenSchema } from '../../domains/shared/types/schemas';
|
||||
|
||||
export class ScreenerController {
|
||||
constructor(
|
||||
private readonly engine: ScreenerEngine,
|
||||
private readonly catalystCache: CatalystCache,
|
||||
// Optional so tests and minimal setups work without a database.
|
||||
private readonly snapshots?: SignalSnapshotRepository,
|
||||
) {}
|
||||
|
||||
register(app: FastifyInstance): void {
|
||||
@@ -21,6 +23,29 @@ export class ScreenerController {
|
||||
{ config: { rateLimit: { max: 10, timeWindow: '1 minute' } } },
|
||||
this.catalysts.bind(this),
|
||||
);
|
||||
app.get('/api/screen/history/:ticker', this.history.bind(this));
|
||||
}
|
||||
|
||||
/** Signal snapshot history for one ticker (P0.1 ledger read side). */
|
||||
private async history(req: FastifyRequest) {
|
||||
if (!this.snapshots) return { ticker: null, snapshots: [] };
|
||||
const { ticker } = req.params as { ticker: string };
|
||||
return {
|
||||
ticker: ticker.toUpperCase(),
|
||||
snapshots: this.snapshots.history(ticker).map((row) => ({
|
||||
date: row.snapshot_date,
|
||||
signal: row.signal,
|
||||
price: row.price,
|
||||
fundamental: { tier: row.fundamental_tier, score: row.fundamental_score },
|
||||
inflated: { tier: row.inflated_tier, score: row.inflated_score },
|
||||
coverage:
|
||||
row.coverage_active != null
|
||||
? { active: row.coverage_active, total: row.coverage_total }
|
||||
: null,
|
||||
riskFlags: row.risk_flags ? JSON.parse(row.risk_flags) : [],
|
||||
rateRegime: row.rate_regime,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private static serializeAssets(arr: LiveAssetResult[]) {
|
||||
@@ -39,6 +64,7 @@ export class ScreenerController {
|
||||
private async screen(req: FastifyRequest) {
|
||||
const tickers = (req.body as { tickers: string[] }).tickers.map((t) => t.toUpperCase());
|
||||
const results = await this.engine.screenTickers(tickers);
|
||||
this.recordSnapshots(results, req);
|
||||
return {
|
||||
...results,
|
||||
STOCK: ScreenerController.serializeAssets(results.STOCK as LiveAssetResult[]),
|
||||
@@ -47,6 +73,29 @@ export class ScreenerController {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* P0.1 signal track record — persist one snapshot per asset per day.
|
||||
* Best-effort: a snapshot failure must never fail the screen response.
|
||||
*/
|
||||
private recordSnapshots(results: ScreenerResult, req: FastifyRequest): void {
|
||||
if (!this.snapshots) return;
|
||||
try {
|
||||
const rateRegime = results.marketContext?.rateRegime ?? null;
|
||||
const inputs = [...results.STOCK, ...results.ETF, ...results.BOND].map((r) => ({
|
||||
ticker: r.asset.ticker,
|
||||
assetType: r.asset.type,
|
||||
price: r.asset.currentPrice ?? null,
|
||||
signal: r.signal,
|
||||
fundamental: r.fundamental,
|
||||
inflated: r.inflated,
|
||||
rateRegime,
|
||||
}));
|
||||
this.snapshots.recordBatch(inputs);
|
||||
} catch (err) {
|
||||
req.log?.warn?.({ err }, 'signal snapshot recording failed');
|
||||
}
|
||||
}
|
||||
|
||||
private async catalysts() {
|
||||
const { tickers, stories } = await this.catalystCache.get();
|
||||
return { tickers, stories };
|
||||
|
||||
Reference in New Issue
Block a user