Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+132
View File
@@ -0,0 +1,132 @@
const pe = require('../../src/services/intelligence/probabilityEstimator');
function logsAt(values) {
return values.map((v) => ({ points: v }));
}
describe('probabilityEstimator.estimateProbability', () => {
test('30-pt scorer with 25.5 line → p_over > 0.70', () => {
const r = pe.estimateProbability({
gameLogs: logsAt([32, 28, 31, 27, 33, 29, 30, 26, 35, 28]),
line: 25.5,
statType: 'points',
});
expect(r.p_over).toBeGreaterThan(0.70);
expect(r.p_under).toBeCloseTo(1 - r.p_over, 5);
});
test('20-pt scorer with 25.5 line → p_over < 0.40', () => {
const r = pe.estimateProbability({
gameLogs: logsAt([18, 22, 19, 21, 20, 17, 23, 19, 18, 22]),
line: 25.5,
statType: 'points',
});
expect(r.p_over).toBeLessThan(0.40);
});
test('home game bumps p_over by ~0.015', () => {
const base = pe.estimateProbability({
gameLogs: logsAt([28, 26, 27, 25, 24]),
line: 25.5,
statType: 'points',
features: {},
});
const home = pe.estimateProbability({
gameLogs: logsAt([28, 26, 27, 25, 24]),
line: 25.5,
statType: 'points',
features: { home_away: 1.0 },
});
expect(home.p_over - base.p_over).toBeCloseTo(0.015, 5);
});
test('weak opponent (rank >= 0.70) adds ~0.03', () => {
const base = pe.estimateProbability({
gameLogs: logsAt([26, 25, 27, 24, 26]),
line: 25.5,
statType: 'points',
});
const weakOpp = pe.estimateProbability({
gameLogs: logsAt([26, 25, 27, 24, 26]),
line: 25.5,
statType: 'points',
features: { opp_rank_stat: 0.85 },
});
expect(weakOpp.p_over - base.p_over).toBeCloseTo(0.03, 5);
});
test('top-defense opponent (rank <= 0.30) subtracts ~0.03', () => {
const base = pe.estimateProbability({
gameLogs: logsAt([26, 25, 27, 24, 26]),
line: 25.5,
statType: 'points',
});
const stiff = pe.estimateProbability({
gameLogs: logsAt([26, 25, 27, 24, 26]),
line: 25.5,
statType: 'points',
features: { opp_rank_stat: 0.1 },
});
expect(base.p_over - stiff.p_over).toBeCloseTo(0.03, 5);
});
test('volatile player (high cv) gets pulled toward 0.50', () => {
// Use a 4/5 sample so base p ≈ 0.8 (well below the 0.95 ceiling)
// — that way the pull-toward-0.5 has visible room to move.
const stable = pe.estimateProbability({
gameLogs: logsAt([28, 24, 29, 27, 30]),
line: 25.5,
statType: 'points',
features: { l10_stddev: 1.0, l20_avg: 27.0 }, // cv ~0.04
});
const wild = pe.estimateProbability({
gameLogs: logsAt([28, 24, 29, 27, 30]),
line: 25.5,
statType: 'points',
features: { l10_stddev: 14.0, l20_avg: 27.0 }, // cv = 0.5, volatile
});
expect(wild.p_over).toBeLessThan(stable.p_over);
});
test('clamps to [0.10, 0.95]', () => {
const ceil = pe.estimateProbability({
gameLogs: logsAt([50, 48, 52, 49, 51]),
line: 10,
statType: 'points',
features: { opp_rank_stat: 0.95, home_away: 1.0 },
});
const floor = pe.estimateProbability({
gameLogs: logsAt([5, 6, 4, 7, 5]),
line: 30,
statType: 'points',
features: { opp_rank_stat: 0.05, home_away: 0.0 },
});
expect(ceil.p_over).toBeLessThanOrEqual(0.95);
expect(floor.p_over).toBeGreaterThanOrEqual(0.10);
});
test('fewer than 5 games uses all available for recency', () => {
const r = pe.estimateProbability({
gameLogs: logsAt([30, 28]),
line: 25.5,
statType: 'points',
});
expect(r.p_over).toBeGreaterThan(0.5);
});
test('returns nulls on empty input', () => {
const r = pe.estimateProbability({ gameLogs: [], line: 25.5, statType: 'points' });
expect(r.p_over).toBeNull();
expect(r.reason).toBe('insufficient_data');
});
test('all-push sample returns reason all_pushes', () => {
const r = pe.estimateProbability({
gameLogs: logsAt([25.5, 25.5, 25.5]),
line: 25.5,
statType: 'points',
});
expect(r.p_over).toBeNull();
expect(r.reason).toBe('all_pushes');
});
});