refactor(models): update model layer to use renamed schema exports

Updated all model files to use the renamed schema exports from Task 1:
- participants → seasonParticipants
- participantExpectedValues → seasonParticipantExpectedValues
- participantQualifyingTotals → seasonParticipantQualifyingTotals
- participantResults → seasonParticipantResults
- participantSurfaceElos → seasonParticipantSurfaceElos
- eventResults.participantId → eventResults.seasonParticipantId
- db.query relation accessors updated
- Relation field .participant → .seasonParticipant where applicable
- Import paths updated: ./participant → ./season-participant

Files updated (14 model files + 3 test files):
- draft-pick.ts
- draft-utils.ts
- event-result.ts
- group-stage-match.ts
- participant-result.ts
- qualifying-points.ts
- scoring-calculator.ts
- scoring-event.ts
- sports-season.ts
- surface-elo.ts
- team-score-events.ts
- cs2-major-stage.ts
- golf-skills.ts
- participant-expected-value.ts
- __tests__/sports-season.clone.test.ts
- __tests__/auto-pick.test.ts
- __tests__/executeAutoPick.timer.test.ts

Typecheck errors decreased: 779 → 499 (280 fewer)
All model file errors related to renamed schemas resolved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-05-01 06:37:53 +00:00
parent fdf5fe8976
commit fd99cab61b
No known key found for this signature in database
17 changed files with 217 additions and 217 deletions

View file

