UI enhancemnts

This commit is contained in:
saikiranvella
2026-06-09 19:34:31 -04:00
parent 5c8cd8935a
commit 662a717916
55 changed files with 6226 additions and 465 deletions
+43
View File
@@ -0,0 +1,43 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { BenchmarkProvider } from '../server/domains/shared/services/BenchmarkProvider.js';
// P0.5 — rate-regime hysteresis: the 10Y must cross a boundary by ±0.25%
// before the regime flips, so a rate hovering at the threshold can't toggle
// INFLATED gates between back-to-back requests.
test('BenchmarkProvider.resolveRateRegime', async (t) => {
await t.test('no previous regime → raw thresholds apply', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(1.5, null), 'LOW');
assert.equal(BenchmarkProvider.resolveRateRegime(4.5, null), 'NORMAL');
assert.equal(BenchmarkProvider.resolveRateRegime(5.1, null), 'HIGH');
});
await t.test('NORMAL holds until 10Y clears 5.25%', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(5.1, 'NORMAL'), 'NORMAL'); // damped
assert.equal(BenchmarkProvider.resolveRateRegime(5.25, 'NORMAL'), 'NORMAL'); // boundary
assert.equal(BenchmarkProvider.resolveRateRegime(5.3, 'NORMAL'), 'HIGH'); // crossed
});
await t.test('HIGH holds until 10Y drops below 4.75%', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(4.9, 'HIGH'), 'HIGH'); // damped
assert.equal(BenchmarkProvider.resolveRateRegime(4.75, 'HIGH'), 'HIGH'); // boundary
assert.equal(BenchmarkProvider.resolveRateRegime(4.7, 'HIGH'), 'NORMAL'); // crossed
});
await t.test('LOW/NORMAL boundary at 2% gets the same damping', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(1.9, 'NORMAL'), 'NORMAL'); // damped
assert.equal(BenchmarkProvider.resolveRateRegime(1.7, 'NORMAL'), 'LOW'); // crossed
assert.equal(BenchmarkProvider.resolveRateRegime(2.1, 'LOW'), 'LOW'); // damped
assert.equal(BenchmarkProvider.resolveRateRegime(2.3, 'LOW'), 'NORMAL'); // crossed
});
await t.test('no change when raw regime equals previous', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(4.5, 'NORMAL'), 'NORMAL');
assert.equal(BenchmarkProvider.resolveRateRegime(6.0, 'HIGH'), 'HIGH');
});
await t.test('double jump (LOW→HIGH) is not damped', () => {
assert.equal(BenchmarkProvider.resolveRateRegime(5.4, 'LOW'), 'HIGH');
assert.equal(BenchmarkProvider.resolveRateRegime(1.2, 'HIGH'), 'LOW');
});
});
+43
View File
@@ -255,6 +255,49 @@ test('EtfScorer', async (t) => {
assert.equal(result.label, '🔴 REJECT');
});
await t.test('does not reject ETF when Yahoo data is missing (null)', () => {
const metrics: EtfMetrics = {
expenseRatio: 0.05,
yield: 1.8,
volume: null, // Yahoo did not return averageVolume
fiveYearReturn: null, // Yahoo did not return fiveYearAverageReturn
totalAssets: null,
};
const result = EtfScorer.score(metrics, DEFAULT_RULES);
// Missing data skips gates — must NOT auto-fail as 0 < gate
assert.notEqual(result.label, '🔴 REJECT');
assert.ok(result.audit?.passedGates);
});
await t.test('still enforces expense gate when other data is missing', () => {
const metrics: EtfMetrics = {
expenseRatio: 0.8, // above 0.2 gate
yield: null,
volume: null,
fiveYearReturn: null,
totalAssets: null,
};
const result = EtfScorer.score(metrics, DEFAULT_RULES);
assert.equal(result.label, '🔴 REJECT');
assert.ok(result.scoreSummary.includes('Expense ratio'));
});
await t.test('labels all-null metrics as No Data instead of Neutral', () => {
const metrics: EtfMetrics = {
expenseRatio: null,
yield: null,
volume: null,
fiveYearReturn: null,
totalAssets: null,
};
const result = EtfScorer.score(metrics, DEFAULT_RULES);
assert.equal(result.label, '🟡 Neutral (No Data)');
assert.equal(result.audit?.coverage?.active, 0);
});
await t.test('handles negative 5-year return', () => {
const metrics: EtfMetrics = {
expenseRatio: 0.1,
+87
View File
@@ -0,0 +1,87 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { SignalSnapshotRepository } from '../server/domains/shared/persistence/SignalSnapshotRepository.js';
import { MockDatabaseConnection } from './helpers/mockDb.js';
import type { DatabaseConnection } from '../server/domains/shared/db/index.js';
import type { ScoreResult } from '../server/domains/shared/types/index.js';
const passResult: ScoreResult = {
label: '🟢 BUY (High Conviction)',
tier: 'PASS',
score: 9,
scoreSummary: 'Score: 9',
audit: { passedGates: true, breakdown: { roe: 3 }, coverage: { active: 6, total: 11 } },
};
const rejectResult: ScoreResult = {
label: '🔴 REJECT',
tier: 'REJECT',
score: null,
scoreSummary: 'Gate failed: P/E 40 > 15',
audit: { passedGates: false, failures: ['P/E 40 > 15'] },
};
function repo(): SignalSnapshotRepository {
return new SignalSnapshotRepository(
new MockDatabaseConnection() as unknown as DatabaseConnection,
);
}
test('SignalSnapshotRepository', async (t) => {
await t.test('record() builds a valid UPSERT (16 params, no throw)', () => {
// QueryBuilder validates placeholder count — a param mismatch throws here.
assert.doesNotThrow(() =>
repo().record({
ticker: 'aapl',
assetType: 'STOCK',
price: 189.5,
signal: '✅ Strong Buy',
fundamental: passResult,
inflated: passResult,
rateRegime: 'NORMAL',
}),
);
});
await t.test('record() tolerates gate-failed results (null score)', () => {
assert.doesNotThrow(() =>
repo().record({
ticker: 'XYZ',
assetType: 'STOCK',
price: null,
signal: '❌ Avoid',
fundamental: rejectResult,
inflated: rejectResult,
}),
);
});
await t.test('recordBatch() returns count written', () => {
const n = repo().recordBatch([
{
ticker: 'AAPL',
assetType: 'STOCK',
price: 189.5,
signal: '✅ Strong Buy',
fundamental: passResult,
inflated: passResult,
},
{
ticker: 'MSFT',
assetType: 'STOCK',
price: 425.3,
signal: '🔄 Neutral',
fundamental: passResult,
inflated: passResult,
},
]);
assert.equal(n, 2);
});
await t.test('read methods build valid queries', () => {
const r = repo();
assert.deepEqual(r.history('aapl'), []);
assert.deepEqual(r.byDate('2026-06-09'), []);
assert.deepEqual(r.latestBefore('2026-06-09'), []);
});
});
+90 -1
View File
@@ -238,8 +238,97 @@ test('StockScorer', async (t) => {
} as any;
const result = StockScorer.score(metrics, DEFAULT_RULES);
// Should handle gracefully (zero is falsy, treated as null)
// Zero quick ratio is a real value and fails the liquidity gate;
// zero P/E, PEG, P/B are impossible values and are treated as missing.
assert.ok(result);
assert.equal(result.label, '🔴 REJECT');
assert.ok(result.scoreSummary.includes('Quick'));
});
await t.test('treats zero revenue growth as a real (stagnant) value', () => {
const metrics: StockMetrics = {
peRatio: 12,
pegRatio: 0.8,
debtToEquity: 0.5,
quickRatio: 1.2,
returnOnEquity: 20,
operatingMargin: 15,
netProfitMargin: 10,
revenueGrowth: 0, // stagnant — must be scored, not skipped
fcfYield: 5,
} as any;
const result = StockScorer.score(metrics, DEFAULT_RULES);
assert.ok(result.audit?.passedGates);
// 0% growth is below revMed (5) → scores -1, same as slightly negative growth
assert.equal(result.audit?.breakdown?.revenue, -1);
});
await t.test('treats zero debt-to-equity as debt-free, not missing', () => {
const metrics: StockMetrics = {
peRatio: 12,
pegRatio: 0.8,
debtToEquity: 0, // debt-free — should pass the gate, not be skipped
quickRatio: 1.2,
returnOnEquity: 20,
operatingMargin: 15,
netProfitMargin: 10,
revenueGrowth: 8,
fcfYield: 5,
} as any;
const result = StockScorer.score(metrics, DEFAULT_RULES);
assert.ok(result.audit?.passedGates);
assert.notEqual(result.label, '🔴 REJECT');
});
await t.test('flags insufficient data instead of plain HOLD', () => {
const metrics: StockMetrics = { currentPrice: 50 } as any;
const result = StockScorer.score(metrics, DEFAULT_RULES);
assert.equal(result.label, '🟡 HOLD (No Data)');
assert.equal(result.audit?.coverage?.active, 0);
});
await t.test('returns structured tier and numeric score (P0.3)', () => {
const strong: StockMetrics = {
peRatio: 12,
pegRatio: 0.7,
debtToEquity: 0.3,
quickRatio: 1.5,
returnOnEquity: 30,
operatingMargin: 25,
netProfitMargin: 18,
revenueGrowth: 12,
fcfYield: 6,
} as any;
const pass = StockScorer.score(strong, DEFAULT_RULES);
assert.equal(pass.tier, 'PASS');
assert.ok(typeof pass.score === 'number' && pass.score >= 4);
const gated: StockMetrics = { ...strong, peRatio: 40 } as any;
const reject = StockScorer.score(gated, DEFAULT_RULES);
assert.equal(reject.tier, 'REJECT');
assert.equal(reject.score, null);
const noData = StockScorer.score({ currentPrice: 50 } as any, DEFAULT_RULES);
assert.equal(noData.tier, 'HOLD');
assert.equal(noData.score, 0);
});
await t.test('reports factor coverage in audit', () => {
const metrics: StockMetrics = {
peRatio: 12,
pegRatio: 0.8,
quickRatio: 1.2,
returnOnEquity: 20,
currentPrice: 50,
} as any;
const result = StockScorer.score(metrics, DEFAULT_RULES);
assert.ok(result.audit?.coverage);
assert.ok(result.audit.coverage.active >= 1);
assert.ok(result.audit.coverage.active <= result.audit.coverage.total);
});
await t.test('scores based on configured thresholds', () => {