Short-circuit QP notifier when nothing drafted is involved
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m47s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m21s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped

Widening the tennis-sync gate to fire on any elimination means
notifyQualifyingPointsUpdate now runs on essentially every early-round
sync, since most eliminated players are undrafted. Fetch the draft picks
first and bail before the QP-total, season, participant, user, and Discord
lookups when no drafted participant earned QP or was knocked out this sync —
avoiding a full query battery just to send nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkPWxSCunhPFknNUTXm4aZ
This commit is contained in:
Claude 2026-07-03 15:10:07 +00:00
parent 8900bf79dc
commit 5be8ac3f2f
No known key found for this signature in database
2 changed files with 51 additions and 17 deletions

View file

@ -373,6 +373,29 @@ describe("notifyQualifyingPointsUpdate", () => {
expect(call.eliminated).toEqual([]); expect(call.eliminated).toEqual([]);
}); });
it("short-circuits before the QP-total/season lookups when nothing drafted is involved", async () => {
// A knockout occurred but the player isn't drafted in any league. The notifier
// is invoked (the sync fires it on any elimination) but must not run the rest
// of its query battery just to send nothing.
const db = makeDb({
draftPicks: { findMany: vi.fn().mockResolvedValue([]) },
});
await notifyQualifyingPointsUpdate(
SPORTS_SEASON_ID,
SCORING_EVENT_ID,
db as never,
new Set(["p-nonexistent"]),
new Set(["p-undrafted"])
);
expect(db.query.draftPicks.findMany).toHaveBeenCalledOnce();
expect(db.query.seasonParticipantQualifyingTotals.findMany).not.toHaveBeenCalled();
expect(db.query.seasons.findMany).not.toHaveBeenCalled();
expect(db.query.seasonParticipants.findMany).not.toHaveBeenCalled();
expect(sendQualifyingPointsUpdateNotification).not.toHaveBeenCalled();
});
it("fires when a drafted player is knocked out even though no QP changed", async () => { it("fires when a drafted player is knocked out even though no QP changed", async () => {
const db = makeDbWithEliminated(); const db = makeDbWithEliminated();

View file

@ -59,6 +59,34 @@ export async function notifyQualifyingPointsUpdate(
if (qpEarnedById.size === 0 && eliminatedIds.size === 0) return; if (qpEarnedById.size === 0 && eliminatedIds.size === 0) return;
const seasonIds = seasonSports.map((s) => s.seasonId);
// Batch-fetch all draft picks for all leagues at once, grouped by season.
// Fetched before the remaining lookups so we can short-circuit when nothing
// that happened this sync was actually drafted: during a Grand Slam's early
// rounds most eliminated players are undrafted, and the notifier now fires on
// every sync that has any elimination — no point running the rest of the
// queries just to send nothing.
const allPicks = await db.query.draftPicks.findMany({
where: inArray(schema.draftPicks.seasonId, seasonIds),
with: { team: true },
});
const picksBySeasonId = new Map<string, (typeof allPicks)[number][]>();
const draftedParticipantIds = new Set<string>();
for (const pick of allPicks) {
draftedParticipantIds.add(pick.participantId);
const bucket = picksBySeasonId.get(pick.seasonId);
if (bucket) {
bucket.push(pick);
} else {
picksBySeasonId.set(pick.seasonId, [pick]);
}
}
const hasDraftedQP = [...qpEarnedById.keys()].some((id) => draftedParticipantIds.has(id));
const hasDraftedEliminated = [...eliminatedIds].some((id) => draftedParticipantIds.has(id));
if (!hasDraftedQP && !hasDraftedEliminated) return;
// Current running QP totals for the season // Current running QP totals for the season
const qpTotals = await db.query.seasonParticipantQualifyingTotals.findMany({ const qpTotals = await db.query.seasonParticipantQualifyingTotals.findMany({
where: eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId), where: eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId),
@ -67,8 +95,6 @@ export async function notifyQualifyingPointsUpdate(
qpTotals.map((t) => [t.participantId, parseFloat(t.totalQualifyingPoints)]) qpTotals.map((t) => [t.participantId, parseFloat(t.totalQualifyingPoints)])
); );
const seasonIds = seasonSports.map((s) => s.seasonId);
// Batch-fetch all season + league metadata in one query // Batch-fetch all season + league metadata in one query
const seasons = await db.query.seasons.findMany({ const seasons = await db.query.seasons.findMany({
where: inArray(schema.seasons.id, seasonIds), where: inArray(schema.seasons.id, seasonIds),
@ -76,21 +102,6 @@ export async function notifyQualifyingPointsUpdate(
}); });
const seasonMap = new Map(seasons.map((s) => [s.id, s])); const seasonMap = new Map(seasons.map((s) => [s.id, s]));
// Batch-fetch all draft picks for all leagues at once, grouped by season
const allPicks = await db.query.draftPicks.findMany({
where: inArray(schema.draftPicks.seasonId, seasonIds),
with: { team: true },
});
const picksBySeasonId = new Map<string, (typeof allPicks)[number][]>();
for (const pick of allPicks) {
const bucket = picksBySeasonId.get(pick.seasonId);
if (bucket) {
bucket.push(pick);
} else {
picksBySeasonId.set(pick.seasonId, [pick]);
}
}
// Batch-fetch participant display names once (same participants across all leagues) // Batch-fetch participant display names once (same participants across all leagues)
const allParticipantIds = [...new Set([...qpEarnedById.keys(), ...eliminatedIds])]; const allParticipantIds = [...new Set([...qpEarnedById.keys(), ...eliminatedIds])];
const participants = await db.query.seasonParticipants.findMany({ const participants = await db.query.seasonParticipants.findMany({