diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts index 2742167..4ea65c9 100644 --- a/app/models/scoring-calculator.ts +++ b/app/models/scoring-calculator.ts @@ -1299,10 +1299,15 @@ export async function recalculateAffectedLeagues( orderBy: (ts, { asc }) => [asc(ts.currentRank)], }); - const hasChanges = afterStandings.some((s) => { - const prev = previousPointsById.get(s.teamId); - return prev === undefined || prev !== parseFloat(s.totalPoints); - }); + const changedTeamIds = new Set( + afterStandings + .filter((s) => { + const prev = previousPointsById.get(s.teamId); + return prev === undefined || prev !== parseFloat(s.totalPoints); + }) + .map((s) => s.teamId) + ); + const hasChanges = changedTeamIds.size > 0; // Look up the league's Discord webhook URL via the season const season = await db.query.seasons.findFirst({ @@ -1333,12 +1338,10 @@ export async function recalculateAffectedLeagues( afterStandings.map((s) => [s.teamId, s.team.ownerId]) ); - // Filter matches to only those involving participants drafted in this season. - // We compute this BEFORE the hasChanges gate because 0-point eliminations - // (e.g. R64 losers whose finalPosition=0 doesn't affect team scores) still - // warrant a Discord notification when a drafted team is eliminated. + // Build scored matches for the notification — computed before the gate + // because an elimination (0 points, no score change) still warrants a + // notification when a drafted participant is knocked out. let scoredMatches: ScoredMatch[] | undefined; - let hasDraftedParticipantMatches = false; if (allCompletedMatches.length > 0) { const draftPicks = await db.query.draftPicks.findMany({ where: eq(schema.draftPicks.seasonId, seasonId), @@ -1351,40 +1354,22 @@ export async function recalculateAffectedLeagues( (m.loserId && draftedIds.has(m.loserId)) ); - hasDraftedParticipantMatches = relevant.length > 0; - if (relevant.length > 0) { const teamIdByParticipantId = new Map( draftPicks.map((p) => [p.participantId, p.teamId]) ); - // Teams whose score actually changed after recalculation. - const changedTeamIds = new Set( - afterStandings - .filter((s) => { - const prev = previousPointsById.get(s.teamId); - return prev === undefined || prev !== parseFloat(s.totalPoints); - }) - .map((s) => s.teamId) - ); - - const usernameForParticipant = (participantId: string | null | undefined) => { + // Look up the fantasy owner's display name for a participant. + // Pass requireScoreChange=true for the winner to avoid surfacing wins + // that didn't award points this round. + const usernameForParticipant = ( + participantId: string | null | undefined, + requireScoreChange = false + ) => { if (!participantId) return undefined; const teamId = teamIdByParticipantId.get(participantId); if (!teamId) return undefined; - const ownerId = ownerIdByTeamId.get(teamId); - if (!ownerId) return undefined; - return usernameByClerkId.get(ownerId); - }; - - // Only set winnerUsername if the winner's owner actually scored points - // this round. This prevents listing matches where a drafted team won - // but the scoring system didn't award points yet (e.g. R64 wins when - // points only trigger at Sweet 16+). - const winnerUsernameForParticipant = (participantId: string | null | undefined) => { - if (!participantId) return undefined; - const teamId = teamIdByParticipantId.get(participantId); - if (!teamId || !changedTeamIds.has(teamId)) return undefined; + if (requireScoreChange && !changedTeamIds.has(teamId)) return undefined; const ownerId = ownerIdByTeamId.get(teamId); if (!ownerId) return undefined; return usernameByClerkId.get(ownerId); @@ -1395,14 +1380,17 @@ export async function recalculateAffectedLeagues( .map((m) => ({ winnerName: m.winnerName!, loserName: m.loserName!, - winnerUsername: winnerUsernameForParticipant(m.winnerId), + winnerUsername: usernameForParticipant(m.winnerId, true), loserUsername: usernameForParticipant(m.loserId), })); } } - // Skip notification if scores didn't change AND no drafted participants were scored. - if (!hasChanges && !hasDraftedParticipantMatches) continue; + // Skip notification if scores didn't change AND no match has a displayable username. + const hasScoredMatchesToShow = scoredMatches?.some( + (m) => m.winnerUsername !== undefined || m.loserUsername !== undefined + ); + if (!hasChanges && !hasScoredMatchesToShow) continue; const standings = afterStandings.map((s) => ({ teamId: s.teamId,