S5 (a1): prop viability — lineups, injury wire, date navigation
- lineupService: statsapi hydrate=lineups (live shape verified) → CONFIRMED (batting slot) / NOT_IN (team posted without the player) / PROJECTED (not posted). 10-min cache, pure parser, injectable. - NOT_IN visibly KILLS the grade on the slate: struck through + NOT IN LINEUP chip, parlay/book actions suppressed. The locked ledger read is untouched — honesty is showing the read is dead, not deleting it. - injuryService: ESPN injuries feed → OUT/GTD/PROB chips (unknown status → no chip, never invented). Chips on slate strips via ViabilityChips. - Date navigation on the Slate: YESTERDAY (results surface — finals + THE SETTLE panel of that date's settled reads w/ outcome + CLV chips, via new ?date= filter on /api/ledger/model) / TODAY / TOMORROW (schedule until lines post). Odds/grades/pitcher layers are TODAY's and never fake other dates; 60s poll only refreshes today. - Routes /api/schedule/:sport/lineups + /injuries + Next proxies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Session 64 (A1-S5) — prop viability: lineup confirmation + injury wire +
|
||||
// the NOT-IN grade kill. Real feeds only; absent → no chips, never guessed.
|
||||
|
||||
const { parseLineups, statusFor } = require('../../src/services/lineupService');
|
||||
const { parseInjuries, chipFor } = require('../../src/services/injuryService');
|
||||
const adapter = require('../../web/src/lib/slateAdapter');
|
||||
|
||||
// Real statsapi hydrate=lineups shape (verified live 2026-07-11).
|
||||
const SCHEDULE_JSON = {
|
||||
dates: [{
|
||||
games: [
|
||||
{
|
||||
gamePk: 823357,
|
||||
teams: { home: { team: { name: 'Milwaukee Brewers' } }, away: { team: { name: 'Pittsburgh Pirates' } } },
|
||||
lineups: {
|
||||
homePlayers: [
|
||||
{ id: 663968, fullName: 'Jake Mangum' },
|
||||
{ id: 664040, fullName: 'Brandon Lowe' },
|
||||
],
|
||||
awayPlayers: [{ id: 1, fullName: 'Bryan Reynolds' }],
|
||||
},
|
||||
},
|
||||
{ gamePk: 823356, teams: { home: { team: { name: 'Detroit Tigers' } }, away: { team: { name: 'Tampa Bay Rays' } } }, lineups: {} },
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
describe('lineupService — CONFIRMED / NOT_IN / PROJECTED', () => {
|
||||
const parsed = parseLineups(SCHEDULE_JSON);
|
||||
|
||||
test('posted lineup → confirmed with the batting slot', () => {
|
||||
expect(parsed.byPlayer['brandon lowe']).toEqual({ status: 'confirmed', slot: 2, team: 'Milwaukee Brewers' });
|
||||
expect(parsed.postedTeams).toContain('Milwaukee Brewers');
|
||||
});
|
||||
|
||||
test('team posted, player absent → NOT_IN', () => {
|
||||
expect(statusFor('Christian Yelich', 'Milwaukee Brewers', parsed).status).toBe('not_in');
|
||||
});
|
||||
|
||||
test('team not posted → PROJECTED (never guessed dead)', () => {
|
||||
expect(statusFor('Riley Greene', 'Detroit Tigers', parsed).status).toBe('projected');
|
||||
});
|
||||
|
||||
test('accented/variant names resolve through nameKey', () => {
|
||||
expect(statusFor('Brandon Lowé', 'Milwaukee Brewers', parsed).status).toBe('confirmed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('injuryService — OUT / GTD / PROB chips', () => {
|
||||
test('parses the ESPN feed shape', () => {
|
||||
const byPlayer = parseInjuries({
|
||||
injuries: [{
|
||||
displayName: 'Arizona Diamondbacks',
|
||||
injuries: [
|
||||
{ athlete: { displayName: 'Ketel Marte' }, status: 'Out', details: { type: 'Hamstring' } },
|
||||
{ athlete: { displayName: 'Corbin Carroll' }, status: 'Day-To-Day', shortComment: 'wrist' },
|
||||
],
|
||||
}],
|
||||
});
|
||||
expect(byPlayer['ketel marte']).toMatchObject({ status: 'OUT', detail: 'Hamstring' });
|
||||
expect(byPlayer['corbin carroll'].status).toBe('GTD');
|
||||
});
|
||||
test('unknown status text → no chip (never invented)', () => {
|
||||
expect(chipFor('Active')).toBeNull();
|
||||
expect(chipFor('')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('slateAdapter — NOT_IN kills the graded props on the strip', () => {
|
||||
const GAME = { home: 'Milwaukee Brewers', away: 'Pittsburgh Pirates' };
|
||||
const props = [{ player: 'Christian Yelich', stat_type: 'hits', line: 0.5 }];
|
||||
const gradeIndex = adapter.indexGrades([
|
||||
{ player: 'Christian Yelich', stat_type: 'hits', line: 0.5, direction: 'over', grade: 'A',
|
||||
team: 'Milwaukee Brewers', gradedAt: { line: 0.5, timestamp: 'x' } },
|
||||
]);
|
||||
// Fixture keys computed via nameKey — the same folding BOTH real sides use
|
||||
// (hand-written keys miss nickname resolution, e.g. jake→jacob).
|
||||
const { nameKey } = require('../../web/src/lib/playerName');
|
||||
const viability = {
|
||||
lineups: { byPlayer: { [nameKey('Jake Mangum')]: { status: 'confirmed', slot: 1 } }, postedTeams: ['Milwaukee Brewers'] },
|
||||
injuries: {},
|
||||
};
|
||||
|
||||
test('graded prop for a NOT_IN player renders dead (grade preserved)', () => {
|
||||
const strips = adapter.buildPlayerStripsFromProps(props, gradeIndex, {}, Date.now(), GAME, viability);
|
||||
expect(strips[0].lineup.status).toBe('not_in');
|
||||
expect(strips[0].props[0].dead).toBe(true);
|
||||
expect(strips[0].props[0].grade).toBe('A'); // struck through in UI, never deleted
|
||||
});
|
||||
|
||||
test('confirmed player carries the slot; no viability feed → no chips', () => {
|
||||
const confirmed = adapter.buildPlayerStripsFromProps(
|
||||
[{ player: 'Jake Mangum', stat_type: 'hits', line: 0.5 }],
|
||||
{}, {}, Date.now(), GAME, viability,
|
||||
);
|
||||
expect(confirmed[0].lineup).toEqual({ status: 'confirmed', slot: 1 });
|
||||
const none = adapter.buildPlayerStripsFromProps(props, gradeIndex, {}, Date.now(), GAME, null);
|
||||
expect(none[0].lineup == null).toBe(true);
|
||||
expect(none[0].props[0].dead == null).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user