feat: Refactor autopick logic to use unified executeAutoPick function and enhance eligibility checks

This commit is contained in:
Chris Parsons 2025-10-25 10:14:36 -07:00
parent 32980428f2
commit 714ab0f484
5 changed files with 396 additions and 407 deletions

View file

@ -9,7 +9,7 @@ import { calculateDraftEligibility } from "~/lib/draft-eligibility";
/**
* Auto-pick for a team when their timer runs out
* 1. Check queue - pick first eligible item if available
* 1. Check queue - pick first eligible item if available (cleans up ineligible items)
* 2. If queue empty, pick highest EV participant not drafted from eligible sports
*
* Updated to respect Omni league draft eligibility rules
@ -39,11 +39,18 @@ export async function autoPickForTeam(
allTeams
);
console.log(
`[AutoPick] Team ${teamId} eligible sports:`,
Array.from(eligibility.eligibleSportIds)
);
// Check queue first - filter by eligible sports
const queue = await getTeamQueue(teamId);
if (queue.length > 0) {
// Get participant details for queue items to check sport eligibility
console.log(`[AutoPick] Team ${teamId} has ${queue.length} items in queue`);
// Get participant details for queue items to check eligibility
const queueParticipantIds = queue.map((item) => item.participantId);
const queueParticipants = await db.query.participants.findMany({
where: inArray(schema.participants.id, queueParticipantIds),
@ -56,23 +63,67 @@ export async function autoPickForTeam(
},
});
// Try queue items in order, checking both drafted status and eligibility
const ineligibleQueueItemIds: string[] = [];
// Try queue items in order, checking both drafted status and sport eligibility
for (const item of queue) {
const participant = queueParticipants.find((p) => p.id === item.participantId);
if (!participant) continue;
if (!participant) {
console.log(`[AutoPick] Queue item ${item.id} - participant not found, will remove`);
ineligibleQueueItemIds.push(item.id);
continue;
}
const sportId = participant.sportsSeason.sport.id;
const isEligible = eligibility.eligibleSportIds.has(sportId);
const isDrafted = await isParticipantDrafted(seasonId, item.participantId);
if (!isDrafted && isEligible) {
if (isDrafted) {
console.log(
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - already drafted, will remove`
);
ineligibleQueueItemIds.push(item.id);
continue;
}
if (!isEligible) {
console.log(
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - not eligible for this team, will remove`
);
ineligibleQueueItemIds.push(item.id);
continue;
}
// Found a valid pick from queue
console.log(
`[AutoPick] Selecting from queue: ${participant.name} (${participant.sportsSeason.sport.name})`
);
// Clean up ineligible items from queue before returning
if (ineligibleQueueItemIds.length > 0) {
await db
.delete(schema.draftQueue)
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
console.log(`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue`);
}
return item.participantId;
}
// All queue items were ineligible or drafted - clean them up
if (ineligibleQueueItemIds.length > 0) {
await db
.delete(schema.draftQueue)
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
console.log(
`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue (all items were invalid)`
);
}
}
// Queue is empty or all queued players drafted/ineligible
// Pick highest EV available from eligible sports
console.log(`[AutoPick] No valid queue items, selecting highest EV from eligible sports`);
return await getTopAvailableParticipant(seasonId, eligibility.eligibleSportIds);
}
@ -230,3 +281,305 @@ export function getTeamForPick(
const { teamIndex } = calculatePickInfo(pickNumber, teamCount);
return sortedOrder[teamIndex]?.teamId || null;
}
/**
* Execute an autopick for a team - unified function for both commissioner-forced and timer-based autopicks
*
* Selection Logic:
* 1. Uses the team's draft queue first (prioritizes manager's preferences)
* 2. Validates each queued participant for:
* - Not already drafted
* - Eligible based on draft rules (sport eligibility, flex spots, etc.)
* 3. Automatically removes ineligible participants from queue
* 4. If queue is empty or all items are ineligible, selects highest EV participant from eligible sports
*
* This ensures autopicks ALWAYS respect draft eligibility rules and cannot make illegal selections.
*
* @param params.seasonId - The season ID
* @param params.teamId - The team making the pick
* @param params.pickNumber - The current pick number
* @param params.triggeredBy - Who/what triggered the autopick ("commissioner" or "timer")
* @param params.commissionerUserId - User ID of commissioner (required if triggeredBy is "commissioner")
* @param params.autodraftSettings - Autodraft settings (used for timer-based picks)
* @param params.db - Database instance (optional, will use database() if not provided)
* @returns Result object with success status and pick data
*/
export async function executeAutoPick(params: {
seasonId: string;
teamId: string;
pickNumber: number;
triggeredBy: "commissioner" | "timer";
commissionerUserId?: string;
autodraftSettings?: any;
db?: ReturnType<typeof database>;
}): Promise<{
success: boolean;
error?: string;
pick?: any;
participant?: any;
nextPickNumber?: number;
isDraftComplete?: boolean;
}> {
const {
seasonId,
teamId,
pickNumber,
triggeredBy,
commissionerUserId,
autodraftSettings,
db: providedDb,
} = params;
const db = providedDb || database();
try {
// Race condition protection - check if pick already made
const existingPick = await db.query.draftPicks.findFirst({
where: and(
eq(schema.draftPicks.seasonId, seasonId),
eq(schema.draftPicks.pickNumber, pickNumber)
),
});
if (existingPick) {
console.log(`[AutoPick] Pick ${pickNumber} already made, skipping`);
return {
success: false,
error: "Pick already made",
};
}
// Get season details
const season = await db.query.seasons.findFirst({
where: eq(schema.seasons.id, seasonId),
});
if (!season) {
return {
success: false,
error: "Season not found",
};
}
// Get draft slots to calculate round/pickInRound and get all team IDs
const draftSlots = await db.query.draftSlots.findMany({
where: eq(schema.draftSlots.seasonId, seasonId),
orderBy: schema.draftSlots.draftOrder,
with: {
team: true,
},
});
const totalTeams = draftSlots.length;
if (totalTeams === 0) {
return {
success: false,
error: "No draft slots found",
};
}
const allTeamIds = draftSlots.map((slot) => slot.teamId);
// Use autoPickForTeam to select participant (respects eligibility and queue)
const participantId = await autoPickForTeam(
seasonId,
teamId,
season.draftRounds,
allTeamIds
);
if (!participantId) {
return {
success: false,
error: "No eligible participants available to pick",
};
}
// Get participant details
const participantToPick = await db.query.participants.findFirst({
where: eq(schema.participants.id, participantId),
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
});
if (!participantToPick) {
return {
success: false,
error: "Participant not found",
};
}
// Calculate round and pickInRound with snake draft logic
const currentRound = Math.ceil(pickNumber / totalTeams);
const isEvenRound = currentRound % 2 === 0;
let pickInRound = ((pickNumber - 1) % totalTeams) + 1;
// Apply snake draft reversal for even rounds
if (isEvenRound) {
pickInRound = totalTeams - pickInRound + 1;
}
// Determine pickedByUserId based on trigger
const pickedByUserId = triggeredBy === "commissioner"
? (commissionerUserId || "")
: "";
// Create the draft pick
const [draftPick] = await db
.insert(schema.draftPicks)
.values({
seasonId,
teamId,
participantId: participantToPick.id,
pickNumber,
round: currentRound,
pickInRound,
pickedByUserId,
pickedByType: "auto",
timeUsed: triggeredBy === "timer" ? (season.draftInitialTime || 120) : undefined,
})
.returning();
console.log(
`[AutoPick] Pick created - ${triggeredBy} triggered - Pick ${pickNumber} - Participant ${participantId}`
);
// Update season's current pick number and check if draft complete
const nextPickNumber = pickNumber + 1;
const totalPicks = totalTeams * season.draftRounds;
const isDraftComplete = nextPickNumber > totalPicks;
await db
.update(schema.seasons)
.set({
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
status: isDraftComplete ? "active" : season.status,
})
.where(eq(schema.seasons.id, seasonId));
// Add increment to the team that just picked
const currentTimer = await db.query.draftTimers.findFirst({
where: and(
eq(schema.draftTimers.seasonId, seasonId),
eq(schema.draftTimers.teamId, teamId)
),
});
if (currentTimer) {
const incrementTime = season.draftIncrementTime || 30;
const newTimeRemaining = currentTimer.timeRemaining + incrementTime;
await db
.update(schema.draftTimers)
.set({
timeRemaining: newTimeRemaining,
updatedAt: new Date(),
})
.where(eq(schema.draftTimers.id, currentTimer.id));
// Emit timer update to all clients
try {
const io = (global as any).__socketIO;
if (io) {
io.to(`draft-${seasonId}`).emit("timer-update", {
seasonId,
teamId,
timeRemaining: newTimeRemaining,
currentPickNumber: pickNumber,
});
}
} catch (error) {
console.error("[AutoPick] Socket.IO timer-update error:", error);
}
}
// Remove from ALL team queues in this season (participant is now drafted)
await db
.delete(schema.draftQueue)
.where(
and(
eq(schema.draftQueue.seasonId, seasonId),
eq(schema.draftQueue.participantId, participantToPick.id)
)
);
// Handle autodraft settings for timer-based picks with "next_pick" mode
if (triggeredBy === "timer" && autodraftSettings?.isEnabled && autodraftSettings.mode === "next_pick") {
console.log(`[AutoPick] Disabling autodraft for team ${teamId} after next_pick`);
await db
.update(schema.autodraftSettings)
.set({
isEnabled: false,
updatedAt: new Date(),
})
.where(eq(schema.autodraftSettings.id, autodraftSettings.id));
// Emit autodraft-updated event
try {
const io = (global as any).__socketIO;
if (io) {
io.to(`draft-${seasonId}`).emit("autodraft-updated", {
teamId,
isEnabled: false,
mode: autodraftSettings.mode,
});
}
} catch (error) {
console.error("[AutoPick] Socket.IO autodraft-updated error:", error);
}
}
// Emit socket events
try {
const io = (global as any).__socketIO;
if (io) {
const team = draftSlots.find((slot) => slot.team.id === teamId)?.team;
// Emit participant-removed-from-queues event
io.to(`draft-${seasonId}`).emit("participant-removed-from-queues", {
participantId: participantToPick.id,
});
// Emit pick-made event
io.to(`draft-${seasonId}`).emit("pick-made", {
pick: {
...draftPick,
team,
participant: {
...participantToPick,
sport: participantToPick.sportsSeason.sport,
},
sport: participantToPick.sportsSeason.sport,
},
nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
isDraftComplete,
});
// Emit draft-completed event if applicable
if (isDraftComplete) {
io.to(`draft-${seasonId}`).emit("draft-completed");
}
}
} catch (error) {
console.error("[AutoPick] Socket.IO events error:", error);
}
return {
success: true,
pick: draftPick,
participant: participantToPick,
nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
isDraftComplete,
};
} catch (error) {
console.error("[AutoPick] Error in executeAutoPick:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}

View file

@ -1,8 +1,8 @@
import { getAuth } from "@clerk/react-router/server";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and } from "drizzle-orm";
import { autoPickForTeam } from "~/models/draft-utils";
import { eq } from "drizzle-orm";
import { executeAutoPick } from "~/models/draft-utils";
export async function action(args: any) {
const { request } = args;
@ -24,7 +24,7 @@ export async function action(args: any) {
const db = database();
// Get season details
// Get season details to verify commissioner permissions
const season = await db.query.seasons.findFirst({
where: eq(schema.seasons.id, seasonId),
with: {
@ -41,166 +41,25 @@ export async function action(args: any) {
return Response.json({ error: "Only commissioner can force autopick" }, { status: 403 });
}
// Get draft slots to pass team IDs
const draftSlots = await db.query.draftSlots.findMany({
where: eq(schema.draftSlots.seasonId, seasonId),
orderBy: schema.draftSlots.draftOrder,
with: {
team: true,
},
});
const allTeamIds = draftSlots.map((slot) => slot.teamId);
// Use autoPickForTeam which now respects eligibility
const participantId = await autoPickForTeam(
// Execute the autopick using the unified function
const result = await executeAutoPick({
seasonId,
teamId,
season.draftRounds,
allTeamIds
);
if (!participantId) {
return Response.json({ error: "No eligible participants available to pick" }, { status: 400 });
}
// Get participant details
const participantToPick = await db.query.participants.findFirst({
where: eq(schema.participants.id, participantId),
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
});
if (!participantToPick) {
return Response.json({ error: "Participant not found" }, { status: 404 });
}
// Calculate round and pickInRound
const totalTeams = draftSlots.length;
const currentRound = Math.ceil(pickNumber / totalTeams);
const isEvenRound = currentRound % 2 === 0;
let pickInRound = ((pickNumber - 1) % totalTeams) + 1;
if (isEvenRound) {
pickInRound = totalTeams - pickInRound + 1;
}
// Create the draft pick
const [draftPick] = await db
.insert(schema.draftPicks)
.values({
seasonId,
teamId,
participantId: participantToPick.id,
pickNumber,
round: currentRound,
pickInRound,
pickedByUserId: userId,
pickedByType: "auto",
})
.returning();
// Update season's current pick number
const nextPickNumber = pickNumber + 1;
const totalPicks = totalTeams * season.draftRounds;
const isDraftComplete = nextPickNumber > totalPicks;
await db
.update(schema.seasons)
.set({
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
status: isDraftComplete ? "active" : season.status,
})
.where(eq(schema.seasons.id, seasonId));
// Add increment to the team that just picked
const currentTimer = await db.query.draftTimers.findFirst({
where: and(
eq(schema.draftTimers.seasonId, seasonId),
eq(schema.draftTimers.teamId, teamId)
),
triggeredBy: "commissioner",
commissionerUserId: userId,
db,
});
if (currentTimer) {
const incrementTime = season.draftIncrementTime || 30;
const newTimeRemaining = currentTimer.timeRemaining + incrementTime;
await db
.update(schema.draftTimers)
.set({
timeRemaining: newTimeRemaining,
updatedAt: new Date(),
})
.where(eq(schema.draftTimers.id, currentTimer.id));
// Emit timer update to all clients
try {
const io = (global as any).__socketIO;
io.to(`draft-${seasonId}`).emit("timer-update", {
seasonId,
teamId,
timeRemaining: newTimeRemaining,
currentPickNumber: pickNumber,
});
} catch (error) {
console.error("Socket.IO timer-update error:", error);
}
}
// Remove from ALL team queues in this season (participant is now drafted)
await db
.delete(schema.draftQueue)
.where(
and(
eq(schema.draftQueue.seasonId, seasonId),
eq(schema.draftQueue.participantId, participantToPick.id)
)
);
// Notify all clients that this participant was removed from queues
try {
const io = (global as any).__socketIO;
io.to(`draft-${seasonId}`).emit("participant-removed-from-queues", {
participantId: participantToPick.id,
});
} catch (error) {
console.error("Socket.IO participant-removed-from-queues error:", error);
}
// Emit socket event
try {
const io = (global as any).__socketIO;
const team = draftSlots.find((slot) => slot.team.id === teamId)?.team;
io.to(`draft-${seasonId}`).emit("pick-made", {
pick: {
...draftPick,
team,
participant: {
...participantToPick,
sport: participantToPick.sportsSeason.sport,
},
sport: participantToPick.sportsSeason.sport,
},
nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
isDraftComplete,
});
if (isDraftComplete) {
io.to(`draft-${seasonId}`).emit("draft-completed");
}
} catch (error) {
console.error("Socket.IO error:", error);
if (!result.success) {
return Response.json({ error: result.error }, { status: 400 });
}
return Response.json({
success: true,
pick: draftPick,
participant: participantToPick,
nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
isDraftComplete,
pick: result.pick,
participant: result.participant,
nextPickNumber: result.nextPickNumber,
isDraftComplete: result.isDraftComplete,
});
}

View file

@ -1,8 +1,9 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "~/database/schema";
import { eq, and, desc, asc, inArray, notInArray } from "drizzle-orm";
import { eq, and, asc } from "drizzle-orm";
import { getSocketIO } from "./socket";
import { executeAutoPick } from "~/models/draft-utils";
// Create a dedicated database connection for the timer
const connectionString = process.env.DATABASE_URL;
@ -186,6 +187,7 @@ async function updateDraftTimers(): Promise<void> {
/**
* Trigger an automatic pick when timer expires
* Uses the unified executeAutoPick function
*/
async function triggerAutoPick(
seasonId: string,
@ -194,254 +196,25 @@ async function triggerAutoPick(
autodraftSettings: any | null
): Promise<void> {
try {
const io = getSocketIO();
// Get season details
const season = await db.query.seasons.findFirst({
where: eq(schema.seasons.id, seasonId),
});
if (!season) {
console.error(`[Timer] Season ${seasonId} not found for auto-pick`);
return;
}
// Check if pick already made (race condition protection)
const existingPick = await db.query.draftPicks.findFirst({
where: and(
eq(schema.draftPicks.seasonId, seasonId),
eq(schema.draftPicks.pickNumber, pickNumber)
),
});
if (existingPick) {
console.log(`[Timer] Pick ${pickNumber} already made, skipping auto-pick`);
return;
}
// Get team's queue
const queueItems = await db.query.draftQueue.findMany({
where: and(
eq(schema.draftQueue.seasonId, seasonId),
eq(schema.draftQueue.teamId, teamId)
),
orderBy: asc(schema.draftQueue.queuePosition),
});
let participantId: string | null = null;
// Try to pick from queue first
if (queueItems.length > 0) {
// Get already drafted participant IDs
const draftedPicks = await db.query.draftPicks.findMany({
where: eq(schema.draftPicks.seasonId, seasonId),
});
const draftedParticipantIds = draftedPicks.map((p: any) => p.participantId);
// Find first queued participant that hasn't been drafted
const availableQueueItem = queueItems.find(
(item: any) => !draftedParticipantIds.includes(item.participantId)
console.log(
`[Timer] Triggering auto-pick for team ${teamId} in season ${seasonId}, pick ${pickNumber}`
);
if (availableQueueItem) {
participantId = availableQueueItem.participantId;
console.log(`[Timer] Auto-picking from queue: ${participantId}`);
}
}
// If no valid queue item, pick highest EV available participant
if (!participantId) {
// Get sports season IDs for this season
const seasonSports = await db.query.seasonTemplateSports.findMany({
where: eq(schema.seasonTemplateSports.templateId, season.templateId!),
});
const sportsSeasonIds = seasonSports.map((ss: any) => ss.sportsSeasonId);
if (sportsSeasonIds.length === 0) {
console.error(`[Timer] No sports seasons found for season ${seasonId}`);
return;
}
// Get already drafted participant IDs
const draftedPicks = await db.query.draftPicks.findMany({
where: eq(schema.draftPicks.seasonId, seasonId),
});
const draftedParticipantIds = draftedPicks.map((p: any) => p.participantId);
// Query available participants from the sports seasons
const availableParticipants = await db.query.participants.findMany({
where: and(
sportsSeasonIds.length === 1
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds),
draftedParticipantIds.length > 0
? notInArray(schema.participants.id, draftedParticipantIds)
: undefined
),
orderBy: desc(schema.participants.expectedValue),
limit: 1,
});
if (availableParticipants.length === 0) {
console.error(`[Timer] No available participants for auto-pick in season ${seasonId}`);
return;
}
participantId = availableParticipants[0].id;
console.log(`[Timer] Auto-picking highest EV: ${participantId}`);
}
// Calculate round and pick in round
const draftSlots = await db.query.draftSlots.findMany({
where: eq(schema.draftSlots.seasonId, seasonId),
orderBy: asc(schema.draftSlots.draftOrder),
});
const totalTeams = draftSlots.length;
const round = Math.ceil(pickNumber / totalTeams);
const pickInRound = ((pickNumber - 1) % totalTeams) + 1;
// Create the draft pick
const [createdPick] = await db.insert(schema.draftPicks).values({
// Execute the autopick using the unified function
const result = await executeAutoPick({
seasonId,
teamId,
participantId,
pickNumber,
round,
pickInRound,
pickedByUserId: "", // Auto-pick has no user
pickedByType: "auto",
timeUsed: season.draftInitialTime || 120, // Used all time
createdAt: new Date(),
}).returning();
console.log(`[Timer] Auto-pick created: Pick ${pickNumber} - Participant ${participantId}`);
// Remove from ALL team queues in this season (participant is now drafted)
await db
.delete(schema.draftQueue)
.where(
and(
eq(schema.draftQueue.seasonId, seasonId),
eq(schema.draftQueue.participantId, participantId)
)
);
// Check if draft is complete
const totalPicks = totalTeams * (season.draftRounds || 1);
const allPicks = await db.query.draftPicks.findMany({
where: eq(schema.draftPicks.seasonId, seasonId),
});
const isDraftComplete = allPicks.length >= totalPicks;
const nextPickNumber = isDraftComplete ? pickNumber : pickNumber + 1;
// If autodraft was enabled with "next_pick" mode, disable it now
if (autodraftSettings?.isEnabled && autodraftSettings.mode === "next_pick") {
console.log(`[Timer] Disabling autodraft for team ${teamId} after next_pick`);
await db
.update(schema.autodraftSettings)
.set({
isEnabled: false,
updatedAt: new Date(),
})
.where(eq(schema.autodraftSettings.id, autodraftSettings.id));
// Emit socket event to notify clients
io.to(`draft-${seasonId}`).emit("autodraft-updated", {
teamId,
isEnabled: false,
mode: autodraftSettings.mode,
});
}
// Add increment to the team that just picked
const pickingTeamTimer = await db.query.draftTimers.findFirst({
where: and(
eq(schema.draftTimers.seasonId, seasonId),
eq(schema.draftTimers.teamId, teamId)
),
triggeredBy: "timer",
autodraftSettings,
db,
});
if (pickingTeamTimer) {
const incrementTime = season.draftIncrementTime || 30;
const newTimeRemaining = pickingTeamTimer.timeRemaining + incrementTime;
await db
.update(schema.draftTimers)
.set({
timeRemaining: newTimeRemaining,
updatedAt: new Date(),
})
.where(eq(schema.draftTimers.id, pickingTeamTimer.id));
// Emit timer update to all clients
io.to(`draft-${seasonId}`).emit("timer-update", {
seasonId,
teamId,
timeRemaining: newTimeRemaining,
currentPickNumber: pickNumber,
});
}
if (isDraftComplete) {
// Draft complete
await db
.update(schema.seasons)
.set({ status: "active" })
.where(eq(schema.seasons.id, seasonId));
io.to(`draft-${seasonId}`).emit("draft-completed");
console.log(`[Timer] Draft completed for season ${seasonId}`);
} else {
// Move to next pick
await db
.update(schema.seasons)
.set({ currentPickNumber: nextPickNumber })
.where(eq(schema.seasons.id, seasonId));
}
// Fetch the complete pick with all relations (same as API routes do)
const completePick = await db
.select({
id: schema.draftPicks.id,
seasonId: schema.draftPicks.seasonId,
teamId: schema.draftPicks.teamId,
participantId: schema.draftPicks.participantId,
pickNumber: schema.draftPicks.pickNumber,
round: schema.draftPicks.round,
pickInRound: schema.draftPicks.pickInRound,
pickedByUserId: schema.draftPicks.pickedByUserId,
pickedByType: schema.draftPicks.pickedByType,
timeUsed: schema.draftPicks.timeUsed,
createdAt: schema.draftPicks.createdAt,
team: schema.teams,
participant: schema.participants,
sport: schema.sports,
})
.from(schema.draftPicks)
.innerJoin(schema.teams, eq(schema.draftPicks.teamId, schema.teams.id))
.innerJoin(schema.participants, eq(schema.draftPicks.participantId, schema.participants.id))
.innerJoin(schema.sportsSeasons, eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id))
.innerJoin(schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id))
.where(eq(schema.draftPicks.id, createdPick.id))
.limit(1);
if (completePick.length === 0) {
console.error(`[Timer] Could not fetch complete pick data`);
if (!result.success) {
console.error(`[Timer] Auto-pick failed: ${result.error}`);
return;
}
// Emit pick-made event with the same structure as API routes
io.to(`draft-${seasonId}`).emit("pick-made", {
pick: {
...completePick[0],
participant: {
...completePick[0].participant,
sport: completePick[0].sport,
},
},
nextPickNumber,
isDraftComplete,
});
console.log(`[Timer] Auto-pick complete and broadcasted`);
} catch (error) {
console.error("[Timer] Error in triggerAutoPick:", error);

View file

@ -4,6 +4,8 @@
"server/**/*.ts",
"database/**/*.ts",
"app/contexts/**/*.ts",
"app/models/**/*.ts",
"app/lib/**/*.ts",
"vite.config.ts"
],
"exclude": [

View file

@ -28,7 +28,9 @@
"server.ts",
"server/**/*.ts",
"database/**/*.ts",
"app/database/**/*.ts"
"app/database/**/*.ts",
"app/models/**/*.ts",
"app/lib/**/*.ts"
],
"exclude": [
"node_modules",