@ -34,7 +34,7 @@ import {
getTeamDraftPicksWithSports, getTeamDraftPicksWithSports,
isParticipantDrafted, isParticipantDrafted,
} from "~/models/draft-pick"; } from "~/models/draft-pick";
import { getParticipantsForSeasonWithSports } from "~/models/participant"; import { getParticipantsForSeasonWithSports } from "~/models/season-participant";
import { getSeasonSportsSimple } from "~/models/season-sport"; import { getSeasonSportsSimple } from "~/models/season-sport";
import { getTeamQueue, getAllQueuesForSeason } from "~/models/draft-queue"; import { getTeamQueue, getAllQueuesForSeason } from "~/models/draft-queue";
import { calculateDraftEligibility } from "~/lib/draft-eligibility"; import { calculateDraftEligibility } from "~/lib/draft-eligibility";
@ -49,7 +49,7 @@ const ALL_TEAM_IDS = [TEAM_ID, "team-2"];
function makeMockDb(overrides: Record<string, unknown> = {}) { function makeMockDb(overrides: Record<string, unknown> = {}) {
const mockDb: Record<string, unknown> = { const mockDb: Record<string, unknown> = {
query: { query: {
participants: { seasonParticipants: {
findMany: vi.fn().mockResolvedValue([]), findMany: vi.fn().mockResolvedValue([]),
}, },
}, },
@ -71,7 +71,7 @@ function makeMockDb(overrides: Record<string, unknown> = {}) {
// 3. select().from().where().orderBy() → top participant → [{id, ...}] // 3. select().from().where().orderBy() → top participant → [{id, ...}]
function makeMockDbWithEvParticipant(participantId: string) { function makeMockDbWithEvParticipant(participantId: string) {
return { return {
query: { participants: { findMany: vi.fn().mockResolvedValue([]) } }, query: { seasonParticipants: { findMany: vi.fn().mockResolvedValue([]) } },
select: vi.fn().mockReturnThis(), select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(),

View file

@ -42,7 +42,7 @@ vi.mock("~/lib/draft-eligibility", () => ({
vi.mock("~/database/context"); vi.mock("~/database/context");
import { getDraftPicksWithSports, getTeamDraftPicksWithSports, isParticipantDrafted } from "~/models/draft-pick"; import { getDraftPicksWithSports, getTeamDraftPicksWithSports, isParticipantDrafted } from "~/models/draft-pick";
import { getParticipantsForSeasonWithSports } from "~/models/participant"; import { getParticipantsForSeasonWithSports } from "~/models/season-participant";
import { getSeasonSportsSimple } from "~/models/season-sport"; import { getSeasonSportsSimple } from "~/models/season-sport";
import { getTeamQueue, getAllQueuesForSeason } from "~/models/draft-queue"; import { getTeamQueue, getAllQueuesForSeason } from "~/models/draft-queue";
import { calculateDraftEligibility } from "~/lib/draft-eligibility"; import { calculateDraftEligibility } from "~/lib/draft-eligibility";
@ -127,7 +127,7 @@ function makeMockDb(seasonOverrides: Record<string, unknown> = {}) {
draftPicks: { findFirst: vi.fn().mockResolvedValue(null) }, draftPicks: { findFirst: vi.fn().mockResolvedValue(null) },
seasons: { findFirst: vi.fn().mockResolvedValue(makeSeason(seasonOverrides)) }, seasons: { findFirst: vi.fn().mockResolvedValue(makeSeason(seasonOverrides)) },
draftSlots: { findMany: vi.fn().mockResolvedValue(mockDraftSlots) }, draftSlots: { findMany: vi.fn().mockResolvedValue(mockDraftSlots) },
participants: { seasonParticipants: {
// findMany: used by autoPickForTeam queue path // findMany: used by autoPickForTeam queue path
findMany: vi.fn().mockResolvedValue([mockParticipantForQueue]), findMany: vi.fn().mockResolvedValue([mockParticipantForQueue]),
// findFirst: used by executeAutoPick to fetch full participant details // findFirst: used by executeAutoPick to fetch full participant details

View file

@ -6,9 +6,9 @@ vi.mock("~/database/context", () => ({
vi.mock("~/database/schema", () => ({ vi.mock("~/database/schema", () => ({
sportsSeasons: { id: "ss.id", sportId: "ss.sport_id" }, sportsSeasons: { id: "ss.id", sportId: "ss.sport_id" },
participants: { sportsSeasonId: "p.sports_season_id" }, seasonParticipants: { sportsSeasonId: "p.sports_season_id" },
scoringEvents: { sportsSeasonId: "se.sports_season_id" }, scoringEvents: { sportsSeasonId: "se.sports_season_id" },
participantExpectedValues: { sportsSeasonId: "pev.sports_season_id" }, seasonParticipantExpectedValues: { sportsSeasonId: "pev.sports_season_id" },
})); }));
vi.mock("drizzle-orm", () => ({ vi.mock("drizzle-orm", () => ({
@ -91,16 +91,16 @@ function makeMockDb({
const db: any = { const db: any = {
query: { query: {
sportsSeasons: { findFirst: vi.fn().mockResolvedValue(ss) }, sportsSeasons: { findFirst: vi.fn().mockResolvedValue(ss) },
participants: { findMany: vi.fn().mockResolvedValue(participants) }, seasonParticipants: { findMany: vi.fn().mockResolvedValue(participants) },
scoringEvents: { findMany: vi.fn().mockResolvedValue(scoringEvents) }, scoringEvents: { findMany: vi.fn().mockResolvedValue(scoringEvents) },
participantExpectedValues: { findMany: vi.fn().mockResolvedValue(sourceEvRows) }, seasonParticipantExpectedValues: { findMany: vi.fn().mockResolvedValue(sourceEvRows) },
}, },
insert: vi.fn().mockImplementation((table: object) => { insert: vi.fn().mockImplementation((table: object) => {
let key: string; let key: string;
let returnRows: object[]; let returnRows: object[];
if (table === schema.sportsSeasons) { if (table === schema.sportsSeasons) {
key = "sportsSeasons"; returnRows = [insertedSeason]; key = "sportsSeasons"; returnRows = [insertedSeason];
} else if (table === schema.participants) { } else if (table === schema.seasonParticipants) {
key = "participants"; returnRows = newParticipantRows; key = "participants"; returnRows = newParticipantRows;
} else if (table === schema.scoringEvents) { } else if (table === schema.scoringEvents) {
key = "scoringEvents"; returnRows = []; key = "scoringEvents"; returnRows = [];

View file

@ -13,7 +13,7 @@
*/ */
import { database } from "~/database/context"; import { database } from "~/database/context";
import { cs2MajorStageResults, participants } from "~/database/schema"; import { cs2MajorStageResults, seasonParticipants } from "~/database/schema";
import { eq, and, sql } from "drizzle-orm"; import { eq, and, sql } from "drizzle-orm";
export interface Cs2StageResult { export interface Cs2StageResult {
@ -56,12 +56,12 @@ export async function getCs2StageResultsForEvent(
finalPlacement: cs2MajorStageResults.finalPlacement, finalPlacement: cs2MajorStageResults.finalPlacement,
createdAt: cs2MajorStageResults.createdAt, createdAt: cs2MajorStageResults.createdAt,
updatedAt: cs2MajorStageResults.updatedAt, updatedAt: cs2MajorStageResults.updatedAt,
participantName: participants.name, participantName: seasonParticipants.name,
}) })
.from(cs2MajorStageResults) .from(cs2MajorStageResults)
.innerJoin(participants, eq(cs2MajorStageResults.participantId, participants.id)) .innerJoin(seasonParticipants, eq(cs2MajorStageResults.participantId, seasonParticipants.id))
.where(eq(cs2MajorStageResults.scoringEventId, scoringEventId)) .where(eq(cs2MajorStageResults.scoringEventId, scoringEventId))
.orderBy(cs2MajorStageResults.stageEntry, participants.name); .orderBy(cs2MajorStageResults.stageEntry, seasonParticipants.name);
return rows; return rows;
} }
@ -135,7 +135,7 @@ export async function clearCs2StageAssignments(
/** /**
* Mark teams as eliminated from a specific stage. * Mark teams as eliminated from a specific stage.
* Records stageEliminated and stageEliminatedWins for the specified participants. * Records stageEliminated and stageEliminatedWins for the specified seasonParticipants.
* Teams NOT in eliminatedEntries that are in this stage are implicitly considered * Teams NOT in eliminatedEntries that are in this stage are implicitly considered
* to have advanced (stageEliminated remains null). * to have advanced (stageEliminated remains null).
*/ */
@ -166,7 +166,7 @@ export async function markCs2StageEliminations(
} }
/** /**
* Set final placements for all participants in a CS2 Major event. * Set final placements for all seasonParticipants in a CS2 Major event.
* Called after the Champions Stage is complete. * Called after the Champions Stage is complete.
* placements: Map from participantId to final placement (132) * placements: Map from participantId to final placement (132)
*/ */

View file

@ -78,14 +78,14 @@ export async function getDraftedParticipantsBySportsSeason(
const results = await db const results = await db
.select({ .select({
sportsSeasonId: schema.participants.sportsSeasonId, sportsSeasonId: schema.seasonParticipants.sportsSeasonId,
participantId: schema.participants.id, participantId: schema.seasonParticipants.id,
participantName: schema.participants.name, participantName: schema.seasonParticipants.name,
}) })
.from(schema.draftPicks) .from(schema.draftPicks)
.innerJoin( .innerJoin(
schema.participants, schema.seasonParticipants,
eq(schema.draftPicks.participantId, schema.participants.id) eq(schema.draftPicks.participantId, schema.seasonParticipants.id)
) )
.where( .where(
and( and(
@ -182,10 +182,10 @@ export async function getDraftedParticipantsWithPoints(
// Batch-fetch QP totals for qualifying_points participants // Batch-fetch QP totals for qualifying_points participants
const qpMap = new Map<string, number>(); // participantId → totalQP const qpMap = new Map<string, number>(); // participantId → totalQP
if (qpParticipantIds.size > 0 && qpSeasonIds.size > 0) { if (qpParticipantIds.size > 0 && qpSeasonIds.size > 0) {
const totals = await db.query.participantQualifyingTotals.findMany({ const totals = await db.query.seasonParticipantQualifyingTotals.findMany({
where: and( where: and(
inArray(schema.participantQualifyingTotals.participantId, [...qpParticipantIds]), inArray(schema.seasonParticipantQualifyingTotals.participantId, [...qpParticipantIds]),
inArray(schema.participantQualifyingTotals.sportsSeasonId, [...qpSeasonIds]) inArray(schema.seasonParticipantQualifyingTotals.sportsSeasonId, [...qpSeasonIds])
), ),
columns: { participantId: true, totalQualifyingPoints: true }, columns: { participantId: true, totalQualifyingPoints: true },
}); });
@ -245,19 +245,19 @@ export async function getDraftPicksWithSports(seasonId: string, providedDb?: Ret
id: schema.draftPicks.id, id: schema.draftPicks.id,
teamId: schema.draftPicks.teamId, teamId: schema.draftPicks.teamId,
pickNumber: schema.draftPicks.pickNumber, pickNumber: schema.draftPicks.pickNumber,
participantId: schema.participants.id, participantId: schema.seasonParticipants.id,
participantName: schema.participants.name, participantName: schema.seasonParticipants.name,
sportId: schema.sports.id, sportId: schema.sports.id,
sportName: schema.sports.name, sportName: schema.sports.name,
}) })
.from(schema.draftPicks) .from(schema.draftPicks)
.innerJoin( .innerJoin(
schema.participants, schema.seasonParticipants,
eq(schema.draftPicks.participantId, schema.participants.id) eq(schema.draftPicks.participantId, schema.seasonParticipants.id)
) )
.innerJoin( .innerJoin(
schema.sportsSeasons, schema.sportsSeasons,
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id) eq(schema.seasonParticipants.sportsSeasonId, schema.sportsSeasons.id)
) )
.innerJoin( .innerJoin(
schema.sports, schema.sports,
@ -289,19 +289,19 @@ export async function getTeamDraftPicksWithSports(teamId: string, seasonId: stri
id: schema.draftPicks.id, id: schema.draftPicks.id,
teamId: schema.draftPicks.teamId, teamId: schema.draftPicks.teamId,
pickNumber: schema.draftPicks.pickNumber, pickNumber: schema.draftPicks.pickNumber,
participantId: schema.participants.id, participantId: schema.seasonParticipants.id,
participantName: schema.participants.name, participantName: schema.seasonParticipants.name,
sportId: schema.sports.id, sportId: schema.sports.id,
sportName: schema.sports.name, sportName: schema.sports.name,
}) })
.from(schema.draftPicks) .from(schema.draftPicks)
.innerJoin( .innerJoin(
schema.participants, schema.seasonParticipants,
eq(schema.draftPicks.participantId, schema.participants.id) eq(schema.draftPicks.participantId, schema.seasonParticipants.id)
) )
.innerJoin( .innerJoin(
schema.sportsSeasons, schema.sportsSeasons,
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id) eq(schema.seasonParticipants.sportsSeasonId, schema.sportsSeasons.id)
) )
.innerJoin( .innerJoin(
schema.sports, schema.sports,
@ -338,13 +338,13 @@ export async function getDraftPicksForSeason(seasonId: string) {
pickInRound: schema.draftPicks.pickInRound, pickInRound: schema.draftPicks.pickInRound,
timeUsed: schema.draftPicks.timeUsed, timeUsed: schema.draftPicks.timeUsed,
team: schema.teams, team: schema.teams,
participant: schema.participants, participant: schema.seasonParticipants,
sport: schema.sports, sport: schema.sports,
}) })
.from(schema.draftPicks) .from(schema.draftPicks)
.innerJoin(schema.teams, eq(schema.draftPicks.teamId, schema.teams.id)) .innerJoin(schema.teams, eq(schema.draftPicks.teamId, schema.teams.id))
.innerJoin(schema.participants, eq(schema.draftPicks.participantId, schema.participants.id)) .innerJoin(schema.seasonParticipants, eq(schema.draftPicks.participantId, schema.seasonParticipants.id))
.innerJoin(schema.sportsSeasons, eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)) .innerJoin(schema.sportsSeasons, eq(schema.seasonParticipants.sportsSeasonId, schema.sportsSeasons.id))
.innerJoin(schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id)) .innerJoin(schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id))
.where(eq(schema.draftPicks.seasonId, seasonId)) .where(eq(schema.draftPicks.seasonId, seasonId))
.orderBy(asc(schema.draftPicks.pickNumber)); .orderBy(asc(schema.draftPicks.pickNumber));

View file

@ -5,7 +5,7 @@ import { logger } from "~/lib/logger";
import type { InferSelectModel } from "drizzle-orm"; import type { InferSelectModel } from "drizzle-orm";
import { getTeamQueue, getAllQueuesForSeason } from "./draft-queue"; import { getTeamQueue, getAllQueuesForSeason } from "./draft-queue";
import { isParticipantDrafted, getDraftPicksWithSports, getTeamDraftPicksWithSports } from "./draft-pick"; import { isParticipantDrafted, getDraftPicksWithSports, getTeamDraftPicksWithSports } from "./draft-pick";
import { getParticipantsForSeasonWithSports } from "./participant"; import { getParticipantsForSeasonWithSports } from "./season-participant";
import { getSeasonSportsSimple } from "./season-sport"; import { getSeasonSportsSimple } from "./season-sport";
import { calculateDraftEligibility } from "~/lib/draft-eligibility"; import { calculateDraftEligibility } from "~/lib/draft-eligibility";
import { getSocketIO, scheduleDraftRoomClosure } from "../../server/socket"; import { getSocketIO, scheduleDraftRoomClosure } from "../../server/socket";
@ -118,8 +118,8 @@ export async function autoPickForTeam(
// Get participant details for queue items to check eligibility // Get participant details for queue items to check eligibility
const queueParticipantIds = queue.map((item) => item.participantId); const queueParticipantIds = queue.map((item) => item.participantId);
const queueParticipants = await db.query.participants.findMany({ const queueParticipants = await db.query.seasonParticipants.findMany({
where: inArray(schema.participants.id, queueParticipantIds), where: inArray(schema.seasonParticipants.id, queueParticipantIds),
with: { with: {
sportsSeason: { sportsSeason: {
with: { with: {
@ -264,17 +264,17 @@ export async function getTopAvailableParticipant(
for (const sportsSeasonId of sportsSeasonIds) { for (const sportsSeasonId of sportsSeasonIds) {
let participantQuery = db let participantQuery = db
.select() .select()
.from(schema.participants) .from(schema.seasonParticipants)
.where(eq(schema.participants.sportsSeasonId, sportsSeasonId)); .where(eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonId));
if (draftedIds.length > 0) { if (draftedIds.length > 0) {
participantQuery = db participantQuery = db
.select() .select()
.from(schema.participants) .from(schema.seasonParticipants)
.where( .where(
and( and(
eq(schema.participants.sportsSeasonId, sportsSeasonId), eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonId),
notInArray(schema.participants.id, draftedIds) notInArray(schema.seasonParticipants.id, draftedIds)
) )
); );
} }
@ -299,21 +299,21 @@ export async function getTopAvailableParticipant(
// Single sport season // Single sport season
let query = db let query = db
.select() .select()
.from(schema.participants) .from(schema.seasonParticipants)
.where(eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])) .where(eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonIds[0]))
.orderBy(desc(schema.participants.vorpValue), schema.participants.name); .orderBy(desc(schema.seasonParticipants.vorpValue), schema.seasonParticipants.name);
if (draftedIds.length > 0) { if (draftedIds.length > 0) {
query = db query = db
.select() .select()
.from(schema.participants) .from(schema.seasonParticipants)
.where( .where(
and( and(
eq(schema.participants.sportsSeasonId, sportsSeasonIds[0]), eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonIds[0]),
notInArray(schema.participants.id, draftedIds) notInArray(schema.seasonParticipants.id, draftedIds)
) )
) )
.orderBy(desc(schema.participants.vorpValue), schema.participants.name); .orderBy(desc(schema.seasonParticipants.vorpValue), schema.seasonParticipants.name);
} }
const [topParticipant] = await query; const [topParticipant] = await query;
@ -471,7 +471,7 @@ export async function executeAutoPick(params: {
success: boolean; success: boolean;
error?: string; error?: string;
pick?: InferSelectModel<typeof schema.draftPicks>; pick?: InferSelectModel<typeof schema.draftPicks>;
participant?: InferSelectModel<typeof schema.participants> & { participant?: InferSelectModel<typeof schema.seasonParticipants> & {
sportsSeason: InferSelectModel<typeof schema.sportsSeasons> & { sportsSeason: InferSelectModel<typeof schema.sportsSeasons> & {
sport: InferSelectModel<typeof schema.sports>; sport: InferSelectModel<typeof schema.sports>;
}; };
@ -581,8 +581,8 @@ export async function executeAutoPick(params: {
} }
// Get participant details // Get participant details
const participantToPick = await db.query.participants.findFirst({ const participantToPick = await db.query.seasonParticipants.findFirst({
where: eq(schema.participants.id, participantId), where: eq(schema.seasonParticipants.id, participantId),
with: { with: {
sportsSeason: { sportsSeason: {
with: { with: {

View file

@ -31,7 +31,7 @@ export async function createEventResult(
.insert(schema.eventResults) .insert(schema.eventResults)
.values({ .values({
scoringEventId: data.scoringEventId, scoringEventId: data.scoringEventId,
participantId: data.participantId, seasonParticipantId: data.participantId,
placement: data.placement, placement: data.placement,
qualifyingPointsAwarded: data.qualifyingPointsAwarded?.toString(), qualifyingPointsAwarded: data.qualifyingPointsAwarded?.toString(),
eliminated: data.eliminated, eliminated: data.eliminated,
@ -57,7 +57,7 @@ export async function createEventResultsBulk(
.values( .values(
results.map((r) => ({ results.map((r) => ({
scoringEventId: r.scoringEventId, scoringEventId: r.scoringEventId,
participantId: r.participantId, seasonParticipantId: r.participantId,
placement: r.placement, placement: r.placement,
qualifyingPointsAwarded: r.qualifyingPointsAwarded?.toString(), qualifyingPointsAwarded: r.qualifyingPointsAwarded?.toString(),
eliminated: r.eliminated, eliminated: r.eliminated,
@ -81,7 +81,7 @@ export async function getEventResultById(
return await db.query.eventResults.findFirst({ return await db.query.eventResults.findFirst({
where: eq(schema.eventResults.id, resultId), where: eq(schema.eventResults.id, resultId),
with: { with: {
participant: { seasonParticipant: {
with: { with: {
sportsSeason: { sportsSeason: {
with: { with: {
@ -108,7 +108,7 @@ export async function getEventResults(
where: eq(schema.eventResults.scoringEventId, eventId), where: eq(schema.eventResults.scoringEventId, eventId),
orderBy: schema.eventResults.placement, orderBy: schema.eventResults.placement,
with: { with: {
participant: { seasonParticipant: {
with: { with: {
sportsSeason: { sportsSeason: {
with: { with: {
@ -131,7 +131,7 @@ export async function getParticipantEventResults(
const db = providedDb || database(); const db = providedDb || database();
return await db.query.eventResults.findMany({ return await db.query.eventResults.findMany({
where: eq(schema.eventResults.participantId, participantId), where: eq(schema.eventResults.seasonParticipantId, participantId),
with: { with: {
scoringEvent: true, scoringEvent: true,
}, },
@ -203,7 +203,7 @@ export async function hasParticipantResult(
const result = await db.query.eventResults.findFirst({ const result = await db.query.eventResults.findFirst({
where: and( where: and(
eq(schema.eventResults.scoringEventId, eventId), eq(schema.eventResults.scoringEventId, eventId),
eq(schema.eventResults.participantId, participantId) eq(schema.eventResults.seasonParticipantId, participantId)
), ),
}); });
@ -226,10 +226,10 @@ export async function getEventResultsForParticipants(
return await db.query.eventResults.findMany({ return await db.query.eventResults.findMany({
where: and( where: and(
eq(schema.eventResults.scoringEventId, eventId), eq(schema.eventResults.scoringEventId, eventId),
inArray(schema.eventResults.participantId, participantIds) inArray(schema.eventResults.seasonParticipantId, participantIds)
), ),
with: { with: {
participant: { seasonParticipant: {
with: { with: {
sportsSeason: { sportsSeason: {
with: { with: {

View file

@ -7,7 +7,7 @@
*/ */
import { database } from "~/database/context"; import { database } from "~/database/context";
import { participantGolfSkills, participants } from "~/database/schema"; import { participantGolfSkills, seasonParticipants } from "~/database/schema";
import { eq, sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
export interface GolfSkillsRecord { export interface GolfSkillsRecord {
@ -40,7 +40,7 @@ export interface GolfSkillsInput {
/** /**
* Get all golf skill records for a sports season, joined with participant names. * Get all golf skill records for a sports season, joined with participant names.
* Returns one record per participant (participants with no record are excluded). * Returns one record per participant (seasonParticipants with no record are excluded).
*/ */
export async function getGolfSkillsForSeason( export async function getGolfSkillsForSeason(
sportsSeasonId: string sportsSeasonId: string
@ -58,12 +58,12 @@ export async function getGolfSkillsForSeason(
openChampionshipOdds: participantGolfSkills.openChampionshipOdds, openChampionshipOdds: participantGolfSkills.openChampionshipOdds,
pgaChampionshipOdds: participantGolfSkills.pgaChampionshipOdds, pgaChampionshipOdds: participantGolfSkills.pgaChampionshipOdds,
updatedAt: participantGolfSkills.updatedAt, updatedAt: participantGolfSkills.updatedAt,
participantName: participants.name, participantName: seasonParticipants.name,
}) })
.from(participantGolfSkills) .from(participantGolfSkills)
.innerJoin(participants, eq(participantGolfSkills.participantId, participants.id)) .innerJoin(seasonParticipants, eq(participantGolfSkills.participantId, seasonParticipants.id))
.where(eq(participantGolfSkills.sportsSeasonId, sportsSeasonId)) .where(eq(participantGolfSkills.sportsSeasonId, sportsSeasonId))
.orderBy(participants.name); .orderBy(seasonParticipants.name);
return rows.map((r) => ({ return rows.map((r) => ({
...r, ...r,
@ -94,7 +94,7 @@ export async function getGolfSkillsMap(
} }
/** /**
* Upsert golf skill ratings for a batch of participants. * Upsert golf skill ratings for a batch of seasonParticipants.
* Uses INSERT ON CONFLICT DO UPDATE so all columns are overwritten atomically. * Uses INSERT ON CONFLICT DO UPDATE so all columns are overwritten atomically.
*/ */
export async function batchUpsertGolfSkills( export async function batchUpsertGolfSkills(

View file

@ -334,8 +334,8 @@ export async function getUpcomingGroupStageMatchesForParticipants(
const allParticipantIds = [ const allParticipantIds = [
...new Set(rows.flatMap((r) => [r.participant1Id, r.participant2Id])), ...new Set(rows.flatMap((r) => [r.participant1Id, r.participant2Id])),
]; ];
const participantRows = await db.query.participants.findMany({ const participantRows = await db.query.seasonParticipants.findMany({
where: inArray(schema.participants.id, allParticipantIds), where: inArray(schema.seasonParticipants.id, allParticipantIds),
}); });
const participantMap = new Map(participantRows.map((p) => [p.id, p])); const participantMap = new Map(participantRows.map((p) => [p.id, p]));

View file

@ -1,12 +1,12 @@
/** /**
* Model for Participant Expected Values * Model for Participant Expected Values
* *
* Manages probability distributions and calculated EVs for participants * Manages probability distributions and calculated EVs for seasonParticipants
* in sports seasons. * in sports seasons.
*/ */
import { database } from "~/database/context"; import { database } from "~/database/context";
import { participantExpectedValues, participants } from "~/database/schema"; import { seasonParticipantExpectedValues, seasonParticipants } from "~/database/schema";
import { eq, and, count, sql } from "drizzle-orm"; import { eq, and, count, sql } from "drizzle-orm";
import type { ProbabilityDistribution, ScoringRules } from "~/services/ev-calculator"; import type { ProbabilityDistribution, ScoringRules } from "~/services/ev-calculator";
import { calculateEV, normalizeProbabilities, calculateReplacementLevel, calculateVORP } from "~/services/ev-calculator"; import { calculateEV, normalizeProbabilities, calculateReplacementLevel, calculateVORP } from "~/services/ev-calculator";
@ -53,9 +53,9 @@ export interface UpdateProbabilityInput {
* Recalculate and persist VORP for every participant in a sports season. * Recalculate and persist VORP for every participant in a sports season.
* *
* VORP = participant EV replacement level EV * VORP = participant EV replacement level EV
* Replacement level = average EV of participants ranked 12th14th in this season. * Replacement level = average EV of seasonParticipants ranked 12th14th in this season.
* *
* Call this after any operation that changes EVs for participants in the season. * Call this after any operation that changes EVs for seasonParticipants in the season.
*/ */
export async function syncVorpForSeason(sportsSeasonId: string): Promise<void> { export async function syncVorpForSeason(sportsSeasonId: string): Promise<void> {
const db = database(); const db = database();
@ -72,12 +72,12 @@ export async function syncVorpForSeason(sportsSeasonId: string): Promise<void> {
const now = new Date(); const now = new Date();
// Build a single CASE expression to update all participants in one query // Build a single CASE expression to update all seasonParticipants in one query
// instead of N individual UPDATE statements. // instead of N individual UPDATE statements.
const vorpCaseExpr = sql`CASE ${sql.join( const vorpCaseExpr = sql`CASE ${sql.join(
sorted.map((ev) => { sorted.map((ev) => {
const vorp = calculateVORP(parseFloat(ev.expectedValue), replacementLevel); const vorp = calculateVORP(parseFloat(ev.expectedValue), replacementLevel);
return sql`WHEN ${participants.id} = ${ev.participantId} THEN ${vorp.toFixed(4)}::numeric`; return sql`WHEN ${seasonParticipants.id} = ${ev.participantId} THEN ${vorp.toFixed(4)}::numeric`;
}), }),
sql` ` sql` `
)} END`; )} END`;
@ -85,9 +85,9 @@ export async function syncVorpForSeason(sportsSeasonId: string): Promise<void> {
const ids = sorted.map((ev) => ev.participantId); const ids = sorted.map((ev) => ev.participantId);
await db await db
.update(participants) .update(seasonParticipants)
.set({ vorpValue: vorpCaseExpr, updatedAt: now }) .set({ vorpValue: vorpCaseExpr, updatedAt: now })
.where(sql`${participants.id} = ANY(${sql`ARRAY[${sql.join(ids.map((id) => sql`${id}`), sql`, `)}]::uuid[]`})`); .where(sql`${seasonParticipants.id} = ANY(${sql`ARRAY[${sql.join(ids.map((id) => sql`${id}`), sql`, `)}]::uuid[]`})`);
} }
/** /**
@ -118,11 +118,11 @@ export async function upsertParticipantEV(
// Check if record exists // Check if record exists
const existing = await db const existing = await db
.select() .select()
.from(participantExpectedValues) .from(seasonParticipantExpectedValues)
.where( .where(
and( and(
eq(participantExpectedValues.participantId, participantId), eq(seasonParticipantExpectedValues.participantId, participantId),
eq(participantExpectedValues.sportsSeasonId, sportsSeasonId) eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)
) )
) )
.limit(1); .limit(1);
@ -134,7 +134,7 @@ export async function upsertParticipantEV(
if (existing.length > 0) { if (existing.length > 0) {
// Update existing // Update existing
const updated = await db const updated = await db
.update(participantExpectedValues) .update(seasonParticipantExpectedValues)
.set({ .set({
probFirst: probabilities.probFirst.toString(), probFirst: probabilities.probFirst.toString(),
probSecond: probabilities.probSecond.toString(), probSecond: probabilities.probSecond.toString(),
@ -150,14 +150,14 @@ export async function upsertParticipantEV(
calculatedAt: now, calculatedAt: now,
updatedAt: now, updatedAt: now,
}) })
.where(eq(participantExpectedValues.id, existing[0].id)) .where(eq(seasonParticipantExpectedValues.id, existing[0].id))
.returning(); .returning();
result = updated[0]; result = updated[0];
} else { } else {
// Create new // Create new
const created = await db const created = await db
.insert(participantExpectedValues) .insert(seasonParticipantExpectedValues)
.values({ .values({
participantId, participantId,
sportsSeasonId, sportsSeasonId,
@ -180,13 +180,13 @@ export async function upsertParticipantEV(
result = created[0]; result = created[0];
} }
// Sync calculated EV to participants table for draft room ranking // Sync calculated EV to seasonParticipants table for draft room ranking
await db await db
.update(participants) .update(seasonParticipants)
.set({ expectedValue: expectedValue.toString(), updatedAt: now }) .set({ expectedValue: expectedValue.toString(), updatedAt: now })
.where(eq(participants.id, participantId)); .where(eq(seasonParticipants.id, participantId));
// Recalculate VORP for all participants in this season (replacement level may have shifted) // Recalculate VORP for all seasonParticipants in this season (replacement level may have shifted)
await syncVorpForSeason(sportsSeasonId); await syncVorpForSeason(sportsSeasonId);
return result; return result;
@ -209,7 +209,7 @@ export async function upsertParticipantEVWithNormalization(
export async function countAllParticipantEVs(): Promise<number> { export async function countAllParticipantEVs(): Promise<number> {
const db = database(); const db = database();
const result = await db.select({ value: count() }).from(participantExpectedValues); const result = await db.select({ value: count() }).from(seasonParticipantExpectedValues);
return result[0].value; return result[0].value;
} }
@ -223,11 +223,11 @@ export async function getParticipantEV(
const db = database(); const db = database();
const result = await db const result = await db
.select() .select()
.from(participantExpectedValues) .from(seasonParticipantExpectedValues)
.where( .where(
and( and(
eq(participantExpectedValues.participantId, participantId), eq(seasonParticipantExpectedValues.participantId, participantId),
eq(participantExpectedValues.sportsSeasonId, sportsSeasonId) eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)
) )
) )
.limit(1); .limit(1);
@ -244,8 +244,8 @@ export async function getAllParticipantEVsForSeason(
const db = database(); const db = database();
return db return db
.select() .select()
.from(participantExpectedValues) .from(seasonParticipantExpectedValues)
.where(eq(participantExpectedValues.sportsSeasonId, sportsSeasonId)); .where(eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId));
} }
/** /**
@ -257,21 +257,21 @@ export async function deleteParticipantEV(
): Promise<void> { ): Promise<void> {
const db = database(); const db = database();
await db await db
.delete(participantExpectedValues) .delete(seasonParticipantExpectedValues)
.where( .where(
and( and(
eq(participantExpectedValues.participantId, participantId), eq(seasonParticipantExpectedValues.participantId, participantId),
eq(participantExpectedValues.sportsSeasonId, sportsSeasonId) eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)
) )
); );
// Reset this participant's EV and VORP to 0 (no longer ranked) // Reset this participant's EV and VORP to 0 (no longer ranked)
await db await db
.update(participants) .update(seasonParticipants)
.set({ expectedValue: "0", vorpValue: "0", updatedAt: new Date() }) .set({ expectedValue: "0", vorpValue: "0", updatedAt: new Date() })
.where(eq(participants.id, participantId)); .where(eq(seasonParticipants.id, participantId));
// Recalculate VORP for remaining participants — replacement level may have shifted // Recalculate VORP for remaining seasonParticipants — replacement level may have shifted
await syncVorpForSeason(sportsSeasonId); await syncVorpForSeason(sportsSeasonId);
} }
@ -301,12 +301,12 @@ export async function batchUpsertParticipantEVs(
const expectedValue = calculateEV(probabilities, scoringRules); const expectedValue = calculateEV(probabilities, scoringRules);
const existing = await tx const existing = await tx
.select({ id: participantExpectedValues.id }) .select({ id: seasonParticipantExpectedValues.id })
.from(participantExpectedValues) .from(seasonParticipantExpectedValues)
.where( .where(
and( and(
eq(participantExpectedValues.participantId, participantId), eq(seasonParticipantExpectedValues.participantId, participantId),
eq(participantExpectedValues.sportsSeasonId, sportsSeasonId) eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)
) )
) )
.limit(1); .limit(1);
@ -330,24 +330,24 @@ export async function batchUpsertParticipantEVs(
if (existing.length > 0) { if (existing.length > 0) {
const [updated] = await tx const [updated] = await tx
.update(participantExpectedValues) .update(seasonParticipantExpectedValues)
// Only overwrite sourceOdds if explicitly provided; preserve existing value otherwise // Only overwrite sourceOdds if explicitly provided; preserve existing value otherwise
.set(sourceOdds !== undefined ? { ...baseValues, sourceOdds } : baseValues) .set(sourceOdds !== undefined ? { ...baseValues, sourceOdds } : baseValues)
.where(eq(participantExpectedValues.id, existing[0].id)) .where(eq(seasonParticipantExpectedValues.id, existing[0].id))
.returning(); .returning();
result = updated; result = updated;
} else { } else {
const [created] = await tx const [created] = await tx
.insert(participantExpectedValues) .insert(seasonParticipantExpectedValues)
.values({ participantId, sportsSeasonId, ...baseValues, sourceOdds: sourceOdds ?? null }) .values({ participantId, sportsSeasonId, ...baseValues, sourceOdds: sourceOdds ?? null })
.returning(); .returning();
result = created; result = created;
} }
await tx await tx
.update(participants) .update(seasonParticipants)
.set({ expectedValue: expectedValue.toString(), updatedAt: now }) .set({ expectedValue: expectedValue.toString(), updatedAt: now })
.where(eq(participants.id, participantId)); .where(eq(seasonParticipants.id, participantId));
results.push(result); results.push(result);
} }
@ -361,7 +361,7 @@ export async function batchUpsertParticipantEVs(
} }
/** /**
* Save American odds for a batch of participants without touching probabilities or EV. * Save American odds for a batch of seasonParticipants without touching probabilities or EV.
* Used by the futures-odds admin page to persist odds before running the full simulation. * Used by the futures-odds admin page to persist odds before running the full simulation.
*/ */
export async function batchSaveSourceOdds( export async function batchSaveSourceOdds(
@ -374,24 +374,24 @@ export async function batchSaveSourceOdds(
for (const { participantId, sportsSeasonId, sourceOdds } of inputs) { for (const { participantId, sportsSeasonId, sourceOdds } of inputs) {
const existing = await tx const existing = await tx
.select({ id: participantExpectedValues.id }) .select({ id: seasonParticipantExpectedValues.id })
.from(participantExpectedValues) .from(seasonParticipantExpectedValues)
.where( .where(
and( and(
eq(participantExpectedValues.participantId, participantId), eq(seasonParticipantExpectedValues.participantId, participantId),
eq(participantExpectedValues.sportsSeasonId, sportsSeasonId) eq(seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)
) )
) )
.limit(1); .limit(1);
if (existing.length > 0) { if (existing.length > 0) {
await tx await tx
.update(participantExpectedValues) .update(seasonParticipantExpectedValues)
.set({ sourceOdds, updatedAt: now }) .set({ sourceOdds, updatedAt: now })
.where(eq(participantExpectedValues.id, existing[0].id)); .where(eq(seasonParticipantExpectedValues.id, existing[0].id));
} else { } else {
// Insert a stub record — probabilities/EV will be filled in by the simulator // Insert a stub record — probabilities/EV will be filled in by the simulator
await tx.insert(participantExpectedValues).values({ await tx.insert(seasonParticipantExpectedValues).values({
participantId, participantId,
sportsSeasonId, sportsSeasonId,
probFirst: "0", probFirst: "0",
@ -414,7 +414,7 @@ export async function batchSaveSourceOdds(
} }
/** /**
* Persist raw Elo ratings (and optional world rankings) for a batch of participants. * Persist raw Elo ratings (and optional world rankings) for a batch of seasonParticipants.
* Used by Elo-based simulators like snooker_bracket and darts_bracket. * Used by Elo-based simulators like snooker_bracket and darts_bracket.
* Creates stub records if none exist; does not touch probabilities or EV. * Creates stub records if none exist; does not touch probabilities or EV.
* Uses INSERT ... ON CONFLICT DO UPDATE to avoid N+1 queries. * Uses INSERT ... ON CONFLICT DO UPDATE to avoid N+1 queries.
@ -427,7 +427,7 @@ export async function batchSaveSourceElos(
const now = new Date(); const now = new Date();
await db await db
.insert(participantExpectedValues) .insert(seasonParticipantExpectedValues)
.values( .values(
inputs.map(({ participantId, sportsSeasonId, sourceElo, worldRanking }) => ({ inputs.map(({ participantId, sportsSeasonId, sourceElo, worldRanking }) => ({
participantId, participantId,
@ -449,7 +449,7 @@ export async function batchSaveSourceElos(
})) }))
) )
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [participantExpectedValues.participantId, participantExpectedValues.sportsSeasonId], target: [seasonParticipantExpectedValues.participantId, seasonParticipantExpectedValues.sportsSeasonId],
set: { set: {
sourceElo: sql`excluded.source_elo`, sourceElo: sql`excluded.source_elo`,
worldRanking: sql`excluded.world_ranking`, worldRanking: sql`excluded.world_ranking`,
@ -499,26 +499,26 @@ export async function recalculateEV(
const db = database(); const db = database();
const now = new Date(); const now = new Date();
const updated = await db const updated = await db
.update(participantExpectedValues) .update(seasonParticipantExpectedValues)
.set({ .set({
expectedValue: newEV.toString(), expectedValue: newEV.toString(),
calculatedAt: now, calculatedAt: now,
updatedAt: now, updatedAt: now,
}) })
.where(eq(participantExpectedValues.id, existing.id)) .where(eq(seasonParticipantExpectedValues.id, existing.id))
.returning(); .returning();
// Sync recalculated EV to participants table // Sync recalculated EV to seasonParticipants table
await db await db
.update(participants) .update(seasonParticipants)
.set({ expectedValue: newEV.toString(), updatedAt: now }) .set({ expectedValue: newEV.toString(), updatedAt: now })
.where(eq(participants.id, participantId)); .where(eq(seasonParticipants.id, participantId));
return updated[0]; return updated[0];
} }
/** /**
* Recalculate EVs for all participants in a sports season * Recalculate EVs for all seasonParticipants in a sports season
* Used when scoring rules change * Used when scoring rules change
*/ */
export async function recalculateAllEVsForSeason( export async function recalculateAllEVsForSeason(

View file

@ -2,18 +2,18 @@ import { eq, and } from "drizzle-orm";
import { database } from "~/database/context"; import { database } from "~/database/context";
import * as schema from "~/database/schema"; import * as schema from "~/database/schema";
export type ParticipantResult = typeof schema.participantResults.$inferSelect; export type ParticipantResult = typeof schema.seasonParticipantResults.$inferSelect;
export type ParticipantResultWithParticipant = ParticipantResult & { export type ParticipantResultWithParticipant = ParticipantResult & {
participant: { id: string; name: string } | null; participant: { id: string; name: string } | null;
}; };
export type NewParticipantResult = typeof schema.participantResults.$inferInsert; export type NewParticipantResult = typeof schema.seasonParticipantResults.$inferInsert;
export async function createParticipantResult( export async function createParticipantResult(
data: NewParticipantResult data: NewParticipantResult
): Promise<ParticipantResult> { ): Promise<ParticipantResult> {
const db = database(); const db = database();
const [result] = await db const [result] = await db
.insert(schema.participantResults) .insert(schema.seasonParticipantResults)
.values(data) .values(data)
.returning(); .returning();
return result; return result;
@ -24,7 +24,7 @@ export async function createManyParticipantResults(
): Promise<ParticipantResult[]> { ): Promise<ParticipantResult[]> {
const db = database(); const db = database();
return await db return await db
.insert(schema.participantResults) .insert(schema.seasonParticipantResults)
.values(data) .values(data)
.returning(); .returning();
} }
@ -33,8 +33,8 @@ export async function findParticipantResultById(
id: string id: string
): Promise<ParticipantResult | undefined> { ): Promise<ParticipantResult | undefined> {
const db = database(); const db = database();
return await db.query.participantResults.findFirst({ return await db.query.seasonParticipantResults.findFirst({
where: eq(schema.participantResults.id, id), where: eq(schema.seasonParticipantResults.id, id),
with: { with: {
participant: true, participant: true,
sportsSeason: { sportsSeason: {
@ -50,8 +50,8 @@ export async function findParticipantResultByParticipantId(
participantId: string participantId: string
): Promise<ParticipantResult | undefined> { ): Promise<ParticipantResult | undefined> {
const db = database(); const db = database();
return await db.query.participantResults.findFirst({ return await db.query.seasonParticipantResults.findFirst({
where: eq(schema.participantResults.participantId, participantId), where: eq(schema.seasonParticipantResults.participantId, participantId),
with: { with: {
participant: true, participant: true,
sportsSeason: { sportsSeason: {
@ -67,8 +67,8 @@ export async function findParticipantResultsBySportsSeasonId(
sportsSeasonId: string sportsSeasonId: string
): Promise<ParticipantResultWithParticipant[]> { ): Promise<ParticipantResultWithParticipant[]> {
const db = database(); const db = database();
return await db.query.participantResults.findMany({ return await db.query.seasonParticipantResults.findMany({
where: eq(schema.participantResults.sportsSeasonId, sportsSeasonId), where: eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId),
orderBy: (results, { asc }) => [asc(results.finalPosition)], orderBy: (results, { asc }) => [asc(results.finalPosition)],
with: { with: {
participant: true, participant: true,
@ -82,16 +82,16 @@ export async function updateParticipantResult(
): Promise<ParticipantResult> { ): Promise<ParticipantResult> {
const db = database(); const db = database();
const [result] = await db const [result] = await db
.update(schema.participantResults) .update(schema.seasonParticipantResults)
.set({ ...data, updatedAt: new Date() }) .set({ ...data, updatedAt: new Date() })
.where(eq(schema.participantResults.id, id)) .where(eq(schema.seasonParticipantResults.id, id))
.returning(); .returning();
return result; return result;
} }
export async function deleteParticipantResult(id: string): Promise<void> { export async function deleteParticipantResult(id: string): Promise<void> {
const db = database(); const db = database();
await db.delete(schema.participantResults).where(eq(schema.participantResults.id, id)); await db.delete(schema.seasonParticipantResults).where(eq(schema.seasonParticipantResults.id, id));
} }
export async function deleteParticipantResultsBySportsSeasonId( export async function deleteParticipantResultsBySportsSeasonId(
@ -99,8 +99,8 @@ export async function deleteParticipantResultsBySportsSeasonId(
): Promise<void> { ): Promise<void> {
const db = database(); const db = database();
await db await db
.delete(schema.participantResults) .delete(schema.seasonParticipantResults)
.where(eq(schema.participantResults.sportsSeasonId, sportsSeasonId)); .where(eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId));
} }
/** /**
@ -117,10 +117,10 @@ export async function setParticipantResult(
const db = database(); const db = database();
// Check if result already exists // Check if result already exists
const existing = await db.query.participantResults.findFirst({ const existing = await db.query.seasonParticipantResults.findFirst({
where: and( where: and(
eq(schema.participantResults.participantId, participantId), eq(schema.seasonParticipantResults.participantId, participantId),
eq(schema.participantResults.sportsSeasonId, sportsSeasonId) eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId)
), ),
}); });

View file

@ -144,16 +144,16 @@ export async function getOrCreateParticipantQPTotal(
) { ) {
const db = providedDb || database(); const db = providedDb || database();
let total = await db.query.participantQualifyingTotals.findFirst({ let total = await db.query.seasonParticipantQualifyingTotals.findFirst({
where: and( where: and(
eq(schema.participantQualifyingTotals.participantId, participantId), eq(schema.seasonParticipantQualifyingTotals.participantId, participantId),
eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId) eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId)
), ),
}); });
if (!total) { if (!total) {
const [created] = await db const [created] = await db
.insert(schema.participantQualifyingTotals) .insert(schema.seasonParticipantQualifyingTotals)
.values({ .values({
participantId, participantId,
sportsSeasonId, sportsSeasonId,
@ -184,12 +184,12 @@ export async function addQualifyingPoints(
// Update with new points (do NOT increment eventsScored here) // Update with new points (do NOT increment eventsScored here)
const [updated] = await db const [updated] = await db
.update(schema.participantQualifyingTotals) .update(schema.seasonParticipantQualifyingTotals)
.set({ .set({
totalQualifyingPoints: (parseFloat(total.totalQualifyingPoints) + pointsToAdd).toString(), totalQualifyingPoints: (parseFloat(total.totalQualifyingPoints) + pointsToAdd).toString(),
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(schema.participantQualifyingTotals.id, total.id)) .where(eq(schema.seasonParticipantQualifyingTotals.id, total.id))
.returning(); .returning();
return updated; return updated;
@ -208,7 +208,7 @@ export async function recalculateParticipantQP(
// Get all event results for this participant in this sports season // Get all event results for this participant in this sports season
const eventResults = await db.query.eventResults.findMany({ const eventResults = await db.query.eventResults.findMany({
where: eq(schema.eventResults.participantId, participantId), where: eq(schema.eventResults.seasonParticipantId, participantId),
with: { with: {
scoringEvent: true, scoringEvent: true,
}, },
@ -238,13 +238,13 @@ export async function recalculateParticipantQP(
// Update with recalculated values // Update with recalculated values
await db await db
.update(schema.participantQualifyingTotals) .update(schema.seasonParticipantQualifyingTotals)
.set({ .set({
totalQualifyingPoints: totalQP.toString(), totalQualifyingPoints: totalQP.toString(),
eventsScored, eventsScored,
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(schema.participantQualifyingTotals.id, total.id)); .where(eq(schema.seasonParticipantQualifyingTotals.id, total.id));
return { totalQP, eventsScored }; return { totalQP, eventsScored };
} }
@ -271,9 +271,9 @@ export async function getQPStandings(
) { ) {
const db = providedDb || database(); const db = providedDb || database();
return await db.query.participantQualifyingTotals.findMany({ return await db.query.seasonParticipantQualifyingTotals.findMany({
where: eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId), where: eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId),
orderBy: [desc(sql`CAST(${schema.participantQualifyingTotals.totalQualifyingPoints} AS DECIMAL)`)], orderBy: [desc(sql`CAST(${schema.seasonParticipantQualifyingTotals.totalQualifyingPoints} AS DECIMAL)`)],
with: { with: {
participant: true, participant: true,
}, },
@ -325,9 +325,9 @@ export async function updateFinalRankings(
// Update all rankings // Update all rankings
for (const update of updates) { for (const update of updates) {
await db await db
.update(schema.participantQualifyingTotals) .update(schema.seasonParticipantQualifyingTotals)
.set({ finalRanking: update.finalRanking, updatedAt: new Date() }) .set({ finalRanking: update.finalRanking, updatedAt: new Date() })
.where(eq(schema.participantQualifyingTotals.id, update.id)); .where(eq(schema.seasonParticipantQualifyingTotals.id, update.id));
} }
return updates; return updates;
@ -343,6 +343,6 @@ export async function resetQualifyingPoints(
const db = providedDb || database(); const db = providedDb || database();
await db await db
.delete(schema.participantQualifyingTotals) .delete(schema.seasonParticipantQualifyingTotals)
.where(eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId)); .where(eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId));
} }

View file

@ -201,8 +201,8 @@ export async function processPlayoffEvent(
// PHASE 5.3: Handle teams that didn't make the playoffs // PHASE 5.3: Handle teams that didn't make the playoffs
// Get all participants in the sports season // Get all participants in the sports season
const allParticipants = await db.query.participants.findMany({ const allParticipants = await db.query.seasonParticipants.findMany({
where: eq(schema.participants.sportsSeasonId, event.sportsSeasonId), where: eq(schema.seasonParticipants.sportsSeasonId, event.sportsSeasonId),
}); });
// Get all participant IDs that are in ANY playoff match (winners or losers) // Get all participant IDs that are in ANY playoff match (winners or losers)
@ -222,10 +222,10 @@ export async function processPlayoffEvent(
for (const participant of allParticipants) { for (const participant of allParticipants) {
if (!participantsInBracket.has(participant.id)) { if (!participantsInBracket.has(participant.id)) {
// Check if they already have a result (don't overwrite) // Check if they already have a result (don't overwrite)
const existingResult = await db.query.participantResults.findFirst({ const existingResult = await db.query.seasonParticipantResults.findFirst({
where: and( where: and(
eq(schema.participantResults.participantId, participant.id), eq(schema.seasonParticipantResults.participantId, participant.id),
eq(schema.participantResults.sportsSeasonId, event.sportsSeasonId) eq(schema.seasonParticipantResults.sportsSeasonId, event.sportsSeasonId)
), ),
}); });
@ -506,10 +506,10 @@ async function upsertParticipantResult(
db: ReturnType<typeof database>, db: ReturnType<typeof database>,
isPartialScore = false isPartialScore = false
): Promise<number | null> { ): Promise<number | null> {
const existing = await db.query.participantResults.findFirst({ const existing = await db.query.seasonParticipantResults.findFirst({
where: and( where: and(
eq(schema.participantResults.participantId, participantId), eq(schema.seasonParticipantResults.participantId, participantId),
eq(schema.participantResults.sportsSeasonId, sportsSeasonId) eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId)
), ),
}); });
@ -520,16 +520,16 @@ async function upsertParticipantResult(
if (!existing.isPartialScore && isPartialScore) return null; if (!existing.isPartialScore && isPartialScore) return null;
await db await db
.update(schema.participantResults) .update(schema.seasonParticipantResults)
.set({ .set({
finalPosition, finalPosition,
isPartialScore, isPartialScore,
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(schema.participantResults.id, existing.id)); .where(eq(schema.seasonParticipantResults.id, existing.id));
return existing.finalPosition ?? 0; return existing.finalPosition ?? 0;
} else { } else {
await db.insert(schema.participantResults).values({ await db.insert(schema.seasonParticipantResults).values({
participantId, participantId,
sportsSeasonId, sportsSeasonId,
finalPosition, finalPosition,
@ -651,7 +651,7 @@ export async function processQualifyingEvent(
// Recalculate totals for all participants from scratch // Recalculate totals for all participants from scratch
// This is the source of truth - sums all their event_results // This is the source of truth - sums all their event_results
const participantIds = new Set(results.map(r => r.participantId)); const participantIds = new Set(results.map(r => r.seasonParticipantId));
for (const participantId of participantIds) { for (const participantId of participantIds) {
await recalculateParticipantQP(participantId, event.sportsSeasonId, db); await recalculateParticipantQP(participantId, event.sportsSeasonId, db);
} }
@ -752,10 +752,10 @@ export async function finalizeQualifyingPoints(
if (currentPlacement <= standings.length) { if (currentPlacement <= standings.length) {
for (let i = currentPlacement - 1; i < standings.length; i++) { for (let i = currentPlacement - 1; i < standings.length; i++) {
const standing = standings[i]; const standing = standings[i];
const hasResult = await db.query.participantResults.findFirst({ const hasResult = await db.query.seasonParticipantResults.findFirst({
where: and( where: and(
eq(schema.participantResults.participantId, standing.participantId), eq(schema.seasonParticipantResults.participantId, standing.participantId),
eq(schema.participantResults.sportsSeasonId, sportsSeasonId) eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId)
), ),
}); });
@ -1388,11 +1388,11 @@ export async function recalculateAffectedLeagues(
.filter((id): id is string => id !== null); .filter((id): id is string => id !== null);
const finalizedLoserIds = new Set<string>(); const finalizedLoserIds = new Set<string>();
if (loserParticipantIds.length > 0) { if (loserParticipantIds.length > 0) {
const loserResults = await db.query.participantResults.findMany({ const loserResults = await db.query.seasonParticipantResults.findMany({
where: and( where: and(
inArray(schema.participantResults.participantId, loserParticipantIds), inArray(schema.seasonParticipantResults.participantId, loserParticipantIds),
eq(schema.participantResults.sportsSeasonId, sportsSeasonId), eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId),
eq(schema.participantResults.isPartialScore, false) eq(schema.seasonParticipantResults.isPartialScore, false)
), ),
}); });
for (const r of loserResults) finalizedLoserIds.add(r.participantId); for (const r of loserResults) finalizedLoserIds.add(r.participantId);

View file

@ -104,7 +104,7 @@ export async function getScoringEventsForSportsSeason(
with: { with: {
eventResults: { eventResults: {
with: { with: {
participant: true, seasonParticipant: true,
}, },
}, },
}, },
@ -224,7 +224,7 @@ export async function deleteScoringEvent(
const results = await db.query.eventResults.findMany({ const results = await db.query.eventResults.findMany({
where: eq(schema.eventResults.scoringEventId, eventId), where: eq(schema.eventResults.scoringEventId, eventId),
}); });
affectedParticipantIds = results.map((r) => r.participantId); affectedParticipantIds = results.map((r) => r.seasonParticipantId);
wasQPProcessed = results.some( wasQPProcessed = results.some(
(r) => r.qualifyingPointsAwarded && parseFloat(r.qualifyingPointsAwarded) > 0 (r) => r.qualifyingPointsAwarded && parseFloat(r.qualifyingPointsAwarded) > 0
); );
@ -233,8 +233,8 @@ export async function deleteScoringEvent(
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
if (event.eventType === "playoff_game") { if (event.eventType === "playoff_game") {
await tx await tx
.delete(schema.participantResults) .delete(schema.seasonParticipantResults)
.where(eq(schema.participantResults.sportsSeasonId, event.sportsSeasonId)); .where(eq(schema.seasonParticipantResults.sportsSeasonId, event.sportsSeasonId));
} }
// Cascades: playoffMatches, eventResults, tournamentGroups/Members // Cascades: playoffMatches, eventResults, tournamentGroups/Members
await tx.delete(schema.scoringEvents).where(eq(schema.scoringEvents.id, eventId)); await tx.delete(schema.scoringEvents).where(eq(schema.scoringEvents.id, eventId));

View file

@ -211,8 +211,8 @@ export async function cloneSportsSeason(
}; };
// Read all source data before writing anything // Read all source data before writing anything
const sourceParticipants = await db.query.participants.findMany({ const sourceParticipants = await db.query.seasonParticipants.findMany({
where: eq(schema.participants.sportsSeasonId, sourceId), where: eq(schema.seasonParticipants.sportsSeasonId, sourceId),
}); });
const sourceEvents = await db.query.scoringEvents.findMany({ const sourceEvents = await db.query.scoringEvents.findMany({
@ -225,8 +225,8 @@ export async function cloneSportsSeason(
: null; : null;
// Read source futures odds and Elo ratings // Read source futures odds and Elo ratings
const sourceEvRows = await db.query.participantExpectedValues.findMany({ const sourceEvRows = await db.query.seasonParticipantExpectedValues.findMany({
where: eq(schema.participantExpectedValues.sportsSeasonId, sourceId), where: eq(schema.seasonParticipantExpectedValues.sportsSeasonId, sourceId),
}); });
// Build lookup: (externalId ?? name) → source EV, only for rows that have // Build lookup: (externalId ?? name) → source EV, only for rows that have
@ -251,7 +251,7 @@ export async function cloneSportsSeason(
const newParticipants = const newParticipants =
sourceParticipants.length > 0 sourceParticipants.length > 0
? await tx ? await tx
.insert(schema.participants) .insert(schema.seasonParticipants)
.values( .values(
sourceParticipants.map((p: typeof sourceParticipants[number]) => ({ sourceParticipants.map((p: typeof sourceParticipants[number]) => ({
sportsSeasonId: newSeason.id, sportsSeasonId: newSeason.id,
@ -288,7 +288,7 @@ export async function cloneSportsSeason(
.filter((r): r is NonNullable<typeof r> => r !== null); .filter((r): r is NonNullable<typeof r> => r !== null);
if (evRows.length > 0) { if (evRows.length > 0) {
await tx.insert(schema.participantExpectedValues).values(evRows); await tx.insert(schema.seasonParticipantExpectedValues).values(evRows);
} }
} }

View file

@ -7,7 +7,7 @@
*/ */
import { database } from "~/database/context"; import { database } from "~/database/context";
import { participantSurfaceElos, participants } from "~/database/schema"; import { 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";
@ -38,7 +38,7 @@ export interface SurfaceEloInput {
/** /**
* Get all surface Elo records for a sports season, joined with participant names. * Get all surface Elo records for a sports season, joined with participant names.
* Returns one record per participant (participants with no Elo record are excluded). * Returns one record per participant (seasonParticipants with no Elo record are excluded).
*/ */
export async function getSurfaceElosForSeason( export async function getSurfaceElosForSeason(
sportsSeasonId: string sportsSeasonId: string
@ -46,26 +46,26 @@ export async function getSurfaceElosForSeason(
const db = database(); const db = database();
const rows = await db const rows = await db
.select({ .select({
id: participantSurfaceElos.id, id: seasonParticipantSurfaceElos.id,
participantId: participantSurfaceElos.participantId, participantId: seasonParticipantSurfaceElos.participantId,
sportsSeasonId: participantSurfaceElos.sportsSeasonId, sportsSeasonId: seasonParticipantSurfaceElos.sportsSeasonId,
worldRanking: participantSurfaceElos.worldRanking, worldRanking: seasonParticipantSurfaceElos.worldRanking,
eloHard: participantSurfaceElos.eloHard, eloHard: seasonParticipantSurfaceElos.eloHard,
eloClay: participantSurfaceElos.eloClay, eloClay: seasonParticipantSurfaceElos.eloClay,
eloGrass: participantSurfaceElos.eloGrass, eloGrass: seasonParticipantSurfaceElos.eloGrass,
updatedAt: participantSurfaceElos.updatedAt, updatedAt: seasonParticipantSurfaceElos.updatedAt,
participantName: participants.name, participantName: seasonParticipants.name,
}) })
.from(participantSurfaceElos) .from(seasonParticipantSurfaceElos)
.innerJoin(participants, eq(participantSurfaceElos.participantId, participants.id)) .innerJoin(seasonParticipants, eq(seasonParticipantSurfaceElos.participantId, seasonParticipants.id))
.where(eq(participantSurfaceElos.sportsSeasonId, sportsSeasonId)) .where(eq(seasonParticipantSurfaceElos.sportsSeasonId, sportsSeasonId))
.orderBy(participants.name); .orderBy(seasonParticipants.name);
return rows; return rows;
} }
/** /**
* Upsert surface Elo ratings for a batch of participants. * Upsert surface Elo ratings for a batch of seasonParticipants.
* Uses INSERT ... ON CONFLICT DO UPDATE so all three surface columns are * Uses INSERT ... ON CONFLICT DO UPDATE so all three surface columns are
* overwritten atomically the admin always submits all three values. * overwritten atomically the admin always submits all three values.
*/ */
@ -77,7 +77,7 @@ export async function batchUpsertSurfaceElos(
const now = new Date(); const now = new Date();
await db await db
.insert(participantSurfaceElos) .insert(seasonParticipantSurfaceElos)
.values( .values(
inputs.map(({ participantId, sportsSeasonId, worldRanking, eloHard, eloClay, eloGrass }) => ({ inputs.map(({ participantId, sportsSeasonId, worldRanking, eloHard, eloClay, eloGrass }) => ({
participantId, participantId,
@ -90,7 +90,7 @@ export async function batchUpsertSurfaceElos(
})) }))
) )
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [participantSurfaceElos.participantId, participantSurfaceElos.sportsSeasonId], target: [seasonParticipantSurfaceElos.participantId, seasonParticipantSurfaceElos.sportsSeasonId],
set: { set: {
worldRanking: sql`excluded.world_ranking`, worldRanking: sql`excluded.world_ranking`,
eloHard: sql`excluded.elo_hard`, eloHard: sql`excluded.elo_hard`,
@ -111,14 +111,14 @@ export async function getSurfaceEloMap(
const db = database(); const db = database();
const rows = await db const rows = await db
.select({ .select({
participantId: participantSurfaceElos.participantId, participantId: seasonParticipantSurfaceElos.participantId,
worldRanking: participantSurfaceElos.worldRanking, worldRanking: seasonParticipantSurfaceElos.worldRanking,
eloHard: participantSurfaceElos.eloHard, eloHard: seasonParticipantSurfaceElos.eloHard,
eloClay: participantSurfaceElos.eloClay, eloClay: seasonParticipantSurfaceElos.eloClay,
eloGrass: participantSurfaceElos.eloGrass, eloGrass: seasonParticipantSurfaceElos.eloGrass,
}) })
.from(participantSurfaceElos) .from(seasonParticipantSurfaceElos)
.where(eq(participantSurfaceElos.sportsSeasonId, sportsSeasonId)); .where(eq(seasonParticipantSurfaceElos.sportsSeasonId, sportsSeasonId));
return new Map(rows.map((r) => [r.participantId, { return new Map(rows.map((r) => [r.participantId, {
worldRanking: r.worldRanking, worldRanking: r.worldRanking,

View file

@ -230,8 +230,8 @@ export async function getRecentTeamScoreEvents(
const participantNameById = new Map<string, string>(); const participantNameById = new Map<string, string>();
if (allParticipantIds.length > 0) { if (allParticipantIds.length > 0) {
const participantRows = await db.query.participants.findMany({ const participantRows = await db.query.seasonParticipants.findMany({
where: inArray(schema.participants.id, allParticipantIds), where: inArray(schema.seasonParticipants.id, allParticipantIds),
columns: { id: true, name: true }, columns: { id: true, name: true },
}); });
for (const p of participantRows) { for (const p of participantRows) {