2025-11-17 22:19:46 -08:00
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
|
import {
|
|
|
|
|
calculateICM,
|
|
|
|
|
calculateICMFromOdds,
|
|
|
|
|
icmResultToArray,
|
|
|
|
|
type ParticipantChips,
|
|
|
|
|
} from '../icm-calculator';
|
|
|
|
|
|
|
|
|
|
describe('icm-calculator', () => {
|
|
|
|
|
describe('calculateICM', () => {
|
|
|
|
|
it('calculates probabilities for 8 equal participants', () => {
|
|
|
|
|
const participants: ParticipantChips[] = Array.from({ length: 8 }, (_, i) => ({
|
|
|
|
|
participantId: String(i + 1),
|
|
|
|
|
championshipProbability: 0.125, // Equal 12.5% each
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(8);
|
|
|
|
|
|
|
|
|
|
// Each participant should have probabilities that sum to 1.0
|
|
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
|
|
|
|
const sum = probs.reduce((acc, p) => acc + p, 0);
|
|
|
|
|
expect(sum).toBeCloseTo(1.0, 1);
|
|
|
|
|
|
|
|
|
|
// With equal odds, probabilities vary by placement preference
|
|
|
|
|
// But should all be reasonable (not 0, not 1)
|
|
|
|
|
probs.forEach(p => {
|
|
|
|
|
expect(p).toBeGreaterThan(0);
|
|
|
|
|
expect(p).toBeLessThan(0.5);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('gives stronger team higher probabilities for better placements', () => {
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: 'strong', championshipProbability: 0.5 }, // 50%
|
|
|
|
|
{ participantId: 'weak', championshipProbability: 0.01 }, // 1%
|
|
|
|
|
...Array.from({ length: 6 }, (_, i) => ({
|
|
|
|
|
participantId: String(i + 3),
|
|
|
|
|
championshipProbability: 0.0817, // ~8.17% each
|
|
|
|
|
})),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const strongResult = results.get('strong');
|
|
|
|
|
const weakResult = results.get('weak');
|
|
|
|
|
if (!strongResult || !weakResult) throw new Error('Expected results not found');
|
|
|
|
|
const strongProbs = icmResultToArray(strongResult);
|
|
|
|
|
const weakProbs = icmResultToArray(weakResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
|
|
|
|
|
// Strong team should have higher P(1st) than weak team
|
|
|
|
|
expect(strongProbs[0]).toBeGreaterThan(weakProbs[0]);
|
|
|
|
|
|
|
|
|
|
// Strong team should have higher P(2nd) than weak team
|
|
|
|
|
expect(strongProbs[1]).toBeGreaterThan(weakProbs[1]);
|
|
|
|
|
|
|
|
|
|
// Weak team should have higher probability of worse placements
|
|
|
|
|
expect(weakProbs[7]).toBeGreaterThan(strongProbs[7]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles 32 team NHL scenario', () => {
|
|
|
|
|
// Simulate realistic NHL championship odds distribution
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: 'COL', championshipProbability: 0.154 }, // 15.4% favorite
|
|
|
|
|
{ participantId: 'FLA', championshipProbability: 0.111 }, // 11.1%
|
|
|
|
|
{ participantId: 'VGK', championshipProbability: 0.111 }, // 11.1%
|
|
|
|
|
{ participantId: 'TBL', championshipProbability: 0.091 }, // 9.1%
|
|
|
|
|
{ participantId: 'NJD', championshipProbability: 0.067 }, // 6.7%
|
|
|
|
|
{ participantId: 'TOR', championshipProbability: 0.038 }, // 3.8%
|
|
|
|
|
{ participantId: 'NYR', championshipProbability: 0.024 }, // 2.4%
|
|
|
|
|
{ participantId: 'DET', championshipProbability: 0.013 }, // 1.3%
|
|
|
|
|
// 24 more teams with decreasing odds
|
|
|
|
|
...Array.from({ length: 24 }, (_, i) => ({
|
|
|
|
|
participantId: `TEAM${i + 9}`,
|
|
|
|
|
championshipProbability: 0.013 / (i + 2), // Decreasing odds
|
|
|
|
|
})),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(32);
|
|
|
|
|
|
|
|
|
|
// Colorado (favorite) should have highest P(1st)
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const colResult32 = results.get('COL');
|
|
|
|
|
if (!colResult32) throw new Error('COL result not found');
|
|
|
|
|
const colProbs = icmResultToArray(colResult32);
|
2025-11-17 22:19:46 -08:00
|
|
|
expect(colProbs[0]).toBeGreaterThan(0.05); // Should have >5% chance of 1st
|
|
|
|
|
|
|
|
|
|
// Even the worst team should have some probability for all placements
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const worstResult = results.get('TEAM32');
|
|
|
|
|
if (!worstResult) throw new Error('TEAM32 result not found');
|
|
|
|
|
const worstProbs = icmResultToArray(worstResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
worstProbs.forEach(p => {
|
|
|
|
|
expect(p).toBeGreaterThan(0); // Not zero
|
|
|
|
|
expect(p).toBeLessThan(1); // Valid probability
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Column sums should equal 1.0 (each position distributed across all teams)
|
|
|
|
|
for (let place = 0; place < 8; place++) {
|
|
|
|
|
let colSum = 0;
|
|
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
|
|
|
|
colSum += probs[place];
|
|
|
|
|
});
|
|
|
|
|
expect(colSum).toBeCloseTo(1.0, 2);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles edge case with single participant', () => {
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: '1', championshipProbability: 1.0 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(1);
|
|
|
|
|
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const singleResult = results.get('1');
|
|
|
|
|
if (!singleResult) throw new Error('Result for participant 1 not found');
|
|
|
|
|
const probs = icmResultToArray(singleResult);
|
2026-02-17 14:24:07 -08:00
|
|
|
// With 1 participant, only 1st place is filled (removed from pool after)
|
|
|
|
|
expect(probs[0]).toBeCloseTo(1.0, 1); // 100% chance of 1st
|
2025-11-17 22:19:46 -08:00
|
|
|
const sum = probs.reduce((acc, p) => acc + p, 0);
|
2026-02-17 14:24:07 -08:00
|
|
|
expect(sum).toBeCloseTo(1.0, 1);
|
2025-11-17 22:19:46 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles zero championship probabilities gracefully', () => {
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: '1', championshipProbability: 0 },
|
|
|
|
|
{ participantId: '2', championshipProbability: 0 },
|
|
|
|
|
{ participantId: '3', championshipProbability: 0 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(3);
|
|
|
|
|
|
|
|
|
|
// Should give equal probabilities when all have zero odds
|
2026-02-17 14:24:07 -08:00
|
|
|
// With 3 teams, only positions 0-2 are filled (each team removed after placement)
|
2025-11-17 22:19:46 -08:00
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
2026-02-17 14:24:07 -08:00
|
|
|
// First 3 positions should each have ~1/3
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
expect(probs[i]).toBeCloseTo(1/3, 1);
|
|
|
|
|
}
|
|
|
|
|
// Positions 3-7 are 0 (no participants left to fill them)
|
|
|
|
|
for (let i = 3; i < 8; i++) {
|
|
|
|
|
expect(probs[i]).toBe(0);
|
|
|
|
|
}
|
2025-11-17 22:19:46 -08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('normalizes championship probabilities that do not sum to 1.0', () => {
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: '1', championshipProbability: 0.6 }, // 60% (with vig)
|
|
|
|
|
{ participantId: '2', championshipProbability: 0.55 }, // 55% (with vig)
|
|
|
|
|
// Total > 1.0, should be normalized
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(2);
|
|
|
|
|
|
2026-02-17 14:24:07 -08:00
|
|
|
// With 2 participants, only positions 0-1 are filled
|
|
|
|
|
// Verify those columns sum to 1.0
|
|
|
|
|
for (let place = 0; place < 2; place++) {
|
2025-11-17 22:19:46 -08:00
|
|
|
let colSum = 0;
|
|
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
|
|
|
|
colSum += probs[place];
|
|
|
|
|
});
|
|
|
|
|
expect(colSum).toBeCloseTo(1.0, 2);
|
|
|
|
|
}
|
2026-02-17 14:24:07 -08:00
|
|
|
|
|
|
|
|
// Participant 1 (higher prob) should have higher P(1st)
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const r1 = results.get('1');
|
|
|
|
|
const r2 = results.get('2');
|
|
|
|
|
if (!r1 || !r2) throw new Error('Results for participants 1 and 2 not found');
|
|
|
|
|
const probs1 = icmResultToArray(r1);
|
|
|
|
|
const probs2 = icmResultToArray(r2);
|
2026-02-17 14:24:07 -08:00
|
|
|
expect(probs1[0]).toBeGreaterThan(probs2[0]);
|
2025-11-17 22:19:46 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns empty map for empty input', () => {
|
|
|
|
|
const results = calculateICM([]);
|
|
|
|
|
expect(results.size).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('maintains probability ordering for sorted championship odds', () => {
|
|
|
|
|
const participants: ParticipantChips[] = [
|
|
|
|
|
{ participantId: '1st', championshipProbability: 0.40 },
|
|
|
|
|
{ participantId: '2nd', championshipProbability: 0.30 },
|
|
|
|
|
{ participantId: '3rd', championshipProbability: 0.20 },
|
|
|
|
|
{ participantId: '4th', championshipProbability: 0.10 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICM(participants);
|
|
|
|
|
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const rFirst = results.get('1st');
|
|
|
|
|
const rSecond = results.get('2nd');
|
|
|
|
|
const rThird = results.get('3rd');
|
|
|
|
|
const rFourth = results.get('4th');
|
|
|
|
|
if (!rFirst || !rSecond || !rThird || !rFourth) throw new Error('Expected results not found');
|
|
|
|
|
const first = icmResultToArray(rFirst);
|
|
|
|
|
const second = icmResultToArray(rSecond);
|
|
|
|
|
const third = icmResultToArray(rThird);
|
|
|
|
|
const fourth = icmResultToArray(rFourth);
|
2025-11-17 22:19:46 -08:00
|
|
|
|
|
|
|
|
// P(1st place) should be ordered
|
|
|
|
|
expect(first[0]).toBeGreaterThan(second[0]);
|
|
|
|
|
expect(second[0]).toBeGreaterThan(third[0]);
|
|
|
|
|
expect(third[0]).toBeGreaterThan(fourth[0]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('calculateICMFromOdds', () => {
|
|
|
|
|
it('converts American odds to ICM probabilities', () => {
|
|
|
|
|
const odds = [
|
|
|
|
|
{ participantId: 'COL', odds: 550 }, // +550
|
|
|
|
|
{ participantId: 'FLA', odds: 800 }, // +800
|
|
|
|
|
{ participantId: 'ARI', odds: 100000 }, // +100000 (longshot)
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICMFromOdds(odds);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(3);
|
|
|
|
|
|
|
|
|
|
// Colorado should have better odds than Arizona
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const colResultOdds = results.get('COL');
|
|
|
|
|
const ariResult = results.get('ARI');
|
|
|
|
|
if (!colResultOdds || !ariResult) throw new Error('COL or ARI result not found');
|
|
|
|
|
const colProbs = icmResultToArray(colResultOdds);
|
|
|
|
|
const ariProbs = icmResultToArray(ariResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
|
|
|
|
|
expect(colProbs[0]).toBeGreaterThan(ariProbs[0]);
|
|
|
|
|
|
|
|
|
|
// Even Arizona should have some probability
|
|
|
|
|
expect(ariProbs[0]).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles negative odds (favorites)', () => {
|
|
|
|
|
const odds = [
|
|
|
|
|
{ participantId: 'FAV', odds: -200 }, // Favorite
|
|
|
|
|
{ participantId: 'DOG', odds: 500 }, // Underdog
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICMFromOdds(odds);
|
|
|
|
|
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const favResult = results.get('FAV');
|
|
|
|
|
const dogResult = results.get('DOG');
|
|
|
|
|
if (!favResult || !dogResult) throw new Error('FAV or DOG result not found');
|
|
|
|
|
const favProbs = icmResultToArray(favResult);
|
|
|
|
|
const dogProbs = icmResultToArray(dogResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
|
|
|
|
|
// Favorite should have higher P(1st)
|
|
|
|
|
expect(favProbs[0]).toBeGreaterThan(dogProbs[0]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('uses custom scoring places', () => {
|
|
|
|
|
const odds = [
|
|
|
|
|
{ participantId: '1', odds: 200 },
|
|
|
|
|
{ participantId: '2', odds: 300 },
|
|
|
|
|
{ participantId: '3', odds: 400 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICMFromOdds(odds, 5); // 5 scoring places
|
|
|
|
|
|
|
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
|
|
|
|
// Should still have 8 values but calculated for 5 places
|
|
|
|
|
expect(probs).toHaveLength(8);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('icmResultToArray', () => {
|
|
|
|
|
it('converts ICM result to array format', () => {
|
|
|
|
|
const icmResult = {
|
|
|
|
|
participantId: 'TEST',
|
|
|
|
|
probabilities: {
|
|
|
|
|
first: 0.25,
|
|
|
|
|
second: 0.20,
|
|
|
|
|
third: 0.15,
|
|
|
|
|
fourth: 0.12,
|
|
|
|
|
fifth: 0.10,
|
|
|
|
|
sixth: 0.08,
|
|
|
|
|
seventh: 0.06,
|
|
|
|
|
eighth: 0.04,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const array = icmResultToArray(icmResult);
|
|
|
|
|
|
|
|
|
|
expect(array).toEqual([0.25, 0.20, 0.15, 0.12, 0.10, 0.08, 0.06, 0.04]);
|
|
|
|
|
expect(array).toHaveLength(8);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('integration: realistic NHL 32-team scenario', () => {
|
|
|
|
|
it('calculates reasonable probabilities for full NHL league', () => {
|
|
|
|
|
// Full 32-team NHL with realistic odds distribution
|
|
|
|
|
const odds = [
|
|
|
|
|
{ participantId: 'COL', odds: 550 },
|
|
|
|
|
{ participantId: 'FLA', odds: 800 },
|
|
|
|
|
{ participantId: 'VGK', odds: 800 },
|
|
|
|
|
{ participantId: 'TBL', odds: 1000 },
|
|
|
|
|
{ participantId: 'NJD', odds: 1400 },
|
|
|
|
|
{ participantId: 'TOR', odds: 2500 },
|
|
|
|
|
{ participantId: 'NYR', odds: 4000 },
|
|
|
|
|
{ participantId: 'DET', odds: 7500 },
|
|
|
|
|
{ participantId: 'VAN', odds: 7500 },
|
|
|
|
|
{ participantId: 'NYI', odds: 15000 },
|
|
|
|
|
{ participantId: 'NSH', odds: 40000 },
|
|
|
|
|
// 21 more teams with increasing odds
|
|
|
|
|
...Array.from({ length: 21 }, (_, i) => ({
|
|
|
|
|
participantId: `TEAM${i + 12}`,
|
|
|
|
|
odds: 40000 + (i + 1) * 5000, // Increasing odds
|
|
|
|
|
})),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const results = calculateICMFromOdds(odds);
|
|
|
|
|
|
|
|
|
|
expect(results.size).toBe(32);
|
|
|
|
|
|
|
|
|
|
// Favorite (Colorado) should have reasonable championship probability
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const colResultInteg = results.get('COL');
|
|
|
|
|
const detResult = results.get('DET');
|
|
|
|
|
const longResult = results.get('TEAM32');
|
|
|
|
|
if (!colResultInteg || !detResult || !longResult) throw new Error('Expected results not found');
|
|
|
|
|
const colProbs = icmResultToArray(colResultInteg);
|
2025-11-17 22:19:46 -08:00
|
|
|
expect(colProbs[0]).toBeGreaterThan(0.05); // >5% for 1st
|
|
|
|
|
expect(colProbs[0]).toBeLessThan(0.35); // <35% for 1st (not guaranteed)
|
|
|
|
|
|
|
|
|
|
// Middle team (Detroit) should have middling probabilities
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const detProbs = icmResultToArray(detResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
expect(detProbs[0]).toBeGreaterThan(0); // Some chance
|
|
|
|
|
expect(detProbs[0]).toBeLessThan(0.10); // But not high
|
|
|
|
|
|
|
|
|
|
// Longshot should have very small but non-zero probabilities
|
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196)
* Fix no-shadow and consistent-function-scoping lint violations
Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint
warnings and promotes both rules to errors in .oxlintrc.json.
no-shadow: renamed Drizzle callback params (sports→s, matches→m,
seasons→s) to avoid shadowing outer imports; removed shadowed
destructures (eq, inArray) from where callbacks; renamed inner
template→bracketTemplate, prev→currentTimers, season→ss, name→teamName
(with name: teamName fix to preserve semantics).
consistent-function-scoping: moved formatDate, getRankBadge,
getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr,
elo (×2), weightedPick, sortByMatchNumber (×2) to module scope;
moved formatTime (×2), isValidLeagueName, getDraftTimes,
makeSeasonQueues to file scope in test files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix no-non-null-assertion lint violations and promote to error
Eliminates all 208 no-non-null-assertion warnings across 38 files.
Promotes typescript/no-non-null-assertion from warn to error in
.oxlintrc.json.
Fix patterns applied:
- Map.get(key)! after .has() check → extract with get() + null guard
- Map.get(key)! on pre-populated count maps → ?? 0 default
- .set(id, map.get(id)! + 1) increment → ?? 0 before adding
- participant1Id!/participant2Id! on DB matches → ?? "" fallback
- array.find()! in tests → guard + throw or expect().toBeDefined()
- bracketTemplateCache.get(id)! → null guard extract
- Various nullable field accesses → optional chain or ?? default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers
Resolves all 9 remaining non-console lint warnings and promotes all
three rules to errors in .oxlintrc.json.
- prefer-add-event-listener: converted onchange/onclick/onload
assignments to addEventListener in useDraftNotifications.ts and
admin.data-sync.tsx; stored changeHandler ref for proper cleanup
with removeEventListener
- no-unassigned-import: configured rule with allow list for legitimate
side-effect imports (*.css, @testing-library/jest-dom,
@testing-library/cypress/add-commands)
- require-module-specifiers: removed redundant `export {}` from
cypress/support/e2e.ts (file already has an import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix TypeScript errors from no-non-null-assertion fixes
Two fixes introduced by the non-null assertion cleanup produced type
errors:
- scoring-event.ts: `?? ""` was wrong type for a participant object map;
restructured to explicit null guards so TypeScript can narrow correctly
- standings-sync/index.ts: `?? null` after name-match lookup lost the
truthy guarantee, causing TS18047 on the write-back block; added
`participant &&` guard before accessing its properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add npm run typecheck as Stop hook in Claude settings
Runs a full project typecheck at the end of each Claude turn so type
errors surface as feedback before the next message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
|
|
|
const longProbs = icmResultToArray(longResult);
|
2025-11-17 22:19:46 -08:00
|
|
|
expect(longProbs[0]).toBeGreaterThan(0); // Not impossible
|
|
|
|
|
expect(longProbs[0]).toBeLessThan(0.05); // But unlikely (with 32 teams, even worst has ~3% uniform)
|
|
|
|
|
|
|
|
|
|
// Column sums should equal 1.0 (each position distributed across all teams)
|
|
|
|
|
for (let place = 0; place < 8; place++) {
|
|
|
|
|
let colSum = 0;
|
|
|
|
|
results.forEach((result) => {
|
|
|
|
|
const probs = icmResultToArray(result);
|
|
|
|
|
colSum += probs[place];
|
|
|
|
|
});
|
|
|
|
|
expect(colSum).toBeCloseTo(1.0, 2);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|