@ttt-productions/q-core 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/business.d.ts +12 -0
- package/dist/constants/business.d.ts.map +1 -1
- package/dist/constants/business.js +24 -0
- package/dist/constants/business.js.map +1 -1
- package/dist/constants/media.d.ts +1 -0
- package/dist/constants/media.d.ts.map +1 -1
- package/dist/constants/media.js +5 -0
- package/dist/constants/media.js.map +1 -1
- package/dist/constants/sports.js.map +1 -1
- package/dist/constants/storage-keys.js.map +1 -1
- package/dist/doc-schemas/admin-notifications.d.ts +1 -0
- package/dist/doc-schemas/admin-notifications.d.ts.map +1 -1
- package/dist/doc-schemas/admin-notifications.js +4 -0
- package/dist/doc-schemas/admin-notifications.js.map +1 -1
- package/dist/doc-schemas/atoms.d.ts +2 -0
- package/dist/doc-schemas/atoms.d.ts.map +1 -1
- package/dist/doc-schemas/audit-event.d.ts +2 -0
- package/dist/doc-schemas/audit-event.d.ts.map +1 -1
- package/dist/doc-schemas/catalogs.js.map +1 -1
- package/dist/doc-schemas/gallery.d.ts +1 -0
- package/dist/doc-schemas/gallery.d.ts.map +1 -1
- package/dist/doc-schemas/gallery.js +3 -0
- package/dist/doc-schemas/gallery.js.map +1 -1
- package/dist/doc-schemas/game-location.js.map +1 -1
- package/dist/doc-schemas/game-stats.js.map +1 -1
- package/dist/doc-schemas/league-team.js.map +1 -1
- package/dist/doc-schemas/migration-console.d.ts +2 -0
- package/dist/doc-schemas/migration-console.d.ts.map +1 -1
- package/dist/doc-schemas/migration-console.js +5 -0
- package/dist/doc-schemas/migration-console.js.map +1 -1
- package/dist/doc-schemas/pending-media.d.ts +1 -0
- package/dist/doc-schemas/pending-media.d.ts.map +1 -1
- package/dist/doc-schemas/pending-media.js +8 -0
- package/dist/doc-schemas/pending-media.js.map +1 -1
- package/dist/doc-schemas/registry.d.ts +12 -0
- package/dist/doc-schemas/registry.d.ts.map +1 -1
- package/dist/doc-schemas/registry.js.map +1 -1
- package/dist/doc-schemas/sport-game.js.map +1 -1
- package/dist/doc-schemas/stats-operation.d.ts.map +1 -1
- package/dist/doc-schemas/stats-operation.js +6 -11
- package/dist/doc-schemas/stats-operation.js.map +1 -1
- package/dist/doc-schemas/system.d.ts +7 -0
- package/dist/doc-schemas/system.d.ts.map +1 -1
- package/dist/doc-schemas/system.js +4 -1
- package/dist/doc-schemas/system.js.map +1 -1
- package/dist/doc-schemas/user-profile.d.ts +1 -0
- package/dist/doc-schemas/user-profile.d.ts.map +1 -1
- package/dist/doc-schemas/user-profile.js +5 -0
- package/dist/doc-schemas/user-profile.js.map +1 -1
- package/dist/doc-schemas/waiver-template.d.ts +1 -0
- package/dist/doc-schemas/waiver-template.d.ts.map +1 -1
- package/dist/doc-schemas/waiver-template.js +10 -1
- package/dist/doc-schemas/waiver-template.js.map +1 -1
- package/dist/doc-schemas/waivers-and-hardware.d.ts +1 -0
- package/dist/doc-schemas/waivers-and-hardware.d.ts.map +1 -1
- package/dist/doc-schemas/waivers-and-hardware.js +17 -9
- package/dist/doc-schemas/waivers-and-hardware.js.map +1 -1
- package/dist/ids/index.js.map +1 -1
- package/dist/ids/minted-ids.d.ts +1 -0
- package/dist/ids/minted-ids.d.ts.map +1 -1
- package/dist/ids/minted-ids.js +6 -0
- package/dist/ids/minted-ids.js.map +1 -1
- package/dist/ids/stats-operation-ids.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/input-formats/index.d.ts +2 -2
- package/dist/input-formats/index.d.ts.map +1 -1
- package/dist/input-formats/index.js +2 -2
- package/dist/input-formats/index.js.map +1 -1
- package/dist/media/file-origins.js.map +1 -1
- package/dist/notifications/archive-all.d.ts +2 -0
- package/dist/notifications/archive-all.d.ts.map +1 -0
- package/dist/notifications/archive-all.js +6 -0
- package/dist/notifications/archive-all.js.map +1 -0
- package/dist/notifications/index.d.ts +1 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +1 -0
- package/dist/notifications/index.js.map +1 -1
- package/dist/paths/collection-refs.js.map +1 -1
- package/dist/paths/collections.js.map +1 -1
- package/dist/paths/path-builders.js.map +1 -1
- package/dist/paths/utils.js.map +1 -1
- package/dist/schemas/account-settings.js.map +1 -1
- package/dist/schemas/admin-notifications.d.ts +1 -0
- package/dist/schemas/admin-notifications.d.ts.map +1 -1
- package/dist/schemas/admin-notifications.js +4 -2
- package/dist/schemas/admin-notifications.js.map +1 -1
- package/dist/schemas/app-config.d.ts +2 -2
- package/dist/schemas/app-config.d.ts.map +1 -1
- package/dist/schemas/app-config.js +9 -5
- package/dist/schemas/app-config.js.map +1 -1
- package/dist/schemas/atoms.js.map +1 -1
- package/dist/schemas/awards.d.ts +16 -4
- package/dist/schemas/awards.d.ts.map +1 -1
- package/dist/schemas/awards.js +10 -6
- package/dist/schemas/awards.js.map +1 -1
- package/dist/schemas/game-locations.d.ts +4 -4
- package/dist/schemas/game-locations.d.ts.map +1 -1
- package/dist/schemas/game-locations.js +11 -3
- package/dist/schemas/game-locations.js.map +1 -1
- package/dist/schemas/hardware-loan-contracts.js.map +1 -1
- package/dist/schemas/migration-console.d.ts +1 -0
- package/dist/schemas/migration-console.d.ts.map +1 -1
- package/dist/schemas/pictures.d.ts +3 -0
- package/dist/schemas/pictures.d.ts.map +1 -1
- package/dist/schemas/schedules.js.map +1 -1
- package/dist/schemas/scoring.js.map +1 -1
- package/dist/schemas/stats.d.ts +2 -0
- package/dist/schemas/stats.d.ts.map +1 -1
- package/dist/schemas/stats.js.map +1 -1
- package/dist/schemas/team-joining.js.map +1 -1
- package/dist/schemas/teams.d.ts +3 -2
- package/dist/schemas/teams.d.ts.map +1 -1
- package/dist/schemas/teams.js +19 -9
- package/dist/schemas/teams.js.map +1 -1
- package/dist/schemas/uploads.js.map +1 -1
- package/dist/schemas/users.d.ts +7 -3
- package/dist/schemas/users.d.ts.map +1 -1
- package/dist/schemas/users.js +10 -3
- package/dist/schemas/users.js.map +1 -1
- package/dist/schemas/waiver-templates.d.ts +1 -0
- package/dist/schemas/waiver-templates.d.ts.map +1 -1
- package/dist/schemas/waiver-templates.js +4 -1
- package/dist/schemas/waiver-templates.js.map +1 -1
- package/dist/sports/basketball-scoring.js.map +1 -1
- package/dist/sports/basketball.js.map +1 -1
- package/dist/sports/bulk-entry.js.map +1 -1
- package/dist/sports/contribution.js.map +1 -1
- package/dist/sports/curling-scoring.js.map +1 -1
- package/dist/sports/finalized-record.js.map +1 -1
- package/dist/sports/football-scoring.js.map +1 -1
- package/dist/sports/index.d.ts +1 -0
- package/dist/sports/index.d.ts.map +1 -1
- package/dist/sports/index.js +1 -0
- package/dist/sports/index.js.map +1 -1
- package/dist/sports/ledger-continuity.d.ts +13 -0
- package/dist/sports/ledger-continuity.d.ts.map +1 -0
- package/dist/sports/ledger-continuity.js +19 -0
- package/dist/sports/ledger-continuity.js.map +1 -0
- package/dist/sports/rankings.js.map +1 -1
- package/dist/sports/rollup-check.js.map +1 -1
- package/dist/sports/scoring.js.map +1 -1
- package/dist/sports/store-driver.js.map +1 -1
- package/dist/types/audit.d.ts +1 -1
- package/dist/types/audit.d.ts.map +1 -1
- package/dist/types/audit.js +5 -2
- package/dist/types/audit.js.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/types/notifications.js.map +1 -1
- package/dist/types/picture-owners.js.map +1 -1
- package/dist/types/sports.js.map +1 -1
- package/dist/types/stats.js.map +1 -1
- package/dist/types/unions.d.ts +4 -2
- package/dist/types/unions.d.ts.map +1 -1
- package/dist/types/unions.js +6 -0
- package/dist/types/unions.js.map +1 -1
- package/dist/upload-variables/index.d.ts +3 -0
- package/dist/upload-variables/index.d.ts.map +1 -0
- package/dist/upload-variables/index.js +3 -0
- package/dist/upload-variables/index.js.map +1 -0
- package/dist/upload-variables/on-progress.d.ts +4 -0
- package/dist/upload-variables/on-progress.d.ts.map +1 -0
- package/dist/upload-variables/on-progress.js +10 -0
- package/dist/upload-variables/on-progress.js.map +1 -0
- package/dist/upload-variables/picture-upload-variables.d.ts +32 -0
- package/dist/upload-variables/picture-upload-variables.d.ts.map +1 -0
- package/dist/upload-variables/picture-upload-variables.js +21 -0
- package/dist/upload-variables/picture-upload-variables.js.map +1 -0
- package/dist/utils/account-status.js.map +1 -1
- package/dist/utils/app-config.d.ts +17 -3
- package/dist/utils/app-config.d.ts.map +1 -1
- package/dist/utils/app-config.js +21 -14
- package/dist/utils/app-config.js.map +1 -1
- package/dist/utils/app-routes.js.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/league-calendar.js.map +1 -1
- package/dist/utils/league-mode.js.map +1 -1
- package/dist/utils/team-member-actions.js.map +1 -1
- package/package.json +94 -90
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basketball.js","sourceRoot":"","sources":["../../src/sports/basketball.ts"],"names":[],"mappings":"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","sourcesContent":["// The basketball adapter: player-scored — a team's score is the sum of its players' points — with\r\n// a hall of fame and a historical baseline. Its scoring, display, and bulk-entry members are in\r\n// ./basketball-scoring.ts.\r\n\r\nimport { SPORT_LINE_SCHEMAS } from '../doc-schemas/aggregate-stats.js';\r\nimport type { BasketballPlayerTotals, BasketballTeamSeasonStats } from '../doc-schemas/aggregate-stats.js';\r\nimport type { BasketballPlayerStats } from '../doc-schemas/game-stats.js';\r\nimport {\r\n BASKETBALL_PLAYER_STAT_KEYS,\r\n BASKETBALL_STAT_EVENT_TYPE_KEYS,\r\n BASKETBALL_TEAM_STAT_KEYS,\r\n SPORT_KEY_BY_LEAGUE_TYPE,\r\n} from '../types/sports.js';\r\nimport {\r\n BASKETBALL_BULK_ENTRY,\r\n BASKETBALL_EVENT_LABELS,\r\n BASKETBALL_FIELD_LABELS,\r\n BASKETBALL_SCOREBOARD,\r\n BASKETBALL_SCORING,\r\n BASKETBALL_SCORING_PANEL,\r\n describeBasketballEvent,\r\n emptyBasketballTeamLine,\r\n} from './basketball-scoring.js';\r\nimport type { ColumnSort, StatColumn } from './column-catalog.js';\r\nimport { lifetimeSummaryFromColumns } from './lifetime-summary.js';\r\nimport { GAMES_PLAYED_COLUMN, TEAM_SEASON_BASE_COLUMN, TEAM_SEASON_BASE_COLUMNS } from './shared-columns.js';\r\nimport type { SportAdapter } from './sport-adapter.js';\r\n\r\n// Each figure's copy, declared once and shared by the player and team columns that show it.\r\nconst POINTS = { id: 'points', label: 'Points', shortLabel: 'PTS' } as const;\r\nconst POINTS_PER_GAME = { id: 'pointsPerGame', label: 'Points per game', shortLabel: 'PPG' } as const;\r\nconst REBOUNDS = { id: 'rebounds', label: 'Rebounds', shortLabel: 'REB' } as const;\r\nconst REBOUNDS_PER_GAME = { id: 'reboundsPerGame', label: 'Rebounds per game', shortLabel: 'RPG' } as const;\r\nconst ASSISTS = { id: 'assists', label: 'Assists', shortLabel: 'AST' } as const;\r\nconst ASSISTS_PER_GAME = { id: 'assistsPerGame', label: 'Assists per game', shortLabel: 'APG' } as const;\r\n// FP is stored on every player line, never computed for display (`deriveLine` writes it).\r\nconst FANTASY_POINTS = { id: 'fantasyPoints', label: 'Fantasy points', shortLabel: 'FP' } as const;\r\nconst FANTASY_POINTS_PER_GAME = { id: 'fantasyPointsPerGame', label: 'Fantasy points per game', shortLabel: 'FP/G' } as const;\r\n\r\nconst BY_POINTS: readonly ColumnSort[] = [{ columnId: POINTS.id, direction: 'desc' }];\r\n\r\nconst playerTotalsColumns: readonly StatColumn<BasketballPlayerTotals>[] = [\r\n GAMES_PLAYED_COLUMN,\r\n { ...POINTS, value: (line) => line.points, perGame: POINTS_PER_GAME, sort: 'desc' },\r\n { ...REBOUNDS, value: (line) => line.rebounds, perGame: REBOUNDS_PER_GAME, sort: 'desc' },\r\n { ...ASSISTS, value: (line) => line.assists, perGame: ASSISTS_PER_GAME, sort: 'desc' },\r\n { ...FANTASY_POINTS, value: (line) => line.fantasyPoints, perGame: FANTASY_POINTS_PER_GAME, sort: 'desc' },\r\n];\r\n\r\nconst gamePlayerColumns: readonly StatColumn<BasketballPlayerStats>[] = [\r\n { ...POINTS, value: (line) => line.points, sort: 'desc' },\r\n { ...REBOUNDS, value: (line) => line.rebounds, sort: 'desc' },\r\n { ...ASSISTS, value: (line) => line.assists, sort: 'desc' },\r\n { ...FANTASY_POINTS, value: (line) => line.fantasyPoints, sort: 'desc' },\r\n];\r\n\r\nconst teamSeasonColumns: readonly StatColumn<BasketballTeamSeasonStats>[] = [\r\n ...TEAM_SEASON_BASE_COLUMNS,\r\n { ...REBOUNDS, value: (line) => line.rebounds, perGame: REBOUNDS_PER_GAME, sort: 'desc' },\r\n { ...ASSISTS, value: (line) => line.assists, perGame: ASSISTS_PER_GAME, sort: 'desc' },\r\n];\r\n\r\nexport const BASKETBALL_ADAPTER: SportAdapter<'Basketball'> = {\r\n leagueType: 'Basketball',\r\n sportKey: SPORT_KEY_BY_LEAGUE_TYPE.Basketball,\r\n eventTypes: BASKETBALL_STAT_EVENT_TYPE_KEYS,\r\n teamStatKeys: BASKETBALL_TEAM_STAT_KEYS,\r\n playerStatKeys: BASKETBALL_PLAYER_STAT_KEYS,\r\n hallOfFame: true,\r\n historicalBaseline: true,\r\n lineSchemas: SPORT_LINE_SCHEMAS.Basketball,\r\n ...BASKETBALL_SCORING,\r\n display: {\r\n label: 'Basketball',\r\n iconKey: 'basketballBall',\r\n lifetimeSummary: lifetimeSummaryFromColumns(playerTotalsColumns),\r\n eventLabels: BASKETBALL_EVENT_LABELS,\r\n fieldLabels: BASKETBALL_FIELD_LABELS,\r\n describeEvent: describeBasketballEvent,\r\n scoreboard: BASKETBALL_SCOREBOARD,\r\n scoringPanel: BASKETBALL_SCORING_PANEL,\r\n },\r\n columns: {\r\n playerTotals: {\r\n columns: playerTotalsColumns,\r\n defaultSort: BY_POINTS,\r\n presets: [\r\n {\r\n id: 'totals',\r\n label: 'Totals',\r\n columnIds: [GAMES_PLAYED_COLUMN.id, POINTS.id, REBOUNDS.id, ASSISTS.id, FANTASY_POINTS.id],\r\n },\r\n {\r\n id: 'perGame',\r\n label: 'Per game',\r\n columnIds: [\r\n GAMES_PLAYED_COLUMN.id,\r\n POINTS_PER_GAME.id,\r\n REBOUNDS_PER_GAME.id,\r\n ASSISTS_PER_GAME.id,\r\n FANTASY_POINTS_PER_GAME.id,\r\n ],\r\n },\r\n ],\r\n },\r\n gamePlayer: {\r\n columns: gamePlayerColumns,\r\n defaultSort: BY_POINTS,\r\n defaultLayout: [POINTS.id, REBOUNDS.id, ASSISTS.id, FANTASY_POINTS.id],\r\n },\r\n teamSeason: {\r\n columns: teamSeasonColumns,\r\n defaultSort: [\r\n { columnId: TEAM_SEASON_BASE_COLUMN.wins.id, direction: 'desc' },\r\n { columnId: TEAM_SEASON_BASE_COLUMN.losses.id, direction: 'asc' },\r\n { columnId: TEAM_SEASON_BASE_COLUMN.pointDifferential.id, direction: 'desc' },\r\n ],\r\n defaultLayout: [\r\n TEAM_SEASON_BASE_COLUMN.wins.id,\r\n TEAM_SEASON_BASE_COLUMN.losses.id,\r\n TEAM_SEASON_BASE_COLUMN.pointsFor.id,\r\n TEAM_SEASON_BASE_COLUMN.pointsAgainst.id,\r\n TEAM_SEASON_BASE_COLUMN.pointDifferential.id,\r\n REBOUNDS.id,\r\n ASSISTS.id,\r\n ],\r\n },\r\n },\r\n emptyStats: (game) => ({\r\n teamStats: Object.fromEntries(game.teamIds.map((teamId) => [teamId, emptyBasketballTeamLine()])),\r\n playerStats: {},\r\n }),\r\n bulkEntry: BASKETBALL_BULK_ENTRY,\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"basketball.js","sourceRoot":"","sources":["../../src/sports/basketball.ts"],"names":[],"mappings":"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","sourcesContent":["// The basketball adapter: player-scored — a team's score is the sum of its players' points — with\n// a hall of fame and a historical baseline. Its scoring, display, and bulk-entry members are in\n// ./basketball-scoring.ts.\n\nimport { SPORT_LINE_SCHEMAS } from '../doc-schemas/aggregate-stats.js';\nimport type { BasketballPlayerTotals, BasketballTeamSeasonStats } from '../doc-schemas/aggregate-stats.js';\nimport type { BasketballPlayerStats } from '../doc-schemas/game-stats.js';\nimport {\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_STAT_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n SPORT_KEY_BY_LEAGUE_TYPE,\n} from '../types/sports.js';\nimport {\n BASKETBALL_BULK_ENTRY,\n BASKETBALL_EVENT_LABELS,\n BASKETBALL_FIELD_LABELS,\n BASKETBALL_SCOREBOARD,\n BASKETBALL_SCORING,\n BASKETBALL_SCORING_PANEL,\n describeBasketballEvent,\n emptyBasketballTeamLine,\n} from './basketball-scoring.js';\nimport type { ColumnSort, StatColumn } from './column-catalog.js';\nimport { lifetimeSummaryFromColumns } from './lifetime-summary.js';\nimport { GAMES_PLAYED_COLUMN, TEAM_SEASON_BASE_COLUMN, TEAM_SEASON_BASE_COLUMNS } from './shared-columns.js';\nimport type { SportAdapter } from './sport-adapter.js';\n\n// Each figure's copy, declared once and shared by the player and team columns that show it.\nconst POINTS = { id: 'points', label: 'Points', shortLabel: 'PTS' } as const;\nconst POINTS_PER_GAME = { id: 'pointsPerGame', label: 'Points per game', shortLabel: 'PPG' } as const;\nconst REBOUNDS = { id: 'rebounds', label: 'Rebounds', shortLabel: 'REB' } as const;\nconst REBOUNDS_PER_GAME = { id: 'reboundsPerGame', label: 'Rebounds per game', shortLabel: 'RPG' } as const;\nconst ASSISTS = { id: 'assists', label: 'Assists', shortLabel: 'AST' } as const;\nconst ASSISTS_PER_GAME = { id: 'assistsPerGame', label: 'Assists per game', shortLabel: 'APG' } as const;\n// FP is stored on every player line, never computed for display (`deriveLine` writes it).\nconst FANTASY_POINTS = { id: 'fantasyPoints', label: 'Fantasy points', shortLabel: 'FP' } as const;\nconst FANTASY_POINTS_PER_GAME = { id: 'fantasyPointsPerGame', label: 'Fantasy points per game', shortLabel: 'FP/G' } as const;\n\nconst BY_POINTS: readonly ColumnSort[] = [{ columnId: POINTS.id, direction: 'desc' }];\n\nconst playerTotalsColumns: readonly StatColumn<BasketballPlayerTotals>[] = [\n GAMES_PLAYED_COLUMN,\n { ...POINTS, value: (line) => line.points, perGame: POINTS_PER_GAME, sort: 'desc' },\n { ...REBOUNDS, value: (line) => line.rebounds, perGame: REBOUNDS_PER_GAME, sort: 'desc' },\n { ...ASSISTS, value: (line) => line.assists, perGame: ASSISTS_PER_GAME, sort: 'desc' },\n { ...FANTASY_POINTS, value: (line) => line.fantasyPoints, perGame: FANTASY_POINTS_PER_GAME, sort: 'desc' },\n];\n\nconst gamePlayerColumns: readonly StatColumn<BasketballPlayerStats>[] = [\n { ...POINTS, value: (line) => line.points, sort: 'desc' },\n { ...REBOUNDS, value: (line) => line.rebounds, sort: 'desc' },\n { ...ASSISTS, value: (line) => line.assists, sort: 'desc' },\n { ...FANTASY_POINTS, value: (line) => line.fantasyPoints, sort: 'desc' },\n];\n\nconst teamSeasonColumns: readonly StatColumn<BasketballTeamSeasonStats>[] = [\n ...TEAM_SEASON_BASE_COLUMNS,\n { ...REBOUNDS, value: (line) => line.rebounds, perGame: REBOUNDS_PER_GAME, sort: 'desc' },\n { ...ASSISTS, value: (line) => line.assists, perGame: ASSISTS_PER_GAME, sort: 'desc' },\n];\n\nexport const BASKETBALL_ADAPTER: SportAdapter<'Basketball'> = {\n leagueType: 'Basketball',\n sportKey: SPORT_KEY_BY_LEAGUE_TYPE.Basketball,\n eventTypes: BASKETBALL_STAT_EVENT_TYPE_KEYS,\n teamStatKeys: BASKETBALL_TEAM_STAT_KEYS,\n playerStatKeys: BASKETBALL_PLAYER_STAT_KEYS,\n hallOfFame: true,\n historicalBaseline: true,\n lineSchemas: SPORT_LINE_SCHEMAS.Basketball,\n ...BASKETBALL_SCORING,\n display: {\n label: 'Basketball',\n iconKey: 'basketballBall',\n lifetimeSummary: lifetimeSummaryFromColumns(playerTotalsColumns),\n eventLabels: BASKETBALL_EVENT_LABELS,\n fieldLabels: BASKETBALL_FIELD_LABELS,\n describeEvent: describeBasketballEvent,\n scoreboard: BASKETBALL_SCOREBOARD,\n scoringPanel: BASKETBALL_SCORING_PANEL,\n },\n columns: {\n playerTotals: {\n columns: playerTotalsColumns,\n defaultSort: BY_POINTS,\n presets: [\n {\n id: 'totals',\n label: 'Totals',\n columnIds: [GAMES_PLAYED_COLUMN.id, POINTS.id, REBOUNDS.id, ASSISTS.id, FANTASY_POINTS.id],\n },\n {\n id: 'perGame',\n label: 'Per game',\n columnIds: [\n GAMES_PLAYED_COLUMN.id,\n POINTS_PER_GAME.id,\n REBOUNDS_PER_GAME.id,\n ASSISTS_PER_GAME.id,\n FANTASY_POINTS_PER_GAME.id,\n ],\n },\n ],\n },\n gamePlayer: {\n columns: gamePlayerColumns,\n defaultSort: BY_POINTS,\n defaultLayout: [POINTS.id, REBOUNDS.id, ASSISTS.id, FANTASY_POINTS.id],\n },\n teamSeason: {\n columns: teamSeasonColumns,\n defaultSort: [\n { columnId: TEAM_SEASON_BASE_COLUMN.wins.id, direction: 'desc' },\n { columnId: TEAM_SEASON_BASE_COLUMN.losses.id, direction: 'asc' },\n { columnId: TEAM_SEASON_BASE_COLUMN.pointDifferential.id, direction: 'desc' },\n ],\n defaultLayout: [\n TEAM_SEASON_BASE_COLUMN.wins.id,\n TEAM_SEASON_BASE_COLUMN.losses.id,\n TEAM_SEASON_BASE_COLUMN.pointsFor.id,\n TEAM_SEASON_BASE_COLUMN.pointsAgainst.id,\n TEAM_SEASON_BASE_COLUMN.pointDifferential.id,\n REBOUNDS.id,\n ASSISTS.id,\n ],\n },\n },\n emptyStats: (game) => ({\n teamStats: Object.fromEntries(game.teamIds.map((teamId) => [teamId, emptyBasketballTeamLine()])),\n playerStats: {},\n }),\n bulkEntry: BASKETBALL_BULK_ENTRY,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-entry.js","sourceRoot":"","sources":["../../src/sports/bulk-entry.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,sDAAsD;AAEtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA8DtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,4BAA4B,GACvC,6FAA6F,CAAC;AAEhG,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAG,qCAAqC,CAAC;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CACzC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAC/C,wBAAwB,CACzB,CAAC","sourcesContent":["// A sport's bulk entry: the form the shared bulk-entry dialog renders for the sport's kind, and the\
|
|
1
|
+
{"version":3,"file":"bulk-entry.js","sourceRoot":"","sources":["../../src/sports/bulk-entry.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,sDAAsD;AAEtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA8DtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,4BAA4B,GACvC,6FAA6F,CAAC;AAEhG,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAG,qCAAqC,CAAC;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CACzC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAC/C,wBAAwB,CACzB,CAAC","sourcesContent":["// A sport's bulk entry: the form the shared bulk-entry dialog renders for the sport's kind, and the\n// events a submission becomes. The form's rules are built from the sport's constants, and every one\n// of them is also an event rule the adapter's `validate` enforces, because the callable sees events,\n// never form values, and captains use it (SPORT-006).\n\nimport { z } from 'zod';\nimport { countSchema } from '../doc-schemas/atoms.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport type { BulkEntryKind } from '../types/stats.js';\nimport type { SportStatEventBody, StatVoidBody } from './scoring.js';\nimport type { GameTeamIds } from './sport-adapter.js';\n\n/** A recorded round's live event — one no void voids — so an edit of that round can void it. */\nexport interface LiveRoundEvent {\n readonly eventId: string;\n readonly roundNumber: number;\n}\n\n/** What the form edits: the game, the scorer's current state, and the game's roster. */\nexport interface BulkEntryContext {\n readonly game: GameTeamIds;\n /** The scorer's optimistic rollup, the queue's pending events applied. */\n readonly state: GameStats;\n /** Every roster entry of the game, grouped by team in the order the form lists them. */\n readonly rosterPlayerIds: readonly string[];\n readonly absentRosterPlayerIds: readonly string[];\n /** For a sport played in rounds, the game's unvoided round events; empty for any other sport. */\n readonly liveRoundEvents: readonly LiveRoundEvent[];\n}\n\n/**\n * One event of a submission, in batch order: a sport event's type and payload, or the target of a\n * void. At submit — the tap — the dialog mints each sport event's id and stamps its `recordedAt`, and\n * the queue sends the drafts as one unit it never splits across calls.\n */\nexport type StatEventDraft = SportStatEventBody | StatVoidBody;\n\nexport interface BulkEntryForm<K extends BulkEntryKind, Values> {\n readonly kind: K;\n /** The form's resolver: its rules for this game and state. */\n readonly formSchema: (context: BulkEntryContext) => z.ZodType<Values>;\n readonly formDefaults: (context: BulkEntryContext) => Values;\n /** The drafts a submission becomes; empty when nothing changed. */\n readonly toEvents: (values: Values, context: BulkEntryContext) => readonly StatEventDraft[];\n}\n\n/** Each roster entry's player counters, by roster entry id. */\nexport interface PlayerLinesValues {\n readonly lines: Readonly<Record<string, Readonly<Record<string, number>>>>;\n}\n\n/** Each team's final score, by team id. */\nexport interface TeamScoresValues {\n readonly scores: Readonly<Record<string, number>>;\n}\n\n/** Each round, first to last: every team's points by team id, `null` where the round is not recorded. */\nexport interface RoundsValues {\n readonly rounds: readonly Readonly<Record<string, number | null>>[];\n}\n\nexport type PlayerLinesBulkEntry = BulkEntryForm<'playerLines', PlayerLinesValues>;\nexport type TeamScoresBulkEntry = BulkEntryForm<'teamScores', TeamScoresValues>;\nexport type RoundsBulkEntry = BulkEntryForm<'rounds', RoundsValues>;\n\n/** The dialog picks its form body by `kind`, from an exhaustive map over the kind union. */\nexport type BulkEntry = PlayerLinesBulkEntry | TeamScoresBulkEntry | RoundsBulkEntry;\n\n/** The form's refusal when a submission would pass the per-write event cap. */\nexport const BULK_ENTRY_TOO_LARGE_MESSAGE =\n 'This entry changes more stats than one save can hold. Save part of it, then enter the rest.';\n\n/** The form's refusal of a value that is not a whole number of zero or more. */\nexport const BULK_ENTRY_COUNT_MESSAGE = 'Enter a whole number, zero or more.';\n\n/**\n * Every bulk-entry cell that holds a count: the count atom's check, answered with the one message the\n * form shows for any failure of it — which the atom's own issues cannot carry.\n */\nexport const bulkEntryCountField = z.custom<number>(\n (value) => countSchema.safeParse(value).success,\n BULK_ENTRY_COUNT_MESSAGE,\n);\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contribution.js","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"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","sourcesContent":["// The aggregate half of the sport adapter contract: who a completed game credits a game played, and\r\n// what the game adds to every derived destination (SPORT-201, SPORT-204, SPORT-207). Each sport\r\n// declares its mapping once, as a `ContributionSpec`; the two members built from it — `participation`\r\n// and `contribution` — are the only place a game's effect on standings, per-membership rows, lifetime\r\n// lines, and the hall of fame is decided. Finalize, reopen, a ghost link, every rebuild, validation,\r\n// and the week view all derive through them, so no two paths can disagree about what a game adds.\r\n//\r\n// Everything here is pure: no Firestore operation, no clock, no id minted (ARCH-202). The aggregate\r\n// writer in Functions applies a contribution; the browser sums contributions for the week view.\r\n//\r\n// A stored derived line that sums to nothing — every counter and games played zero — is the same as\r\n// no line at all, as an all-zero player line is in a game's rollup: a contribution leaves one out,\r\n// and applying a contribution removes a line it returns to zero. That is what makes finalize followed\r\n// by reopen leave every destination exactly as it was.\r\n\r\nimport type { GameStats } from '../doc-schemas/sport-game.js';\r\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\r\nimport { isGhostUserId } from '../ids/minted-ids.js';\r\nimport type { LeagueType } from '../types/sports.js';\r\nimport type { StatLineKind } from '../types/stats.js';\r\nimport { isAllZeroLine } from './scoring.js';\r\nimport type { StatLineValues } from './scoring.js';\r\n\r\n/**\r\n * A completed game as its finalized record holds it: the record's events replayed to lines, and the\r\n * first page's sport, teams, and participation snapshot. Finalize builds it from the game it is\r\n * finalizing; every later path builds it from the record, never from the game document (SPORT-204).\r\n */\r\nexport interface FinalizedGame {\r\n readonly leagueType: LeagueType;\r\n readonly teamIds: readonly string[];\r\n readonly stats: GameStats;\r\n readonly rosterPlayerIds: readonly string[];\r\n readonly absentRosterPlayerIds: readonly string[];\r\n readonly nonScoringRosterPlayerIds: readonly string[];\r\n}\r\n\r\n/**\r\n * What one completed game adds to each destination, before the caller's sign. Every line carries its\r\n * derived fields through the sport's `deriveLine`.\r\n */\r\nexport interface GameContribution {\r\n /** Per team id: the team's standings line (`teamSeason`). A team with no opponent has none. */\r\n readonly teamLines: Readonly<Record<string, StatLineValues>>;\r\n /** Per roster-entry id: that membership's row (`membershipRow`), credited to the team in its key. */\r\n readonly membershipRows: Readonly<Record<string, StatLineValues>>;\r\n /**\r\n * Per person who is not a ghost: the sum of that person's rows, games played included — what the\r\n * game adds to `lifetimeStats.{sportKey}` and, where the sport keeps one, the hall of fame\r\n * (`lifetime`). A person on both teams of the game counts both sides (SPORT-208 keeps ghosts out).\r\n */\r\n readonly personLines: Readonly<Record<string, StatLineValues>>;\r\n}\r\n\r\nexport const EMPTY_GAME_CONTRIBUTION: GameContribution = { teamLines: {}, membershipRows: {}, personLines: {} };\r\n\r\n/** One sport's aggregate mapping, declared once by its adapter (SPORT-207). */\r\nexport interface ContributionSpec {\r\n /** The counters of a player line that a membership row, a lifetime line, and the hall of fame sum. */\r\n readonly playerStatKeys: readonly string[];\r\n /** The player counters summed onto the team's standings line from the lines keyed to that team. */\r\n readonly teamSeasonPlayerSums: readonly string[];\r\n /**\r\n * Whether a present entry whose line holds nothing is credited no game played. Basketball's rule:\r\n * zero points, rebounds, and assists — zero points alone is not exclusion. Without it only absence\r\n * withholds a game played.\r\n */\r\n readonly zeroLineIsNonScoring: boolean;\r\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\r\n}\r\n\r\n/** The value of a counter on a stored line, absent read as zero. */\r\nfunction counter(line: StatLineValues | undefined, field: string): number {\r\n const value = line?.[field];\r\n return typeof value === 'number' ? value : 0;\r\n}\r\n\r\n/** Whether every number a line holds is zero: a derived line that sums to nothing. */\r\nexport function isZeroStatLine(line: StatLineValues): boolean {\r\n return Object.values(line).every((value) => typeof value !== 'number' || value === 0);\r\n}\r\n\r\n/**\r\n * The games-played decision for every roster entry: the entries credited no game played. An absent\r\n * entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is\r\n * not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided\r\n * again, by every later path (SPORT-204). Returned in roster order.\r\n */\r\nexport function decideParticipation(\r\n spec: Pick<ContributionSpec, 'playerStatKeys' | 'zeroLineIsNonScoring'>,\r\n stats: GameStats,\r\n rosterPlayerIds: readonly string[],\r\n absentRosterPlayerIds: readonly string[],\r\n): string[] {\r\n if (!spec.zeroLineIsNonScoring) return [];\r\n const absent = new Set(absentRosterPlayerIds);\r\n const playerStats = stats.playerStats as Readonly<Record<string, StatLineValues>>;\r\n return rosterPlayerIds.filter((entryId) => {\r\n if (absent.has(entryId)) return true;\r\n const line = playerStats[entryId];\r\n return line === undefined || isAllZeroLine(line, spec.playerStatKeys);\r\n });\r\n}\r\n\r\n/** Whether the stored decision credits the entry a game played. */\r\nexport function isCreditedGamePlayed(game: Pick<FinalizedGame, 'absentRosterPlayerIds' | 'nonScoringRosterPlayerIds'>, entryId: string): boolean {\r\n return !game.absentRosterPlayerIds.includes(entryId) && !game.nonScoringRosterPlayerIds.includes(entryId);\r\n}\r\n\r\n/** `left` with every number of `right` added `sign` times; a field only one holds is kept. */\r\nexport function addStatLines(left: StatLineValues, right: StatLineValues, sign: 1 | -1 = 1): Record<string, number> {\r\n const sum: Record<string, number> = {};\r\n for (const field of new Set([...Object.keys(left), ...Object.keys(right)])) {\r\n const [a, b] = [left[field], right[field]];\r\n if ((a !== undefined && typeof a !== 'number') || (b !== undefined && typeof b !== 'number')) continue;\r\n sum[field] = (a ?? 0) + sign * (b ?? 0);\r\n }\r\n return sum;\r\n}\r\n\r\n/**\r\n * The contribution of one completed game under a sport's mapping. Every line derives from the\r\n * replayed lines and the stored decision only, never from a stored total (SPORT-202, SPORT-204):\r\n *\r\n * - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,\r\n * its points for and against, and the spec's player counters summed from the lines keyed to it;\r\n * - each roster entry of such a team — and any line keyed to one, rostered or not, so no recorded\r\n * stat is lost — its row: games played per the stored decision, and the sport's counters, credited\r\n * to the team in its key;\r\n * - each person who is not a ghost: the sum of their rows.\r\n *\r\n * A row or person line that sums to nothing is left out.\r\n */\r\nexport function buildGameContribution(spec: ContributionSpec, game: FinalizedGame): GameContribution {\r\n const teamStats = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\r\n const playerStats = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\r\n const opponentOf = (teamId: string): string | undefined => game.teamIds.find((other) => other !== teamId);\r\n const counted = new Set(game.teamIds.filter((teamId) => opponentOf(teamId) !== undefined));\r\n\r\n const teamLines: Record<string, StatLineValues> = {};\r\n for (const teamId of counted) {\r\n const scored = counter(teamStats[teamId], 'score');\r\n const allowed = counter(teamStats[opponentOf(teamId) as string], 'score');\r\n const line: Record<string, number> = {\r\n gamesPlayed: 1,\r\n wins: scored > allowed ? 1 : 0,\r\n losses: scored < allowed ? 1 : 0,\r\n ties: scored === allowed ? 1 : 0,\r\n pointsFor: scored,\r\n pointsAgainst: allowed,\r\n };\r\n for (const field of spec.teamSeasonPlayerSums) line[field] = 0;\r\n for (const [entryId, playerLine] of Object.entries(playerStats)) {\r\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\r\n for (const field of spec.teamSeasonPlayerSums) line[field] = (line[field] ?? 0) + counter(playerLine, field);\r\n }\r\n teamLines[teamId] = spec.deriveLine('teamSeason', line);\r\n }\r\n\r\n const membershipRows: Record<string, StatLineValues> = {};\r\n const personTotals = new Map<string, Record<string, number>>();\r\n const entryIds = [...new Set([...game.rosterPlayerIds, ...Object.keys(playerStats)])].sort();\r\n for (const entryId of entryIds) {\r\n const entry = parseGameRosterPlayerId(entryId);\r\n if (entry === null || !counted.has(entry.teamId)) continue;\r\n const row: Record<string, number> = { gamesPlayed: isCreditedGamePlayed(game, entryId) ? 1 : 0 };\r\n for (const field of spec.playerStatKeys) row[field] = counter(playerStats[entryId], field);\r\n if (isZeroStatLine(row)) continue;\r\n membershipRows[entryId] = spec.deriveLine('membershipRow', row);\r\n if (isGhostUserId(entry.userId)) continue;\r\n personTotals.set(entry.userId, addStatLines(personTotals.get(entry.userId) ?? {}, row));\r\n }\r\n\r\n const personLines: Record<string, StatLineValues> = {};\r\n for (const [userId, line] of personTotals) personLines[userId] = spec.deriveLine('lifetime', line);\r\n return { teamLines, membershipRows, personLines };\r\n}\r\n\r\n/** A contribution and the sign it is applied with: +1 to add a game, −1 to take it back. */\r\nexport interface SignedContribution {\r\n readonly contribution: GameContribution;\r\n readonly sign: 1 | -1;\r\n}\r\n\r\n/**\r\n * Several signed contributions folded into one, every line through the sport's `deriveLine` and a\r\n * line that sums to nothing left out — the week view's rows, a rebuild's totals, and a ghost link's\r\n * reversal of the old entries beside the merged one, all through the same arithmetic.\r\n */\r\nexport function sumGameContributions(\r\n deriveLine: ContributionSpec['deriveLine'],\r\n signed: readonly SignedContribution[],\r\n): GameContribution {\r\n const fold = (pick: (contribution: GameContribution) => Readonly<Record<string, StatLineValues>>, kind: StatLineKind) => {\r\n const totals = new Map<string, Record<string, number>>();\r\n for (const { contribution, sign } of signed) {\r\n for (const [key, line] of Object.entries(pick(contribution))) {\r\n totals.set(key, addStatLines(totals.get(key) ?? {}, line, sign));\r\n }\r\n }\r\n const lines: Record<string, StatLineValues> = {};\r\n for (const [key, line] of totals) {\r\n const derived = deriveLine(kind, line);\r\n if (!isZeroStatLine(derived)) lines[key] = derived;\r\n }\r\n return lines;\r\n };\r\n return {\r\n teamLines: fold((contribution) => contribution.teamLines, 'teamSeason'),\r\n membershipRows: fold((contribution) => contribution.membershipRows, 'membershipRow'),\r\n personLines: fold((contribution) => contribution.personLines, 'lifetime'),\r\n };\r\n}\r\n\r\n/**\r\n * A stored line moved by a delta: the line after, `null` when it sums to nothing, or the field that\r\n * would go below zero.\r\n */\r\nexport type StatLineChange =\r\n | { readonly ok: true; readonly after: StatLineValues | null }\r\n | { readonly ok: false; readonly field: string };\r\n\r\n/**\r\n * `before` (absent read as no line) moved by `delta` times `sign`, through the sport's `deriveLine`\r\n * for `kind`. A counter that would go below zero is refused, never clamped: it means the stored line\r\n * no longer holds what the games put there.\r\n */\r\nexport function applyStatLineDelta(\r\n deriveLine: ContributionSpec['deriveLine'],\r\n kind: StatLineKind,\r\n before: StatLineValues | undefined,\r\n delta: StatLineValues,\r\n sign: 1 | -1,\r\n): StatLineChange {\r\n // A derived field is recomputed from the moved counters, never moved itself, so a stale stored FP\r\n // can neither go below zero nor survive.\r\n const after = deriveLine(kind, addStatLines(before ?? {}, delta, sign));\r\n const below = Object.entries(after).find(([, value]) => typeof value === 'number' && value < 0);\r\n if (below) return { ok: false, field: below[0] };\r\n return { ok: true, after: isZeroStatLine(after) ? null : after };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"contribution.js","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"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","sourcesContent":["// The aggregate half of the sport adapter contract: who a completed game credits a game played, and\n// what the game adds to every derived destination (SPORT-201, SPORT-204, SPORT-207). Each sport\n// declares its mapping once, as a `ContributionSpec`; the two members built from it — `participation`\n// and `contribution` — are the only place a game's effect on standings, per-membership rows, lifetime\n// lines, and the hall of fame is decided. Finalize, reopen, a ghost link, every rebuild, validation,\n// and the week view all derive through them, so no two paths can disagree about what a game adds.\n//\n// Everything here is pure: no Firestore operation, no clock, no id minted (ARCH-202). The aggregate\n// writer in Functions applies a contribution; the browser sums contributions for the week view.\n//\n// A stored derived line that sums to nothing — every counter and games played zero — is the same as\n// no line at all, as an all-zero player line is in a game's rollup: a contribution leaves one out,\n// and applying a contribution removes a line it returns to zero. That is what makes finalize followed\n// by reopen leave every destination exactly as it was.\n\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { isGhostUserId } from '../ids/minted-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport { isAllZeroLine } from './scoring.js';\nimport type { StatLineValues } from './scoring.js';\n\n/**\n * A completed game as its finalized record holds it: the record's events replayed to lines, and the\n * first page's sport, teams, and participation snapshot. Finalize builds it from the game it is\n * finalizing; every later path builds it from the record, never from the game document (SPORT-204).\n */\nexport interface FinalizedGame {\n readonly leagueType: LeagueType;\n readonly teamIds: readonly string[];\n readonly stats: GameStats;\n readonly rosterPlayerIds: readonly string[];\n readonly absentRosterPlayerIds: readonly string[];\n readonly nonScoringRosterPlayerIds: readonly string[];\n}\n\n/**\n * What one completed game adds to each destination, before the caller's sign. Every line carries its\n * derived fields through the sport's `deriveLine`.\n */\nexport interface GameContribution {\n /** Per team id: the team's standings line (`teamSeason`). A team with no opponent has none. */\n readonly teamLines: Readonly<Record<string, StatLineValues>>;\n /** Per roster-entry id: that membership's row (`membershipRow`), credited to the team in its key. */\n readonly membershipRows: Readonly<Record<string, StatLineValues>>;\n /**\n * Per person who is not a ghost: the sum of that person's rows, games played included — what the\n * game adds to `lifetimeStats.{sportKey}` and, where the sport keeps one, the hall of fame\n * (`lifetime`). A person on both teams of the game counts both sides (SPORT-208 keeps ghosts out).\n */\n readonly personLines: Readonly<Record<string, StatLineValues>>;\n}\n\nexport const EMPTY_GAME_CONTRIBUTION: GameContribution = { teamLines: {}, membershipRows: {}, personLines: {} };\n\n/** One sport's aggregate mapping, declared once by its adapter (SPORT-207). */\nexport interface ContributionSpec {\n /** The counters of a player line that a membership row, a lifetime line, and the hall of fame sum. */\n readonly playerStatKeys: readonly string[];\n /** The player counters summed onto the team's standings line from the lines keyed to that team. */\n readonly teamSeasonPlayerSums: readonly string[];\n /**\n * Whether a present entry whose line holds nothing is credited no game played. Basketball's rule:\n * zero points, rebounds, and assists — zero points alone is not exclusion. Without it only absence\n * withholds a game played.\n */\n readonly zeroLineIsNonScoring: boolean;\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\n}\n\n/** The value of a counter on a stored line, absent read as zero. */\nfunction counter(line: StatLineValues | undefined, field: string): number {\n const value = line?.[field];\n return typeof value === 'number' ? value : 0;\n}\n\n/** Whether every number a line holds is zero: a derived line that sums to nothing. */\nexport function isZeroStatLine(line: StatLineValues): boolean {\n return Object.values(line).every((value) => typeof value !== 'number' || value === 0);\n}\n\n/**\n * The games-played decision for every roster entry: the entries credited no game played. An absent\n * entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is\n * not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided\n * again, by every later path (SPORT-204). Returned in roster order.\n */\nexport function decideParticipation(\n spec: Pick<ContributionSpec, 'playerStatKeys' | 'zeroLineIsNonScoring'>,\n stats: GameStats,\n rosterPlayerIds: readonly string[],\n absentRosterPlayerIds: readonly string[],\n): string[] {\n if (!spec.zeroLineIsNonScoring) return [];\n const absent = new Set(absentRosterPlayerIds);\n const playerStats = stats.playerStats as Readonly<Record<string, StatLineValues>>;\n return rosterPlayerIds.filter((entryId) => {\n if (absent.has(entryId)) return true;\n const line = playerStats[entryId];\n return line === undefined || isAllZeroLine(line, spec.playerStatKeys);\n });\n}\n\n/** Whether the stored decision credits the entry a game played. */\nexport function isCreditedGamePlayed(game: Pick<FinalizedGame, 'absentRosterPlayerIds' | 'nonScoringRosterPlayerIds'>, entryId: string): boolean {\n return !game.absentRosterPlayerIds.includes(entryId) && !game.nonScoringRosterPlayerIds.includes(entryId);\n}\n\n/** `left` with every number of `right` added `sign` times; a field only one holds is kept. */\nexport function addStatLines(left: StatLineValues, right: StatLineValues, sign: 1 | -1 = 1): Record<string, number> {\n const sum: Record<string, number> = {};\n for (const field of new Set([...Object.keys(left), ...Object.keys(right)])) {\n const [a, b] = [left[field], right[field]];\n if ((a !== undefined && typeof a !== 'number') || (b !== undefined && typeof b !== 'number')) continue;\n sum[field] = (a ?? 0) + sign * (b ?? 0);\n }\n return sum;\n}\n\n/**\n * The contribution of one completed game under a sport's mapping. Every line derives from the\n * replayed lines and the stored decision only, never from a stored total (SPORT-202, SPORT-204):\n *\n * - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,\n * its points for and against, and the spec's player counters summed from the lines keyed to it;\n * - each roster entry of such a team — and any line keyed to one, rostered or not, so no recorded\n * stat is lost — its row: games played per the stored decision, and the sport's counters, credited\n * to the team in its key;\n * - each person who is not a ghost: the sum of their rows.\n *\n * A row or person line that sums to nothing is left out.\n */\nexport function buildGameContribution(spec: ContributionSpec, game: FinalizedGame): GameContribution {\n const teamStats = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\n const playerStats = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\n const opponentOf = (teamId: string): string | undefined => game.teamIds.find((other) => other !== teamId);\n const counted = new Set(game.teamIds.filter((teamId) => opponentOf(teamId) !== undefined));\n\n const teamLines: Record<string, StatLineValues> = {};\n for (const teamId of counted) {\n const scored = counter(teamStats[teamId], 'score');\n const allowed = counter(teamStats[opponentOf(teamId) as string], 'score');\n const line: Record<string, number> = {\n gamesPlayed: 1,\n wins: scored > allowed ? 1 : 0,\n losses: scored < allowed ? 1 : 0,\n ties: scored === allowed ? 1 : 0,\n pointsFor: scored,\n pointsAgainst: allowed,\n };\n for (const field of spec.teamSeasonPlayerSums) line[field] = 0;\n for (const [entryId, playerLine] of Object.entries(playerStats)) {\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\n for (const field of spec.teamSeasonPlayerSums) line[field] = (line[field] ?? 0) + counter(playerLine, field);\n }\n teamLines[teamId] = spec.deriveLine('teamSeason', line);\n }\n\n const membershipRows: Record<string, StatLineValues> = {};\n const personTotals = new Map<string, Record<string, number>>();\n const entryIds = [...new Set([...game.rosterPlayerIds, ...Object.keys(playerStats)])].sort();\n for (const entryId of entryIds) {\n const entry = parseGameRosterPlayerId(entryId);\n if (entry === null || !counted.has(entry.teamId)) continue;\n const row: Record<string, number> = { gamesPlayed: isCreditedGamePlayed(game, entryId) ? 1 : 0 };\n for (const field of spec.playerStatKeys) row[field] = counter(playerStats[entryId], field);\n if (isZeroStatLine(row)) continue;\n membershipRows[entryId] = spec.deriveLine('membershipRow', row);\n if (isGhostUserId(entry.userId)) continue;\n personTotals.set(entry.userId, addStatLines(personTotals.get(entry.userId) ?? {}, row));\n }\n\n const personLines: Record<string, StatLineValues> = {};\n for (const [userId, line] of personTotals) personLines[userId] = spec.deriveLine('lifetime', line);\n return { teamLines, membershipRows, personLines };\n}\n\n/** A contribution and the sign it is applied with: +1 to add a game, −1 to take it back. */\nexport interface SignedContribution {\n readonly contribution: GameContribution;\n readonly sign: 1 | -1;\n}\n\n/**\n * Several signed contributions folded into one, every line through the sport's `deriveLine` and a\n * line that sums to nothing left out — the week view's rows, a rebuild's totals, and a ghost link's\n * reversal of the old entries beside the merged one, all through the same arithmetic.\n */\nexport function sumGameContributions(\n deriveLine: ContributionSpec['deriveLine'],\n signed: readonly SignedContribution[],\n): GameContribution {\n const fold = (pick: (contribution: GameContribution) => Readonly<Record<string, StatLineValues>>, kind: StatLineKind) => {\n const totals = new Map<string, Record<string, number>>();\n for (const { contribution, sign } of signed) {\n for (const [key, line] of Object.entries(pick(contribution))) {\n totals.set(key, addStatLines(totals.get(key) ?? {}, line, sign));\n }\n }\n const lines: Record<string, StatLineValues> = {};\n for (const [key, line] of totals) {\n const derived = deriveLine(kind, line);\n if (!isZeroStatLine(derived)) lines[key] = derived;\n }\n return lines;\n };\n return {\n teamLines: fold((contribution) => contribution.teamLines, 'teamSeason'),\n membershipRows: fold((contribution) => contribution.membershipRows, 'membershipRow'),\n personLines: fold((contribution) => contribution.personLines, 'lifetime'),\n };\n}\n\n/**\n * A stored line moved by a delta: the line after, `null` when it sums to nothing, or the field that\n * would go below zero.\n */\nexport type StatLineChange =\n | { readonly ok: true; readonly after: StatLineValues | null }\n | { readonly ok: false; readonly field: string };\n\n/**\n * `before` (absent read as no line) moved by `delta` times `sign`, through the sport's `deriveLine`\n * for `kind`. A counter that would go below zero is refused, never clamped: it means the stored line\n * no longer holds what the games put there.\n */\nexport function applyStatLineDelta(\n deriveLine: ContributionSpec['deriveLine'],\n kind: StatLineKind,\n before: StatLineValues | undefined,\n delta: StatLineValues,\n sign: 1 | -1,\n): StatLineChange {\n // A derived field is recomputed from the moved counters, never moved itself, so a stale stored FP\n // can neither go below zero nor survive.\n const after = deriveLine(kind, addStatLines(before ?? {}, delta, sign));\n const below = Object.entries(after).find(([, value]) => typeof value === 'number' && value < 0);\n if (below) return { ok: false, field: below[0] };\n return { ok: true, after: isZeroStatLine(after) ? null : after };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curling-scoring.js","sourceRoot":"","sources":["../../src/sports/curling-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The curling adapter's scoring, display, and bulk-entry members. Curling is team-scored by round: a\r\n// round event records one round — one team's points, or a blank round for every team — and nothing\r\n// names a player. A round is recorded once, in order: `validate` accepts a round event only for the\r\n// first unrecorded round, and re-scoring a round is a void of its event plus a new one, so no curling\r\n// event is ever superseded and undoing one always returns its round to unrecorded (SPORT-103).\r\n\r\nimport { z } from 'zod';\r\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS, MAX_STAT_EVENTS_PER_WRITE } from '../constants/sports.js';\r\nimport type { CurlingTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\r\nimport type { CurlingGameStats, GameStats } from '../doc-schemas/sport-game.js';\r\nimport {\r\n CURLING_ROUND_BLANK_EVENT_TYPE,\r\n CURLING_ROUND_SCORE_EVENT_TYPE,\r\n CURLING_STAT_EVENT_TYPE_KEYS,\r\n} from '../types/sports.js';\r\nimport type { CurlingStatEventType, CurlingTeamStatKey } from '../types/sports.js';\r\nimport { STAT_VOID_EVENT_TYPE } from '../types/stats.js';\r\nimport {\r\n BULK_ENTRY_TOO_LARGE_MESSAGE,\r\n bulkEntryCountField,\r\n type BulkEntryContext,\r\n type RoundsBulkEntry,\r\n type RoundsValues,\r\n type StatEventDraft,\r\n} from './bulk-entry.js';\r\nimport { buildGameContribution, decideParticipation, type ContributionSpec } from './contribution.js';\r\nimport {\r\n FINALIZATION_READY,\r\n STAT_EVENT_ACCEPTED,\r\n isSportStatEventIn,\r\n isStatCorrection,\r\n refuseStatEvent,\r\n type CorrectableFields,\r\n type FinalizationReadiness,\r\n type GameTeamLine,\r\n type SportScoring,\r\n type SportStatEventBody,\r\n type StatEventBody,\r\n} from './scoring.js';\r\nimport {\r\n describeStoreEvent,\r\n type RoundsDisplay,\r\n type RoundsScoringPanel,\r\n type Scoreboard,\r\n type StatEventLabel,\r\n type StatEventNameResolver,\r\n type StatFieldLabels,\r\n} from './scoring-display.js';\r\n\r\ntype CurlingEvent = Extract<SportStatEventBody, { type: CurlingStatEventType }>;\r\n\r\nfunction isCurlingEvent(event: StatEventBody | undefined): event is CurlingEvent {\r\n return isSportStatEventIn(CURLING_STAT_EVENT_TYPE_KEYS, event);\r\n}\r\n\r\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\r\n// curling game's (SPORT-002).\r\nfunction ownStats(state: GameStats): CurlingGameStats {\r\n return state as CurlingGameStats;\r\n}\r\n\r\nfunction ownTeamLine(line: GameTeamLine): CurlingTeamStats {\r\n return line as CurlingTeamStats;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Lines — a team's `score` is always the sum of its recorded rounds\r\n// ---------------------------------------------------------------------------------------------\r\n\r\ntype RoundSlots = CurlingTeamStats['roundScores'];\r\n\r\nfunction sumOfRounds(roundScores: RoundSlots): number {\r\n return roundScores.reduce<number>((total, slot) => total + (slot ?? 0), 0);\r\n}\r\n\r\n/** Every round unrecorded — `null`, where a blank round is `0`. */\r\nexport function emptyCurlingTeamLine(): CurlingTeamStats {\r\n const roundScores: RoundSlots = Array.from({ length: CURLING_ROUND_COUNT }, () => null);\r\n return { score: sumOfRounds(roundScores), roundScores };\r\n}\r\n\r\nfunction withRound(line: CurlingTeamStats, roundNumber: number, slot: number | null): CurlingTeamStats {\r\n const roundScores = [...line.roundScores];\r\n roundScores[roundNumber - 1] = slot;\r\n return { score: sumOfRounds(roundScores), roundScores };\r\n}\r\n\r\n/**\r\n * Record or clear one round on every team line: a scored round is the scoring team's points and 0\r\n * for the others, a blank round 0 for all, and a cleared round `null` for all.\r\n */\r\nfunction setRound(stateIn: GameStats, event: CurlingEvent, recorded: boolean): CurlingGameStats {\r\n const state = ownStats(stateIn);\r\n const teamStats = { ...state.teamStats };\r\n const scoringTeamId = event.type === CURLING_ROUND_SCORE_EVENT_TYPE ? event.teamId : undefined;\r\n if (scoringTeamId !== undefined && teamStats[scoringTeamId] === undefined) {\r\n teamStats[scoringTeamId] = emptyCurlingTeamLine();\r\n }\r\n for (const [teamId, line] of Object.entries(teamStats)) {\r\n const points = event.type === CURLING_ROUND_SCORE_EVENT_TYPE && teamId === scoringTeamId ? event.points : 0;\r\n teamStats[teamId] = withRound(line, event.roundNumber, recorded ? points : null);\r\n }\r\n return { teamStats, playerStats: state.playerStats };\r\n}\r\n\r\nfunction nextCurlingRound(stateIn: GameStats): number | null {\r\n const lines = Object.values(ownStats(stateIn).teamStats);\r\n for (let roundNumber = 1; roundNumber <= CURLING_ROUND_COUNT; roundNumber += 1) {\r\n if (lines.length === 0 || lines.some((line) => line.roundScores[roundNumber - 1] === null)) return roundNumber;\r\n }\r\n return null;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Scoring\r\n// ---------------------------------------------------------------------------------------------\r\n\r\n// No curling field is correctable: a curling result changes only by reopening the game and\r\n// re-scoring the round.\r\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: [], player: [] };\r\n\r\nfunction namesPlayer(event: StatEventBody): boolean {\r\n return 'playerUserId' in event && event.playerUserId !== undefined;\r\n}\r\n\r\nconst validate: SportScoring['validate'] = (state, event, roster) => {\r\n if (!isCurlingEvent(event)) return refuseStatEvent('eventTypeNotInSport');\r\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\r\n if (event.type === CURLING_ROUND_SCORE_EVENT_TYPE && !roster.teamIds.includes(event.teamId)) {\r\n return refuseStatEvent('teamNotInGame');\r\n }\r\n const next = nextCurlingRound(state);\r\n if (next === null) return refuseStatEvent('roundsComplete');\r\n return event.roundNumber === next ? STAT_EVENT_ACCEPTED : refuseStatEvent('roundNotNext');\r\n};\r\n\r\nfunction finalizationReadiness(state: GameStats): FinalizationReadiness {\r\n return nextCurlingRound(state) === null ? FINALIZATION_READY : { ready: false, reason: 'roundsIncomplete' };\r\n}\r\n\r\n// What a completed curling game adds: the standings line from the two scores, and a game played\r\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\r\n// absence withholds a game played and no entry is ever non-scoring.\r\nconst CURLING_CONTRIBUTION: ContributionSpec = {\r\n playerStatKeys: [],\r\n teamSeasonPlayerSums: [],\r\n zeroLineIsNonScoring: false,\r\n deriveLine: (_kind, line) => line,\r\n};\r\n\r\nexport const CURLING_SCORING: SportScoring = {\r\n validate,\r\n apply: (state, event) => {\r\n if (isStatCorrection(event)) throw new Error('No curling field is correctable.');\r\n if (!isCurlingEvent(event)) throw new Error(`The curling adapter does not apply ${event.type}.`);\r\n return setRound(state, event, true);\r\n },\r\n undo: (state, target) => {\r\n if (!isCurlingEvent(target)) throw new Error(`The curling adapter does not undo ${target.type}.`);\r\n return setRound(state, target, false);\r\n },\r\n // A curling line has no derived field.\r\n deriveLine: CURLING_CONTRIBUTION.deriveLine,\r\n correctableFields: CORRECTABLE_FIELDS,\r\n nextRound: nextCurlingRound,\r\n finalizationReadiness,\r\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\r\n decideParticipation(CURLING_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\r\n contribution: (game) => buildGameContribution(CURLING_CONTRIBUTION, game),\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Display\r\n// ---------------------------------------------------------------------------------------------\r\n\r\nexport const CURLING_ROUNDS: RoundsDisplay = { count: CURLING_ROUND_COUNT, label: 'Round' };\r\n\r\nexport const CURLING_EVENT_LABELS = {\r\n [CURLING_ROUND_SCORE_EVENT_TYPE]: { button: 'Score', play: 'Round score' },\r\n [CURLING_ROUND_BLANK_EVENT_TYPE]: { button: '+0 (Blank)', play: 'Blank end' },\r\n} as const satisfies Record<CurlingStatEventType, StatEventLabel>;\r\n\r\nexport const CURLING_FIELD_LABELS = {\r\n team: { score: 'Score' },\r\n player: {},\r\n} as const satisfies StatFieldLabels<CurlingTeamStatKey, never>;\r\n\r\nexport function describeCurlingEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\r\n const storeEvent = describeStoreEvent(event, resolve, CURLING_FIELD_LABELS);\r\n if (storeEvent !== null) return storeEvent;\r\n if (!isCurlingEvent(event)) return event.type;\r\n const round = `${CURLING_ROUNDS.label} ${event.roundNumber}`;\r\n if (event.type === CURLING_ROUND_BLANK_EVENT_TYPE) return `${round}: ${CURLING_EVENT_LABELS[event.type].play.toLowerCase()}`;\r\n return `${round}: ${resolve.team(event.teamId)} scored ${event.points}`;\r\n}\r\n\r\nexport const CURLING_SCOREBOARD: Scoreboard = {\r\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\r\n jumpBall: false,\r\n roundSlots: (teamLine) => ownTeamLine(teamLine).roundScores,\r\n};\r\n\r\nexport const CURLING_SCORING_PANEL: RoundsScoringPanel = {\r\n kind: 'rounds',\r\n scoreEventType: CURLING_ROUND_SCORE_EVENT_TYPE,\r\n blankEventType: CURLING_ROUND_BLANK_EVENT_TYPE,\r\n maxPoints: MAX_CURLING_ROUND_POINTS,\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Bulk entry — one row per round, a cell per team\r\n// ---------------------------------------------------------------------------------------------\r\n\r\nexport const CURLING_ROUND_POINTS_MESSAGE = `A round is worth 0 to ${MAX_CURLING_ROUND_POINTS} points.`;\r\nexport const CURLING_ONE_SCORING_TEAM_MESSAGE =\r\n 'A round is scored by one team (the other 0 or empty), blank (0 for every team), or left empty.';\r\nexport const CURLING_NO_SKIPPED_ROUND_MESSAGE = 'Rounds are recorded in order: fill in every earlier round first.';\r\n\r\ntype RoundOutcome =\r\n | { readonly kind: 'unrecorded' }\r\n | { readonly kind: 'blank' }\r\n | { readonly kind: 'scored'; readonly teamId: string; readonly points: number }\r\n | { readonly kind: 'invalid' };\r\n\r\nfunction roundOutcome(row: Readonly<Record<string, number | null | undefined>>, teamIds: readonly string[]): RoundOutcome {\r\n const cells = teamIds.map((teamId) => ({ teamId, points: row[teamId] ?? null }));\r\n if (cells.every((cell) => cell.points === null)) return { kind: 'unrecorded' };\r\n if (cells.every((cell) => cell.points === 0)) return { kind: 'blank' };\r\n const scorers = cells.filter((cell) => cell.points !== null && cell.points > 0);\r\n const [scorer] = scorers;\r\n if (scorers.length === 1 && scorer !== undefined && scorer.points !== null) {\r\n return { kind: 'scored', teamId: scorer.teamId, points: scorer.points };\r\n }\r\n return { kind: 'invalid' };\r\n}\r\n\r\nfunction sameOutcome(left: RoundOutcome, right: RoundOutcome): boolean {\r\n if (left.kind === 'scored' && right.kind === 'scored') return left.teamId === right.teamId && left.points === right.points;\r\n return left.kind === right.kind;\r\n}\r\n\r\nfunction storedRound(context: BulkEntryContext, roundNumber: number): Record<string, number | null> {\r\n const state = ownStats(context.state);\r\n return Object.fromEntries(\r\n context.game.teamIds.map((teamId) => [teamId, state.teamStats[teamId]?.roundScores[roundNumber - 1] ?? null]),\r\n );\r\n}\r\n\r\nfunction roundDrafts(values: RoundsValues, context: BulkEntryContext): StatEventDraft[] {\r\n const { teamIds } = context.game;\r\n const liveEventByRound = new Map(context.liveRoundEvents.map((event) => [event.roundNumber, event.eventId]));\r\n const voids: StatEventDraft[] = [];\r\n const recorded: StatEventDraft[] = [];\r\n values.rounds.forEach((row, index) => {\r\n const roundNumber = index + 1;\r\n const entered = roundOutcome(row, teamIds);\r\n if (sameOutcome(entered, roundOutcome(storedRound(context, roundNumber), teamIds))) return;\r\n const liveEventId = liveEventByRound.get(roundNumber);\r\n if (liveEventId !== undefined) voids.push({ type: STAT_VOID_EVENT_TYPE, targetEventId: liveEventId });\r\n if (entered.kind === 'scored') {\r\n recorded.push({ type: CURLING_ROUND_SCORE_EVENT_TYPE, teamId: entered.teamId, roundNumber, points: entered.points });\r\n } else if (entered.kind === 'blank') {\r\n recorded.push({ type: CURLING_ROUND_BLANK_EVENT_TYPE, roundNumber });\r\n }\r\n });\r\n // Every void first, then the new rounds in order: each changed round is unrecorded again before\r\n // any new event is validated, so each new event is the next unrecorded round when it arrives.\r\n return [...voids, ...recorded];\r\n}\r\n\r\nconst roundCell = bulkEntryCountField\r\n .pipe(z.number().max(MAX_CURLING_ROUND_POINTS, CURLING_ROUND_POINTS_MESSAGE))\r\n .nullable();\r\n\r\nexport const CURLING_BULK_ENTRY: RoundsBulkEntry = {\r\n kind: 'rounds',\r\n formDefaults: (context) => ({\r\n rounds: Array.from({ length: CURLING_ROUND_COUNT }, (_, index) => storedRound(context, index + 1)),\r\n }),\r\n formSchema: (context) => {\r\n const { teamIds } = context.game;\r\n const row = z.strictObject(Object.fromEntries(teamIds.map((teamId) => [teamId, roundCell])));\r\n return z\r\n .object({ rounds: z.array(row).length(CURLING_ROUND_COUNT) })\r\n .superRefine((values, issues) => {\r\n let unrecordedBefore = false;\r\n values.rounds.forEach((cells, index) => {\r\n const outcome = roundOutcome(cells, teamIds);\r\n if (outcome.kind === 'invalid') {\r\n issues.addIssue({ code: 'custom', path: ['rounds', index], message: CURLING_ONE_SCORING_TEAM_MESSAGE });\r\n } else if (outcome.kind === 'unrecorded') {\r\n unrecordedBefore = true;\r\n } else if (unrecordedBefore) {\r\n issues.addIssue({ code: 'custom', path: ['rounds', index], message: CURLING_NO_SKIPPED_ROUND_MESSAGE });\r\n }\r\n });\r\n if (roundDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\r\n issues.addIssue({ code: 'custom', path: ['rounds'], message: BULK_ENTRY_TOO_LARGE_MESSAGE });\r\n }\r\n });\r\n },\r\n toEvents: roundDrafts,\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"curling-scoring.js","sourceRoot":"","sources":["../../src/sports/curling-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The curling adapter's scoring, display, and bulk-entry members. Curling is team-scored by round: a\n// round event records one round — one team's points, or a blank round for every team — and nothing\n// names a player. A round is recorded once, in order: `validate` accepts a round event only for the\n// first unrecorded round, and re-scoring a round is a void of its event plus a new one, so no curling\n// event is ever superseded and undoing one always returns its round to unrecorded (SPORT-103).\n\nimport { z } from 'zod';\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS, MAX_STAT_EVENTS_PER_WRITE } from '../constants/sports.js';\nimport type { CurlingTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { CurlingGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_STAT_EVENT_TYPE_KEYS,\n} from '../types/sports.js';\nimport type { CurlingStatEventType, CurlingTeamStatKey } from '../types/sports.js';\nimport { STAT_VOID_EVENT_TYPE } from '../types/stats.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type RoundsBulkEntry,\n type RoundsValues,\n type StatEventDraft,\n} from './bulk-entry.js';\nimport { buildGameContribution, decideParticipation, type ContributionSpec } from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type FinalizationReadiness,\n type GameTeamLine,\n type SportScoring,\n type SportStatEventBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n type RoundsDisplay,\n type RoundsScoringPanel,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n} from './scoring-display.js';\n\ntype CurlingEvent = Extract<SportStatEventBody, { type: CurlingStatEventType }>;\n\nfunction isCurlingEvent(event: StatEventBody | undefined): event is CurlingEvent {\n return isSportStatEventIn(CURLING_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// curling game's (SPORT-002).\nfunction ownStats(state: GameStats): CurlingGameStats {\n return state as CurlingGameStats;\n}\n\nfunction ownTeamLine(line: GameTeamLine): CurlingTeamStats {\n return line as CurlingTeamStats;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Lines — a team's `score` is always the sum of its recorded rounds\n// ---------------------------------------------------------------------------------------------\n\ntype RoundSlots = CurlingTeamStats['roundScores'];\n\nfunction sumOfRounds(roundScores: RoundSlots): number {\n return roundScores.reduce<number>((total, slot) => total + (slot ?? 0), 0);\n}\n\n/** Every round unrecorded — `null`, where a blank round is `0`. */\nexport function emptyCurlingTeamLine(): CurlingTeamStats {\n const roundScores: RoundSlots = Array.from({ length: CURLING_ROUND_COUNT }, () => null);\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\nfunction withRound(line: CurlingTeamStats, roundNumber: number, slot: number | null): CurlingTeamStats {\n const roundScores = [...line.roundScores];\n roundScores[roundNumber - 1] = slot;\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\n/**\n * Record or clear one round on every team line: a scored round is the scoring team's points and 0\n * for the others, a blank round 0 for all, and a cleared round `null` for all.\n */\nfunction setRound(stateIn: GameStats, event: CurlingEvent, recorded: boolean): CurlingGameStats {\n const state = ownStats(stateIn);\n const teamStats = { ...state.teamStats };\n const scoringTeamId = event.type === CURLING_ROUND_SCORE_EVENT_TYPE ? event.teamId : undefined;\n if (scoringTeamId !== undefined && teamStats[scoringTeamId] === undefined) {\n teamStats[scoringTeamId] = emptyCurlingTeamLine();\n }\n for (const [teamId, line] of Object.entries(teamStats)) {\n const points = event.type === CURLING_ROUND_SCORE_EVENT_TYPE && teamId === scoringTeamId ? event.points : 0;\n teamStats[teamId] = withRound(line, event.roundNumber, recorded ? points : null);\n }\n return { teamStats, playerStats: state.playerStats };\n}\n\nfunction nextCurlingRound(stateIn: GameStats): number | null {\n const lines = Object.values(ownStats(stateIn).teamStats);\n for (let roundNumber = 1; roundNumber <= CURLING_ROUND_COUNT; roundNumber += 1) {\n if (lines.length === 0 || lines.some((line) => line.roundScores[roundNumber - 1] === null)) return roundNumber;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Scoring\n// ---------------------------------------------------------------------------------------------\n\n// No curling field is correctable: a curling result changes only by reopening the game and\n// re-scoring the round.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: [], player: [] };\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (state, event, roster) => {\n if (!isCurlingEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === CURLING_ROUND_SCORE_EVENT_TYPE && !roster.teamIds.includes(event.teamId)) {\n return refuseStatEvent('teamNotInGame');\n }\n const next = nextCurlingRound(state);\n if (next === null) return refuseStatEvent('roundsComplete');\n return event.roundNumber === next ? STAT_EVENT_ACCEPTED : refuseStatEvent('roundNotNext');\n};\n\nfunction finalizationReadiness(state: GameStats): FinalizationReadiness {\n return nextCurlingRound(state) === null ? FINALIZATION_READY : { ready: false, reason: 'roundsIncomplete' };\n}\n\n// What a completed curling game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst CURLING_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const CURLING_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) throw new Error('No curling field is correctable.');\n if (!isCurlingEvent(event)) throw new Error(`The curling adapter does not apply ${event.type}.`);\n return setRound(state, event, true);\n },\n undo: (state, target) => {\n if (!isCurlingEvent(target)) throw new Error(`The curling adapter does not undo ${target.type}.`);\n return setRound(state, target, false);\n },\n // A curling line has no derived field.\n deriveLine: CURLING_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n nextRound: nextCurlingRound,\n finalizationReadiness,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(CURLING_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(CURLING_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUNDS: RoundsDisplay = { count: CURLING_ROUND_COUNT, label: 'Round' };\n\nexport const CURLING_EVENT_LABELS = {\n [CURLING_ROUND_SCORE_EVENT_TYPE]: { button: 'Score', play: 'Round score' },\n [CURLING_ROUND_BLANK_EVENT_TYPE]: { button: '+0 (Blank)', play: 'Blank end' },\n} as const satisfies Record<CurlingStatEventType, StatEventLabel>;\n\nexport const CURLING_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<CurlingTeamStatKey, never>;\n\nexport function describeCurlingEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\n const storeEvent = describeStoreEvent(event, resolve, CURLING_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isCurlingEvent(event)) return event.type;\n const round = `${CURLING_ROUNDS.label} ${event.roundNumber}`;\n if (event.type === CURLING_ROUND_BLANK_EVENT_TYPE) return `${round}: ${CURLING_EVENT_LABELS[event.type].play.toLowerCase()}`;\n return `${round}: ${resolve.team(event.teamId)} scored ${event.points}`;\n}\n\nexport const CURLING_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n roundSlots: (teamLine) => ownTeamLine(teamLine).roundScores,\n};\n\nexport const CURLING_SCORING_PANEL: RoundsScoringPanel = {\n kind: 'rounds',\n scoreEventType: CURLING_ROUND_SCORE_EVENT_TYPE,\n blankEventType: CURLING_ROUND_BLANK_EVENT_TYPE,\n maxPoints: MAX_CURLING_ROUND_POINTS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — one row per round, a cell per team\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUND_POINTS_MESSAGE = `A round is worth 0 to ${MAX_CURLING_ROUND_POINTS} points.`;\nexport const CURLING_ONE_SCORING_TEAM_MESSAGE =\n 'A round is scored by one team (the other 0 or empty), blank (0 for every team), or left empty.';\nexport const CURLING_NO_SKIPPED_ROUND_MESSAGE = 'Rounds are recorded in order: fill in every earlier round first.';\n\ntype RoundOutcome =\n | { readonly kind: 'unrecorded' }\n | { readonly kind: 'blank' }\n | { readonly kind: 'scored'; readonly teamId: string; readonly points: number }\n | { readonly kind: 'invalid' };\n\nfunction roundOutcome(row: Readonly<Record<string, number | null | undefined>>, teamIds: readonly string[]): RoundOutcome {\n const cells = teamIds.map((teamId) => ({ teamId, points: row[teamId] ?? null }));\n if (cells.every((cell) => cell.points === null)) return { kind: 'unrecorded' };\n if (cells.every((cell) => cell.points === 0)) return { kind: 'blank' };\n const scorers = cells.filter((cell) => cell.points !== null && cell.points > 0);\n const [scorer] = scorers;\n if (scorers.length === 1 && scorer !== undefined && scorer.points !== null) {\n return { kind: 'scored', teamId: scorer.teamId, points: scorer.points };\n }\n return { kind: 'invalid' };\n}\n\nfunction sameOutcome(left: RoundOutcome, right: RoundOutcome): boolean {\n if (left.kind === 'scored' && right.kind === 'scored') return left.teamId === right.teamId && left.points === right.points;\n return left.kind === right.kind;\n}\n\nfunction storedRound(context: BulkEntryContext, roundNumber: number): Record<string, number | null> {\n const state = ownStats(context.state);\n return Object.fromEntries(\n context.game.teamIds.map((teamId) => [teamId, state.teamStats[teamId]?.roundScores[roundNumber - 1] ?? null]),\n );\n}\n\nfunction roundDrafts(values: RoundsValues, context: BulkEntryContext): StatEventDraft[] {\n const { teamIds } = context.game;\n const liveEventByRound = new Map(context.liveRoundEvents.map((event) => [event.roundNumber, event.eventId]));\n const voids: StatEventDraft[] = [];\n const recorded: StatEventDraft[] = [];\n values.rounds.forEach((row, index) => {\n const roundNumber = index + 1;\n const entered = roundOutcome(row, teamIds);\n if (sameOutcome(entered, roundOutcome(storedRound(context, roundNumber), teamIds))) return;\n const liveEventId = liveEventByRound.get(roundNumber);\n if (liveEventId !== undefined) voids.push({ type: STAT_VOID_EVENT_TYPE, targetEventId: liveEventId });\n if (entered.kind === 'scored') {\n recorded.push({ type: CURLING_ROUND_SCORE_EVENT_TYPE, teamId: entered.teamId, roundNumber, points: entered.points });\n } else if (entered.kind === 'blank') {\n recorded.push({ type: CURLING_ROUND_BLANK_EVENT_TYPE, roundNumber });\n }\n });\n // Every void first, then the new rounds in order: each changed round is unrecorded again before\n // any new event is validated, so each new event is the next unrecorded round when it arrives.\n return [...voids, ...recorded];\n}\n\nconst roundCell = bulkEntryCountField\n .pipe(z.number().max(MAX_CURLING_ROUND_POINTS, CURLING_ROUND_POINTS_MESSAGE))\n .nullable();\n\nexport const CURLING_BULK_ENTRY: RoundsBulkEntry = {\n kind: 'rounds',\n formDefaults: (context) => ({\n rounds: Array.from({ length: CURLING_ROUND_COUNT }, (_, index) => storedRound(context, index + 1)),\n }),\n formSchema: (context) => {\n const { teamIds } = context.game;\n const row = z.strictObject(Object.fromEntries(teamIds.map((teamId) => [teamId, roundCell])));\n return z\n .object({ rounds: z.array(row).length(CURLING_ROUND_COUNT) })\n .superRefine((values, issues) => {\n let unrecordedBefore = false;\n values.rounds.forEach((cells, index) => {\n const outcome = roundOutcome(cells, teamIds);\n if (outcome.kind === 'invalid') {\n issues.addIssue({ code: 'custom', path: ['rounds', index], message: CURLING_ONE_SCORING_TEAM_MESSAGE });\n } else if (outcome.kind === 'unrecorded') {\n unrecordedBefore = true;\n } else if (unrecordedBefore) {\n issues.addIssue({ code: 'custom', path: ['rounds', index], message: CURLING_NO_SKIPPED_ROUND_MESSAGE });\n }\n });\n if (roundDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({ code: 'custom', path: ['rounds'], message: BULK_ENTRY_TOO_LARGE_MESSAGE });\n }\n });\n },\n toEvents: roundDrafts,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalized-record.js","sourceRoot":"","sources":["../../src/sports/finalized-record.ts"],"names":[],"mappings":"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","sourcesContent":["// A completed game's finalized record, `sportGames/{gameId}/gameStatHistory/{pageId}`: every event\
|
|
1
|
+
{"version":3,"file":"finalized-record.js","sourceRoot":"","sources":["../../src/sports/finalized-record.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,qGAAqG;AACrG,oGAAoG;AACpG,kGAAkG;AAClG,2CAA2C;AAE3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAgC,EAChC,QAGC;IAED,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,sBAAsB,CAAC,CAAC,CAAC;IAClF,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,sBAAsB,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC;QAClG,MAAM,IAAI,GACR,KAAK,KAAK,CAAC;YACT,CAAC,CAAC;gBACE,MAAM,EAAE,KAAK;gBACb,SAAS;gBACT,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;gBAC9B,eAAe,EAAE,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC;gBAC9C,qBAAqB,EAAE,CAAC,GAAG,QAAQ,CAAC,qBAAqB,CAAC;gBAC1D,yBAAyB,EAAE,CAAC,GAAG,QAAQ,CAAC,yBAAyB,CAAC;aACnE;YACH,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB,CAAC,IAAyB;IACzD,OAAO,WAAW,IAAI,IAAI,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAqC;IAIvE,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACxH,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,SAAS,sBAAsB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACjG,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,IAAI,KAAK,GAAG,CAAC,IAAK,MAAM,CAAC,KAAK,GAAG,CAAC,CAAmB,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjF,MAAM,IAAI,KAAK,CAAC,kCAAkC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;QACtF,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAA4B;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,yBAAyB,EAAE,KAAK,CAAC,yBAAyB;KAC3D,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAwB,EAAE,KAAqC;IACrG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACxD,OAAO;QACL,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC;QACjD,eAAe,EAAE,QAAQ,CAAC,eAAe;QACzC,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;QACrD,yBAAyB,EAAE,QAAQ,CAAC,yBAAyB;KAC9D,CAAC;AACJ,CAAC","sourcesContent":["// A completed game's finalized record, `sportGames/{gameId}/gameStatHistory/{pageId}`: every event\n// document of the game in `sequence` order, in pages of up to STAT_HISTORY_PAGE_SIZE, the first page\n// carrying the sport, the teams, and the participation snapshot finalize applied. These are the one\n// builder and the one reader of its pages, so finalize, the game copy, a ghost link, and the week\n// view all write and read the same record.\n\nimport { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { GameStatHistoryFirstPage, GameStatHistoryPage } from '../doc-schemas/game-stat-history.js';\nimport { buildStatHistoryPageId } from '../ids/stat-event-ids.js';\nimport type { FinalizedGame } from './contribution.js';\nimport { replayGameStats } from './replay.js';\nimport type { AnySportAdapter } from './sport-adapter.js';\n\n/** What a record holds beside its events: the sport, the teams, and the participation snapshot. */\nexport type FinalizedRecordSnapshot = Omit<GameStatHistoryFirstPage, 'events' | 'pageCount'>;\n\n/** One page as written, at its id. */\nexport interface FinalizedRecordPage {\n readonly pageId: string;\n readonly page: GameStatHistoryPage;\n}\n\n/**\n * The record's pages: the events in `sequence` order, cut into pages, the first carrying the page\n * count and the snapshot. A game with no events still has its first page.\n */\nexport function buildFinalizedRecordPages(\n events: readonly GameStatEvent[],\n snapshot: Pick<\n FinalizedGame,\n 'leagueType' | 'teamIds' | 'rosterPlayerIds' | 'absentRosterPlayerIds' | 'nonScoringRosterPlayerIds'\n >,\n): FinalizedRecordPage[] {\n const ordered = [...events].sort((left, right) => left.sequence - right.sequence);\n const pageCount = Math.max(1, Math.ceil(ordered.length / STAT_HISTORY_PAGE_SIZE));\n const pages: FinalizedRecordPage[] = [];\n for (let index = 0; index < pageCount; index += 1) {\n const slice = ordered.slice(index * STAT_HISTORY_PAGE_SIZE, (index + 1) * STAT_HISTORY_PAGE_SIZE);\n const page: GameStatHistoryPage =\n index === 0\n ? {\n events: slice,\n pageCount,\n leagueType: snapshot.leagueType,\n teamIds: [...snapshot.teamIds],\n rosterPlayerIds: [...snapshot.rosterPlayerIds],\n absentRosterPlayerIds: [...snapshot.absentRosterPlayerIds],\n nonScoringRosterPlayerIds: [...snapshot.nonScoringRosterPlayerIds],\n }\n : { events: slice };\n pages.push({ pageId: buildStatHistoryPageId(index), page });\n }\n return pages;\n}\n\n/** Whether a page is a record's first page, which carries the snapshot. */\nexport function isFirstRecordPage(page: GameStatHistoryPage): page is GameStatHistoryFirstPage {\n return 'pageCount' in page;\n}\n\n/**\n * The record read back: the first page's snapshot and every event, in order. Throws when the pages\n * are not one whole record — no first page, a page count they do not match, or events out of order —\n * which is damage to a completed game, never a designed answer.\n */\nexport function readFinalizedRecord(pages: readonly GameStatHistoryPage[]): {\n readonly snapshot: FinalizedRecordSnapshot;\n readonly events: GameStatEvent[];\n} {\n const [first, ...rest] = pages;\n if (first === undefined || !isFirstRecordPage(first)) throw new Error('A finalized record starts with its first page.');\n if (first.pageCount !== pages.length) {\n throw new Error(`A finalized record of ${first.pageCount} pages was read as ${pages.length}.`);\n }\n const events = [...first.events, ...rest.flatMap((page) => page.events)];\n events.forEach((event, index) => {\n if (index > 0 && (events[index - 1] as GameStatEvent).sequence >= event.sequence) {\n throw new Error(`A finalized record holds event ${event.eventId} out of sequence.`);\n }\n });\n const snapshot: FinalizedRecordSnapshot = {\n leagueType: first.leagueType,\n teamIds: first.teamIds,\n rosterPlayerIds: first.rosterPlayerIds,\n absentRosterPlayerIds: first.absentRosterPlayerIds,\n nonScoringRosterPlayerIds: first.nonScoringRosterPlayerIds,\n };\n return { snapshot, events };\n}\n\n/**\n * The completed game a record describes: its events replayed through the sport's adapter, with the\n * snapshot finalize stored. Every path after finalize derives a game's contribution from this, never\n * from the game document.\n */\nexport function finalizedGameFromRecord(adapter: AnySportAdapter, pages: readonly GameStatHistoryPage[]): FinalizedGame {\n const { snapshot, events } = readFinalizedRecord(pages);\n return {\n leagueType: snapshot.leagueType,\n teamIds: snapshot.teamIds,\n stats: replayGameStats(adapter, snapshot, events),\n rosterPlayerIds: snapshot.rosterPlayerIds,\n absentRosterPlayerIds: snapshot.absentRosterPlayerIds,\n nonScoringRosterPlayerIds: snapshot.nonScoringRosterPlayerIds,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"football-scoring.js","sourceRoot":"","sources":["../../src/sports/football-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The football adapter's scoring, display, and bulk-entry members. Football is team-scored: each\r\n// event adds its points to one team's score, no event names a player, and there is no finish rule —\r\n// the game is finalized by hand. Bulk entry takes each team's final score.\r\n//\r\n// `apply` and `undo` are one function over an event's points with opposite signs, so they are exact\r\n// inverses by construction (SPORT-103).\r\n\r\nimport { z } from 'zod';\r\nimport { FOOTBALL_POINTS_BY_EVENT_TYPE, MAX_STAT_EVENTS_PER_WRITE, TEAM_NOT_IN_GAME_MESSAGE } from '../constants/sports.js';\r\nimport type { FootballTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\r\nimport type { FootballGameStats, GameStats } from '../doc-schemas/sport-game.js';\r\nimport {\r\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\r\n FOOTBALL_ONE_POINT_EVENT_TYPE,\r\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\r\n FOOTBALL_SIX_POINT_EVENT_TYPE,\r\n FOOTBALL_STAT_EVENT_TYPE_KEYS,\r\n FOOTBALL_TEAM_STAT_KEYS,\r\n FOOTBALL_TWO_POINT_EVENT_TYPE,\r\n} from '../types/sports.js';\r\nimport type { FootballStatEventType, FootballTeamStatKey } from '../types/sports.js';\r\nimport {\r\n BULK_ENTRY_TOO_LARGE_MESSAGE,\r\n bulkEntryCountField,\r\n type BulkEntryContext,\r\n type StatEventDraft,\r\n type TeamScoresBulkEntry,\r\n type TeamScoresValues,\r\n} from './bulk-entry.js';\r\nimport { buildGameContribution, decideParticipation, type ContributionSpec } from './contribution.js';\r\nimport {\r\n FINALIZATION_READY,\r\n STAT_EVENT_ACCEPTED,\r\n isSportStatEventIn,\r\n isStatCorrection,\r\n refuseStatEvent,\r\n type CorrectableFields,\r\n type SportScoring,\r\n type SportStatEventBody,\r\n type StatCorrectionBody,\r\n type StatEventBody,\r\n} from './scoring.js';\r\nimport {\r\n describeStoreEvent,\r\n formatSignedValue,\r\n type Scoreboard,\r\n type StatEventLabel,\r\n type StatEventNameResolver,\r\n type StatFieldLabels,\r\n type TeamPointsScoringPanel,\r\n} from './scoring-display.js';\r\n\r\ntype FootballEvent = Extract<SportStatEventBody, { type: FootballStatEventType }>;\r\n\r\nfunction isFootballEvent(event: StatEventBody | undefined): event is FootballEvent {\r\n return isSportStatEventIn(FOOTBALL_STAT_EVENT_TYPE_KEYS, event);\r\n}\r\n\r\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\r\n// football game's (SPORT-002).\r\nfunction ownStats(state: GameStats): FootballGameStats {\r\n return state as FootballGameStats;\r\n}\r\n\r\nexport function emptyFootballTeamLine(): FootballTeamStats {\r\n return Object.fromEntries(FOOTBALL_TEAM_STAT_KEYS.map((key) => [key, 0])) as FootballTeamStats;\r\n}\r\n\r\nfunction teamScoreOf(state: FootballGameStats, teamId: string): number {\r\n return (state.teamStats[teamId] ?? emptyFootballTeamLine()).score;\r\n}\r\n\r\n/** The points an event adds to its team's score: a scoring event's own, or a bulk edit's `value`. */\r\nfunction pointsOf(event: FootballEvent): number {\r\n return event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE ? event.value : FOOTBALL_POINTS_BY_EVENT_TYPE[event.type];\r\n}\r\n\r\nfunction addToScore(stateIn: GameStats, teamId: string, amount: number): FootballGameStats {\r\n const state = ownStats(stateIn);\r\n const line = state.teamStats[teamId] ?? emptyFootballTeamLine();\r\n return { teamStats: { ...state.teamStats, [teamId]: { ...line, score: line.score + amount } }, playerStats: state.playerStats };\r\n}\r\n\r\n// A correction to the team's score; football keeps no player line to correct.\r\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: FOOTBALL_TEAM_STAT_KEYS, player: [] };\r\n\r\nfunction correctionAmount(event: StatCorrectionBody): number {\r\n const fields = event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\r\n if (!fields.includes(event.stat)) {\r\n throw new Error(`A football correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`);\r\n }\r\n return event.value;\r\n}\r\n\r\nfunction namesPlayer(event: StatEventBody): boolean {\r\n return 'playerUserId' in event && event.playerUserId !== undefined;\r\n}\r\n\r\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\r\n if (!isFootballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\r\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\r\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\r\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\r\n if (!Number.isInteger(event.value) || event.value === 0) return refuseStatEvent('bulkValueInvalid');\r\n const score = teamScoreOf(ownStats(stateIn), event.teamId);\r\n if (event.previousValue !== score) return refuseStatEvent('bulkStale');\r\n if (score + event.value < 0) return refuseStatEvent('bulkValueInvalid');\r\n }\r\n return STAT_EVENT_ACCEPTED;\r\n};\r\n\r\n// What a completed football game adds: the standings line from the two scores, and a game played\r\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\r\n// absence withholds a game played and no entry is ever non-scoring.\r\nconst FOOTBALL_CONTRIBUTION: ContributionSpec = {\r\n playerStatKeys: [],\r\n teamSeasonPlayerSums: [],\r\n zeroLineIsNonScoring: false,\r\n deriveLine: (_kind, line) => line,\r\n};\r\n\r\nexport const FOOTBALL_SCORING: SportScoring = {\r\n validate,\r\n apply: (state, event) => {\r\n if (isStatCorrection(event)) return addToScore(state, event.teamId, correctionAmount(event));\r\n if (!isFootballEvent(event)) throw new Error(`The football adapter does not apply ${event.type}.`);\r\n return addToScore(state, event.teamId, pointsOf(event));\r\n },\r\n undo: (state, target) => {\r\n if (!isFootballEvent(target)) throw new Error(`The football adapter does not undo ${target.type}.`);\r\n return addToScore(state, target.teamId, -pointsOf(target));\r\n },\r\n // A football line has no derived field.\r\n deriveLine: FOOTBALL_CONTRIBUTION.deriveLine,\r\n correctableFields: CORRECTABLE_FIELDS,\r\n // No rounds and no finish rule: the game is finalized by hand.\r\n nextRound: () => null,\r\n finalizationReadiness: () => FINALIZATION_READY,\r\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\r\n decideParticipation(FOOTBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\r\n contribution: (game) => buildGameContribution(FOOTBALL_CONTRIBUTION, game),\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Display\r\n// ---------------------------------------------------------------------------------------------\r\n\r\nexport const FOOTBALL_EVENT_LABELS = {\r\n [FOOTBALL_SIX_POINT_EVENT_TYPE]: { button: '+6', play: '6 points' },\r\n [FOOTBALL_TWO_POINT_EVENT_TYPE]: { button: '+2', play: '2 points' },\r\n [FOOTBALL_ONE_POINT_EVENT_TYPE]: { button: '+1', play: '1 point' },\r\n [FOOTBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Final scores', play: 'Final score entry' },\r\n} as const satisfies Record<FootballStatEventType, StatEventLabel>;\r\n\r\nexport const FOOTBALL_FIELD_LABELS = {\r\n team: { score: 'Score' },\r\n player: {},\r\n} as const satisfies StatFieldLabels<FootballTeamStatKey, never>;\r\n\r\nexport function describeFootballEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\r\n const storeEvent = describeStoreEvent(event, resolve, FOOTBALL_FIELD_LABELS);\r\n if (storeEvent !== null) return storeEvent;\r\n if (!isFootballEvent(event)) return event.type;\r\n const team = resolve.team(event.teamId);\r\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\r\n return `${team}: ${formatSignedValue(event.value)} points (${FOOTBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\r\n }\r\n return `${team}: ${FOOTBALL_EVENT_LABELS[event.type].play}`;\r\n}\r\n\r\nexport const FOOTBALL_SCOREBOARD: Scoreboard = {\r\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\r\n jumpBall: false,\r\n};\r\n\r\nexport const FOOTBALL_SCORING_PANEL: TeamPointsScoringPanel = {\r\n kind: 'teamPoints',\r\n eventTypes: FOOTBALL_SCORE_EVENT_TYPE_KEYS,\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------\r\n// Bulk entry — each team's final score\r\n// ---------------------------------------------------------------------------------------------\r\n\r\nfunction scoreDrafts(values: TeamScoresValues, context: BulkEntryContext): StatEventDraft[] {\r\n const state = ownStats(context.state);\r\n const drafts: StatEventDraft[] = [];\r\n for (const teamId of context.game.teamIds) {\r\n const entered = values.scores[teamId];\r\n const previousValue = teamScoreOf(state, teamId);\r\n if (entered === undefined || entered === previousValue) continue;\r\n drafts.push({ type: FOOTBALL_BULK_EDIT_EVENT_TYPE, teamId, value: entered - previousValue, previousValue });\r\n }\r\n return drafts;\r\n}\r\n\r\nexport const FOOTBALL_BULK_ENTRY: TeamScoresBulkEntry = {\r\n kind: 'teamScores',\r\n formDefaults: (context) => ({\r\n scores: Object.fromEntries(context.game.teamIds.map((teamId) => [teamId, teamScoreOf(ownStats(context.state), teamId)])),\r\n }),\r\n formSchema: (context) => {\r\n const teams = new Set(context.game.teamIds);\r\n return z.object({ scores: z.record(z.string(), bulkEntryCountField) }).superRefine((values, issues) => {\r\n for (const teamId of Object.keys(values.scores)) {\r\n if (!teams.has(teamId)) issues.addIssue({ code: 'custom', path: ['scores', teamId], message: TEAM_NOT_IN_GAME_MESSAGE });\r\n }\r\n if (scoreDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\r\n issues.addIssue({ code: 'custom', path: ['scores'], message: BULK_ENTRY_TOO_LARGE_MESSAGE });\r\n }\r\n });\r\n },\r\n toEvents: scoreDrafts,\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"football-scoring.js","sourceRoot":"","sources":["../../src/sports/football-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The football adapter's scoring, display, and bulk-entry members. Football is team-scored: each\n// event adds its points to one team's score, no event names a player, and there is no finish rule —\n// the game is finalized by hand. Bulk entry takes each team's final score.\n//\n// `apply` and `undo` are one function over an event's points with opposite signs, so they are exact\n// inverses by construction (SPORT-103).\n\nimport { z } from 'zod';\nimport { FOOTBALL_POINTS_BY_EVENT_TYPE, MAX_STAT_EVENTS_PER_WRITE, TEAM_NOT_IN_GAME_MESSAGE } from '../constants/sports.js';\nimport type { FootballTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { FootballGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_ONE_POINT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_SIX_POINT_EVENT_TYPE,\n FOOTBALL_STAT_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n FOOTBALL_TWO_POINT_EVENT_TYPE,\n} from '../types/sports.js';\nimport type { FootballStatEventType, FootballTeamStatKey } from '../types/sports.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type StatEventDraft,\n type TeamScoresBulkEntry,\n type TeamScoresValues,\n} from './bulk-entry.js';\nimport { buildGameContribution, decideParticipation, type ContributionSpec } from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type SportScoring,\n type SportStatEventBody,\n type StatCorrectionBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n formatSignedValue,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n type TeamPointsScoringPanel,\n} from './scoring-display.js';\n\ntype FootballEvent = Extract<SportStatEventBody, { type: FootballStatEventType }>;\n\nfunction isFootballEvent(event: StatEventBody | undefined): event is FootballEvent {\n return isSportStatEventIn(FOOTBALL_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// football game's (SPORT-002).\nfunction ownStats(state: GameStats): FootballGameStats {\n return state as FootballGameStats;\n}\n\nexport function emptyFootballTeamLine(): FootballTeamStats {\n return Object.fromEntries(FOOTBALL_TEAM_STAT_KEYS.map((key) => [key, 0])) as FootballTeamStats;\n}\n\nfunction teamScoreOf(state: FootballGameStats, teamId: string): number {\n return (state.teamStats[teamId] ?? emptyFootballTeamLine()).score;\n}\n\n/** The points an event adds to its team's score: a scoring event's own, or a bulk edit's `value`. */\nfunction pointsOf(event: FootballEvent): number {\n return event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE ? event.value : FOOTBALL_POINTS_BY_EVENT_TYPE[event.type];\n}\n\nfunction addToScore(stateIn: GameStats, teamId: string, amount: number): FootballGameStats {\n const state = ownStats(stateIn);\n const line = state.teamStats[teamId] ?? emptyFootballTeamLine();\n return { teamStats: { ...state.teamStats, [teamId]: { ...line, score: line.score + amount } }, playerStats: state.playerStats };\n}\n\n// A correction to the team's score; football keeps no player line to correct.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: FOOTBALL_TEAM_STAT_KEYS, player: [] };\n\nfunction correctionAmount(event: StatCorrectionBody): number {\n const fields = event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\n if (!fields.includes(event.stat)) {\n throw new Error(`A football correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`);\n }\n return event.value;\n}\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\n if (!isFootballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n if (!Number.isInteger(event.value) || event.value === 0) return refuseStatEvent('bulkValueInvalid');\n const score = teamScoreOf(ownStats(stateIn), event.teamId);\n if (event.previousValue !== score) return refuseStatEvent('bulkStale');\n if (score + event.value < 0) return refuseStatEvent('bulkValueInvalid');\n }\n return STAT_EVENT_ACCEPTED;\n};\n\n// What a completed football game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst FOOTBALL_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const FOOTBALL_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) return addToScore(state, event.teamId, correctionAmount(event));\n if (!isFootballEvent(event)) throw new Error(`The football adapter does not apply ${event.type}.`);\n return addToScore(state, event.teamId, pointsOf(event));\n },\n undo: (state, target) => {\n if (!isFootballEvent(target)) throw new Error(`The football adapter does not undo ${target.type}.`);\n return addToScore(state, target.teamId, -pointsOf(target));\n },\n // A football line has no derived field.\n deriveLine: FOOTBALL_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n // No rounds and no finish rule: the game is finalized by hand.\n nextRound: () => null,\n finalizationReadiness: () => FINALIZATION_READY,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(FOOTBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(FOOTBALL_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const FOOTBALL_EVENT_LABELS = {\n [FOOTBALL_SIX_POINT_EVENT_TYPE]: { button: '+6', play: '6 points' },\n [FOOTBALL_TWO_POINT_EVENT_TYPE]: { button: '+2', play: '2 points' },\n [FOOTBALL_ONE_POINT_EVENT_TYPE]: { button: '+1', play: '1 point' },\n [FOOTBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Final scores', play: 'Final score entry' },\n} as const satisfies Record<FootballStatEventType, StatEventLabel>;\n\nexport const FOOTBALL_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<FootballTeamStatKey, never>;\n\nexport function describeFootballEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\n const storeEvent = describeStoreEvent(event, resolve, FOOTBALL_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isFootballEvent(event)) return event.type;\n const team = resolve.team(event.teamId);\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n return `${team}: ${formatSignedValue(event.value)} points (${FOOTBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\n }\n return `${team}: ${FOOTBALL_EVENT_LABELS[event.type].play}`;\n}\n\nexport const FOOTBALL_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n};\n\nexport const FOOTBALL_SCORING_PANEL: TeamPointsScoringPanel = {\n kind: 'teamPoints',\n eventTypes: FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — each team's final score\n// ---------------------------------------------------------------------------------------------\n\nfunction scoreDrafts(values: TeamScoresValues, context: BulkEntryContext): StatEventDraft[] {\n const state = ownStats(context.state);\n const drafts: StatEventDraft[] = [];\n for (const teamId of context.game.teamIds) {\n const entered = values.scores[teamId];\n const previousValue = teamScoreOf(state, teamId);\n if (entered === undefined || entered === previousValue) continue;\n drafts.push({ type: FOOTBALL_BULK_EDIT_EVENT_TYPE, teamId, value: entered - previousValue, previousValue });\n }\n return drafts;\n}\n\nexport const FOOTBALL_BULK_ENTRY: TeamScoresBulkEntry = {\n kind: 'teamScores',\n formDefaults: (context) => ({\n scores: Object.fromEntries(context.game.teamIds.map((teamId) => [teamId, teamScoreOf(ownStats(context.state), teamId)])),\n }),\n formSchema: (context) => {\n const teams = new Set(context.game.teamIds);\n return z.object({ scores: z.record(z.string(), bulkEntryCountField) }).superRefine((values, issues) => {\n for (const teamId of Object.keys(values.scores)) {\n if (!teams.has(teamId)) issues.addIssue({ code: 'custom', path: ['scores', teamId], message: TEAM_NOT_IN_GAME_MESSAGE });\n }\n if (scoreDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({ code: 'custom', path: ['scores'], message: BULK_ENTRY_TOO_LARGE_MESSAGE });\n }\n });\n },\n toEvents: scoreDrafts,\n};\n"]}
|
package/dist/sports/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './scoring.js';
|
|
|
5
5
|
export * from './scoring-display.js';
|
|
6
6
|
export * from './bulk-entry.js';
|
|
7
7
|
export * from './replay.js';
|
|
8
|
+
export * from './ledger-continuity.js';
|
|
8
9
|
export * from './store-driver.js';
|
|
9
10
|
export * from './contribution.js';
|
|
10
11
|
export * from './finalized-record.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAGA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAGA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/sports/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './scoring.js';
|
|
|
7
7
|
export * from './scoring-display.js';
|
|
8
8
|
export * from './bulk-entry.js';
|
|
9
9
|
export * from './replay.js';
|
|
10
|
+
export * from './ledger-continuity.js';
|
|
10
11
|
export * from './store-driver.js';
|
|
11
12
|
export * from './contribution.js';
|
|
12
13
|
export * from './finalized-record.js';
|
package/dist/sports/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,6BAA6B;AAE7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// The sport adapters. A sport is selected only through the registry (SPORT-002), so the individual\
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,6BAA6B;AAE7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// The sport adapters. A sport is selected only through the registry (SPORT-002), so the individual\n// adapters are not exported.\n\nexport * from './column-catalog.js';\nexport * from './sport-adapter.js';\nexport * from './shared-columns.js';\nexport * from './scoring.js';\nexport * from './scoring-display.js';\nexport * from './bulk-entry.js';\nexport * from './replay.js';\nexport * from './ledger-continuity.js';\nexport * from './store-driver.js';\nexport * from './contribution.js';\nexport * from './finalized-record.js';\nexport * from './rankings.js';\nexport * from './rollup-check.js';\nexport type { LifetimeSummary, LifetimeSummaryAverage, LifetimeSummaryTotal } from './lifetime-summary.js';\nexport { basketballFantasyPoints } from './basketball-scoring.js';\nexport { SPORT_ADAPTERS, SPORT_ADAPTER_LIST, UnknownSportError, getSportAdapter } from './registry.js';\nexport { gameScoreMirror } from './score-mirror.js';\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The first place a ledger departs from 1..n: a sequence missing, or one held by two events. */
|
|
2
|
+
export type LedgerContinuityBreak = {
|
|
3
|
+
readonly kind: 'gap';
|
|
4
|
+
readonly missingSequence: number;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'duplicate';
|
|
7
|
+
readonly sequence: number;
|
|
8
|
+
};
|
|
9
|
+
/** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */
|
|
10
|
+
export declare function findLedgerContinuityBreak(events: readonly {
|
|
11
|
+
readonly sequence: number;
|
|
12
|
+
}[]): LedgerContinuityBreak | null;
|
|
13
|
+
//# sourceMappingURL=ledger-continuity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-continuity.d.ts","sourceRoot":"","sources":["../../src/sports/ledger-continuity.ts"],"names":[],"mappings":"AAKA,iGAAiG;AACjG,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,oHAAoH;AACpH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,qBAAqB,GAAG,IAAI,CAUxH"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Whether a game's event ledger holds exactly the sequences 1..n. `statEventSequence` is both the
|
|
2
|
+
// number of events and the last sequence given, so a gap or a duplicate is out-of-band damage: the
|
|
3
|
+
// check reports it, and finalize and the restore refuse it. The event documents are the source of
|
|
4
|
+
// truth (SPORT-101), so nothing renumbers them to close a gap.
|
|
5
|
+
/** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */
|
|
6
|
+
export function findLedgerContinuityBreak(events) {
|
|
7
|
+
const sequences = events.map((event) => event.sequence).sort((left, right) => left - right);
|
|
8
|
+
for (let index = 0; index < sequences.length; index += 1) {
|
|
9
|
+
const expected = index + 1;
|
|
10
|
+
const sequence = sequences[index];
|
|
11
|
+
if (sequence === expected)
|
|
12
|
+
continue;
|
|
13
|
+
if (index > 0 && sequence === sequences[index - 1])
|
|
14
|
+
return { kind: 'duplicate', sequence };
|
|
15
|
+
return { kind: 'gap', missingSequence: expected };
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=ledger-continuity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-continuity.js","sourceRoot":"","sources":["../../src/sports/ledger-continuity.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,+DAA+D;AAO/D,oHAAoH;AACpH,MAAM,UAAU,yBAAyB,CAAC,MAAgD;IACxF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IAC5F,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACpC,IAAI,KAAK,GAAG,CAAC,IAAI,QAAQ,KAAK,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;QAC3F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// Whether a game's event ledger holds exactly the sequences 1..n. `statEventSequence` is both the\n// number of events and the last sequence given, so a gap or a duplicate is out-of-band damage: the\n// check reports it, and finalize and the restore refuse it. The event documents are the source of\n// truth (SPORT-101), so nothing renumbers them to close a gap.\n\n/** The first place a ledger departs from 1..n: a sequence missing, or one held by two events. */\nexport type LedgerContinuityBreak =\n | { readonly kind: 'gap'; readonly missingSequence: number }\n | { readonly kind: 'duplicate'; readonly sequence: number };\n\n/** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */\nexport function findLedgerContinuityBreak(events: readonly { readonly sequence: number }[]): LedgerContinuityBreak | null {\n const sequences = events.map((event) => event.sequence).sort((left, right) => left - right);\n for (let index = 0; index < sequences.length; index += 1) {\n const expected = index + 1;\n const sequence = sequences[index];\n if (sequence === expected) continue;\n if (index > 0 && sequence === sequences[index - 1]) return { kind: 'duplicate', sequence };\n return { kind: 'gap', missingSequence: expected };\n }\n return null;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../src/sports/rankings.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wDAAwD;AACxD,mGAAmG;AACnG,iGAAiG;AACjG,qCAAqC;AAErC,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAYzD,kGAAkG;AAClG,MAAM,UAAU,iBAAiB,CAC/B,KAAoH;IAEpH,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,EAAE,CACrD,MAAM,CAAC,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAC5F,CAAC;AACJ,CAAC;AASD;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAKG;IAEH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,qBAAqB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,SAAS;QAC9E,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC7B,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CACrG,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAwB,EAAE,KAA+B;IACvF,MAAM,MAAM,GAAG,oBAAoB,CACjC,OAAO,CAAC,UAAU,EAClB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAU,EAAE,CAAC,CAAC,CACtF,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;QACvE,MAAM,KAAK,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC/C,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// The league's player rankings: one row per team a player played for, for the whole season or for\
|
|
1
|
+
{"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../src/sports/rankings.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wDAAwD;AACxD,mGAAmG;AACnG,iGAAiG;AACjG,qCAAqC;AAErC,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAYzD,kGAAkG;AAClG,MAAM,UAAU,iBAAiB,CAC/B,KAAoH;IAEpH,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,EAAE,CACrD,MAAM,CAAC,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAC5F,CAAC;AACJ,CAAC;AASD;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAKG;IAEH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,qBAAqB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,SAAS;QAC9E,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC7B,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CACrG,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAwB,EAAE,KAA+B;IACvF,MAAM,MAAM,GAAG,oBAAoB,CACjC,OAAO,CAAC,UAAU,EAClB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAU,EAAE,CAAC,CAAC,CACtF,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;QACvE,MAAM,KAAK,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC/C,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// The league's player rankings: one row per team a player played for, for the whole season or for\n// one saved week. The season rows are the teams' stored\n// per-membership rows; a week's rows are derived at read, never stored — the contributions of that\n// week's completed games summed through the same mapping finalize applies, so a week's rows over\n// every week add up to the season's.\n\nimport { COMPLETED_GAME_STATUS } from '../types/unions.js';\nimport type { GameStatus } from '../types/unions.js';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { sumGameContributions } from './contribution.js';\nimport type { FinalizedGame } from './contribution.js';\nimport type { StatLineValues } from './scoring.js';\nimport type { AnySportAdapter } from './sport-adapter.js';\n\n/** One rankings row: a person's games for one team, keyed by the membership the team, league, and person name. */\nexport interface RankingRow {\n readonly teamId: string;\n readonly userId: string;\n readonly line: StatLineValues;\n}\n\n/** The season rows: every team's stored per-membership rows, one per team a player played for. */\nexport function seasonRankingRows(\n teams: readonly { readonly teamId: string; readonly playerSeasonStats?: Readonly<Record<string, StatLineValues>> }[],\n): RankingRow[] {\n return teams.flatMap(({ teamId, playerSeasonStats }) =>\n Object.entries(playerSeasonStats ?? {}).map(([userId, line]) => ({ teamId, userId, line })),\n );\n}\n\n/** A saved schedule week as the rankings' week filter offers it. */\nexport interface PlayedWeek {\n readonly weekId: string;\n readonly weekTitle: string;\n readonly weekStartDate: number;\n}\n\n/**\n * The weeks the filter offers: every saved week holding at least one completed game, in the order\n * the weeks start, taken from the game documents the league schedule already loads.\n */\nexport function playedWeeks(\n games: readonly {\n readonly weekId: string;\n readonly weekTitle: string;\n readonly weekStartDate: number;\n readonly status: GameStatus;\n }[],\n): PlayedWeek[] {\n const weeks = new Map<string, PlayedWeek>();\n for (const game of games) {\n if (game.status !== COMPLETED_GAME_STATUS || weeks.has(game.weekId)) continue;\n weeks.set(game.weekId, { weekId: game.weekId, weekTitle: game.weekTitle, weekStartDate: game.weekStartDate });\n }\n return [...weeks.values()].sort(\n (left, right) => left.weekStartDate - right.weekStartDate || left.weekId.localeCompare(right.weekId),\n );\n}\n\n/**\n * One week's rows: the summed contributions of the week's completed games, each read from its\n * finalized record (`finalizedGameFromRecord`). A player who played both sides of a game that week\n * has a row on each team.\n */\nexport function weekRankingRows(adapter: AnySportAdapter, games: readonly FinalizedGame[]): RankingRow[] {\n const summed = sumGameContributions(\n adapter.deriveLine,\n games.map((game) => ({ contribution: adapter.contribution(game), sign: 1 as const })),\n );\n return Object.entries(summed.membershipRows).flatMap(([entryId, line]) => {\n const entry = parseGameRosterPlayerId(entryId);\n return entry === null ? [] : [{ teamId: entry.teamId, userId: entry.userId, line }];\n });\n}\n"]}
|