From 05c8699b0c5f165083153de42e21ebdb491e27ad Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Fri, 1 May 2026 21:48:52 +0000 Subject: [PATCH] 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 --- app/models/surface-elo.ts | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/app/models/surface-elo.ts b/app/models/surface-elo.ts index edb4a27..6267fcf 100644 --- a/app/models/surface-elo.ts +++ b/app/models/surface-elo.ts @@ -7,7 +7,7 @@ */ import { database } from "~/database/context"; -import { seasonParticipantSurfaceElos, seasonParticipants } from "~/database/schema"; +import { participantSurfaceElos, seasonParticipantSurfaceElos, seasonParticipants } from "~/database/schema"; import { eq, sql } from "drizzle-orm"; 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. * 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( sportsSeasonId: string ): Promise> { const db = database(); const rows = await db .select({ - participantId: seasonParticipantSurfaceElos.participantId, - worldRanking: seasonParticipantSurfaceElos.worldRanking, - eloHard: seasonParticipantSurfaceElos.eloHard, - eloClay: seasonParticipantSurfaceElos.eloClay, - eloGrass: seasonParticipantSurfaceElos.eloGrass, + seasonParticipantId: seasonParticipants.id, + worldRanking: participantSurfaceElos.worldRanking, + eloHard: participantSurfaceElos.eloHard, + eloClay: participantSurfaceElos.eloClay, + eloGrass: participantSurfaceElos.eloGrass, }) - .from(seasonParticipantSurfaceElos) - .where(eq(seasonParticipantSurfaceElos.sportsSeasonId, sportsSeasonId)); + .from(seasonParticipants) + .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, eloHard: r.eloHard, eloClay: r.eloClay,