feat(surface-elo): getSurfaceEloMap reads from canonical

Switch the tennis simulator's Elo source from the per-window table
to the canonical participant_surface_elos via season_participants.participant_id.
Map key stays season_participant.id so the simulator's call pattern is unchanged.

Other functions in this file still read/write the per-window table
(seasonParticipantSurfaceElos). They back the old admin surface-elo page
and are removed in Phase 4 after the canonical admin flow replaces them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-05-01 21:48:52 +00:00
parent 699fb1f982
commit 05c8699b0c
No known key found for this signature in database

View file

@ -7,7 +7,7 @@
*/ */
import { database } from "~/database/context"; import { database } from "~/database/context";
import { seasonParticipantSurfaceElos, seasonParticipants } from "~/database/schema"; import { participantSurfaceElos, seasonParticipantSurfaceElos, seasonParticipants } from "~/database/schema";
import { eq, sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
export type CourtSurface = "hard" | "clay" | "grass"; export type CourtSurface = "hard" | "clay" | "grass";
@ -105,22 +105,35 @@ export async function batchUpsertSurfaceElos(
* Returns a Map from participantId to surface Elos for use in the simulator. * Returns a Map from participantId to surface Elos for use in the simulator.
* Participants with no record are absent from the map (simulator falls back to 1500). * Participants with no record are absent from the map (simulator falls back to 1500).
*/ */
/**
* Build a map keyed by seasonParticipant.id surface Elo values, sourced from
* the canonical `participant_surface_elos` table. Joins seasonParticipants
* canonical participants canonical surfaceElo.
*
* Season participants without a linked canonical participant, or with no
* canonical surface Elo row, are simply absent from the map callers should
* handle `undefined` for such cases (as the tennis simulator does).
*/
export async function getSurfaceEloMap( export async function getSurfaceEloMap(
sportsSeasonId: string sportsSeasonId: string
): Promise<Map<string, { worldRanking: number | null; eloHard: number | null; eloClay: number | null; eloGrass: number | null }>> { ): Promise<Map<string, { worldRanking: number | null; eloHard: number | null; eloClay: number | null; eloGrass: number | null }>> {
const db = database(); const db = database();
const rows = await db const rows = await db
.select({ .select({
participantId: seasonParticipantSurfaceElos.participantId, seasonParticipantId: seasonParticipants.id,
worldRanking: seasonParticipantSurfaceElos.worldRanking, worldRanking: participantSurfaceElos.worldRanking,
eloHard: seasonParticipantSurfaceElos.eloHard, eloHard: participantSurfaceElos.eloHard,
eloClay: seasonParticipantSurfaceElos.eloClay, eloClay: participantSurfaceElos.eloClay,
eloGrass: seasonParticipantSurfaceElos.eloGrass, eloGrass: participantSurfaceElos.eloGrass,
}) })
.from(seasonParticipantSurfaceElos) .from(seasonParticipants)
.where(eq(seasonParticipantSurfaceElos.sportsSeasonId, sportsSeasonId)); .innerJoin(
participantSurfaceElos,
eq(participantSurfaceElos.participantId, seasonParticipants.participantId),
)
.where(eq(seasonParticipants.sportsSeasonId, sportsSeasonId));
return new Map(rows.map((r) => [r.participantId, { return new Map(rows.map((r) => [r.seasonParticipantId, {
worldRanking: r.worldRanking, worldRanking: r.worldRanking,
eloHard: r.eloHard, eloHard: r.eloHard,
eloClay: r.eloClay, eloClay: r.eloClay,