Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
const mockState = {
|
||||
resolutionCount: 100,
|
||||
weightRows: [], // engine1_weights rows
|
||||
inserts: [],
|
||||
};
|
||||
|
||||
jest.mock('../../src/utils/supabase', () => ({
|
||||
getSupabaseServiceClient: () => ({
|
||||
from(table) {
|
||||
const ctx = { table, filters: {} };
|
||||
const proxy = {
|
||||
select(_cols, opts) {
|
||||
ctx.head = !!opts?.head;
|
||||
ctx.countMode = opts?.count;
|
||||
return proxy;
|
||||
},
|
||||
eq(col, val) { ctx.filters[col] = val; return proxy; },
|
||||
order() { return proxy; },
|
||||
limit() { return proxy; },
|
||||
maybeSingle() {
|
||||
const match = mockState.weightRows.find(
|
||||
(r) =>
|
||||
r.sport === ctx.filters.sport
|
||||
&& r.stat_type === ctx.filters.stat_type
|
||||
&& r.factor_name === ctx.filters.factor_name
|
||||
&& r.version === ctx.filters.version
|
||||
);
|
||||
return Promise.resolve({ data: match || null, error: null });
|
||||
},
|
||||
insert(row) {
|
||||
mockState.inserts.push(row);
|
||||
mockState.weightRows.push(row);
|
||||
return Promise.resolve({ error: null });
|
||||
},
|
||||
then(resolve) {
|
||||
if (ctx.table === 'resolution_results' && ctx.countMode === 'exact') {
|
||||
return resolve({ count: mockState.resolutionCount, error: null });
|
||||
}
|
||||
// List of engine1_weights matching filters.
|
||||
const matches = mockState.weightRows.filter((r) => {
|
||||
for (const [k, v] of Object.entries(ctx.filters)) {
|
||||
if (r[k] !== v) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
matches.sort((a, b) => b.version - a.version);
|
||||
return resolve({ data: matches, error: null });
|
||||
},
|
||||
};
|
||||
return proxy;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const wa = require('../../src/services/intelligence/weightAdjuster');
|
||||
|
||||
beforeEach(() => {
|
||||
mockState.resolutionCount = 100;
|
||||
mockState.weightRows = [];
|
||||
mockState.inserts.length = 0;
|
||||
});
|
||||
|
||||
describe('weightAdjuster — skip conditions', () => {
|
||||
test('skips when sample too thin', async () => {
|
||||
mockState.resolutionCount = 5;
|
||||
const r = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A', result: 'hit',
|
||||
factors: ['l5_avg'], grade_id: 'g',
|
||||
});
|
||||
expect(r.skipped).toBe(true);
|
||||
expect(r.reason).toBe('thin_sample');
|
||||
});
|
||||
|
||||
test('skips on push / void', async () => {
|
||||
const r = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A', result: 'push',
|
||||
factors: ['l5_avg'], grade_id: 'g',
|
||||
});
|
||||
expect(r.skipped).toBe(true);
|
||||
expect(r.reason).toBe('non_decisive_result');
|
||||
});
|
||||
|
||||
test('skips on incomplete input', async () => {
|
||||
const r = await wa.adjustWeights({ sport: 'nba', grade: 'A', result: 'hit', factors: [] });
|
||||
expect(r.skipped).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weightAdjuster — adjustments', () => {
|
||||
test('A+ hit nudges factor up by at most 0.5%', async () => {
|
||||
const r = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A+', result: 'hit',
|
||||
factors: ['l5_hot_vs_line'], grade_id: 'g1',
|
||||
});
|
||||
expect(r.skipped).toBe(false);
|
||||
const adj = r.adjustments[0];
|
||||
expect(adj.previous).toBe(1.0);
|
||||
expect(adj.next).toBeGreaterThan(1.0);
|
||||
// confidence of A+ = 1.0, LR = 0.005 → multiplier = 1.005 → next = 1.005
|
||||
expect(adj.next).toBeCloseTo(1.005, 5);
|
||||
});
|
||||
|
||||
test('A+ miss nudges factor down by at most 0.5%', async () => {
|
||||
const r = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A+', result: 'miss',
|
||||
factors: ['l5_hot_vs_line'], grade_id: 'g2',
|
||||
});
|
||||
expect(r.adjustments[0].next).toBeCloseTo(0.995, 5);
|
||||
});
|
||||
|
||||
test('low-confidence grade produces smaller nudge', async () => {
|
||||
const high = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A+', result: 'hit',
|
||||
factors: ['x'], grade_id: 'h',
|
||||
});
|
||||
const low = await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'C', result: 'hit',
|
||||
factors: ['y'], grade_id: 'l',
|
||||
});
|
||||
expect(Math.abs(high.adjustments[0].next - 1.0))
|
||||
.toBeGreaterThan(Math.abs(low.adjustments[0].next - 1.0));
|
||||
});
|
||||
|
||||
test('weights clamp at MIN_WEIGHT and MAX_WEIGHT', () => {
|
||||
const c = wa.__internals.clamp;
|
||||
expect(c(0.05)).toBe(wa.MIN_WEIGHT);
|
||||
expect(c(99)).toBe(wa.MAX_WEIGHT);
|
||||
expect(c(2.5)).toBe(2.5);
|
||||
});
|
||||
|
||||
test('repeated adjustments stack into incrementing versions', async () => {
|
||||
await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A', result: 'hit',
|
||||
factors: ['l5_hot_vs_line'], grade_id: 'g1',
|
||||
});
|
||||
await wa.adjustWeights({
|
||||
sport: 'nba', stat_type: 'points', grade: 'A', result: 'hit',
|
||||
factors: ['l5_hot_vs_line'], grade_id: 'g2',
|
||||
});
|
||||
const history = await wa.getWeightHistory('nba', 'points', 'l5_hot_vs_line', 10);
|
||||
expect(history.length).toBe(2);
|
||||
expect(history[0].version).toBe(2);
|
||||
expect(history[1].version).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weightAdjuster — rollback', () => {
|
||||
test('rollback inserts a new row whose weight equals the target version', async () => {
|
||||
// Seed three versions.
|
||||
mockState.weightRows.push(
|
||||
{ sport: 'nba', stat_type: 'points', factor_name: 'f', weight: 1.0, version: 1 },
|
||||
{ sport: 'nba', stat_type: 'points', factor_name: 'f', weight: 1.1, version: 2 },
|
||||
{ sport: 'nba', stat_type: 'points', factor_name: 'f', weight: 1.2, version: 3 },
|
||||
);
|
||||
const ok = await wa.rollbackToVersion('nba', 'points', 'f', 1);
|
||||
expect(ok).toBe(true);
|
||||
const history = await wa.getWeightHistory('nba', 'points', 'f', 10);
|
||||
expect(history[0].weight).toBe(1.0);
|
||||
expect(history[0].version).toBe(4);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user