From 7483cd628bb35ca636db0aa7515270de049cd030 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Fri, 1 May 2026 22:23:44 +0000 Subject: [PATCH] feat(surface-elo): mirror batchUpsert writes to canonical table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The simulator now reads surface Elo from the canonical table (participant_surface_elos). The existing per-window admin page still posts to batchUpsertSurfaceElos; without this change, those edits would silently fail to affect simulator output. Mirror every write to both tables until Phase 4 removes the per-window path entirely. Phase 3 Task 5 (partial — canonical write path; dedicated canonical admin UI deferred; existing page continues to work and now edits both tables). Co-Authored-By: Claude Opus 4.7 --- app/models/surface-elo.ts | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/app/models/surface-elo.ts b/app/models/surface-elo.ts index 6267fcf..83258c2 100644 --- a/app/models/surface-elo.ts +++ b/app/models/surface-elo.ts @@ -8,7 +8,7 @@ import { database } from "~/database/context"; import { participantSurfaceElos, seasonParticipantSurfaceElos, seasonParticipants } from "~/database/schema"; -import { eq, sql } from "drizzle-orm"; +import { eq, inArray, sql } from "drizzle-orm"; export type CourtSurface = "hard" | "clay" | "grass"; @@ -99,6 +99,45 @@ export async function batchUpsertSurfaceElos( updatedAt: sql`excluded.updated_at`, }, }); + + // Mirror writes to the canonical participant_surface_elos table. + // The simulator reads from canonical, so per-window-only edits would + // not affect simulations. Remove the per-window path entirely in Phase 4. + const sps = await db + .select({ id: seasonParticipants.id, canonicalId: seasonParticipants.participantId }) + .from(seasonParticipants) + .where(inArray(seasonParticipants.id, inputs.map((i) => i.participantId))); + const canonicalByInputId = new Map(sps.map((sp) => [sp.id, sp.canonicalId])); + + const canonicalRows = inputs + .map((i) => ({ + canonicalId: canonicalByInputId.get(i.participantId), + worldRanking: i.worldRanking ?? null, + eloHard: i.eloHard ?? null, + eloClay: i.eloClay ?? null, + eloGrass: i.eloGrass ?? null, + })) + .filter((r): r is typeof r & { canonicalId: string } => typeof r.canonicalId === "string"); + + if (canonicalRows.length === 0) return; + + await db + .insert(participantSurfaceElos) + .values(canonicalRows.map(({ canonicalId, ...rest }) => ({ + participantId: canonicalId, + ...rest, + updatedAt: now, + }))) + .onConflictDoUpdate({ + target: [participantSurfaceElos.participantId], + set: { + worldRanking: sql`excluded.world_ranking`, + eloHard: sql`excluded.elo_hard`, + eloClay: sql`excluded.elo_clay`, + eloGrass: sql`excluded.elo_grass`, + updatedAt: sql`excluded.updated_at`, + }, + }); } /**