Fix Discord notification edge cases from code review
- Fix point delta sign: negative diffs now correctly show -5 pts not +-5 pts
- Replace hardcoded soccer emoji with a neutral bullet (works for all sports)
- Truncate embed description at Discord's 4096-char limit
- Thread eventId through processMatchResult and bracket batch handler so
scored matches appear in single-match notifications too
- Pass eventName to finalizeQualifyingPoints ("Final Standings") and
processSeasonStandings ("Season Complete") so those notifications have context
- Test notification now fetches current season to show "League 2025" format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1f8a69a2b7
commit
db49c83999
5 changed files with 26 additions and 15 deletions
|
|
@ -310,13 +310,14 @@ export async function processMatchResult(
|
||||||
isScoring: boolean;
|
isScoring: boolean;
|
||||||
sportsSeasonId: string;
|
sportsSeasonId: string;
|
||||||
bracketTemplateId?: string | null;
|
bracketTemplateId?: string | null;
|
||||||
|
eventId?: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
skipSideEffects?: boolean;
|
skipSideEffects?: boolean;
|
||||||
},
|
},
|
||||||
providedDb?: ReturnType<typeof database>
|
providedDb?: ReturnType<typeof database>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const db = providedDb || database();
|
const db = providedDb || database();
|
||||||
const { round, winnerId, loserId, isScoring, sportsSeasonId, bracketTemplateId, eventName, skipSideEffects } = params;
|
const { round, winnerId, loserId, isScoring, sportsSeasonId, bracketTemplateId, eventId, eventName, skipSideEffects } = params;
|
||||||
|
|
||||||
if (!isScoring) {
|
if (!isScoring) {
|
||||||
// Non-scoring (pre-bracket) round: loser permanently eliminated (0 pts),
|
// Non-scoring (pre-bracket) round: loser permanently eliminated (0 pts),
|
||||||
|
|
@ -346,7 +347,7 @@ export async function processMatchResult(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipSideEffects) {
|
if (!skipSideEffects) {
|
||||||
await recalculateAffectedLeagues(sportsSeasonId, db, eventName ? { eventName } : undefined);
|
await recalculateAffectedLeagues(sportsSeasonId, db, (eventName || eventId) ? { eventName, eventId } : undefined);
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -629,7 +630,7 @@ export async function finalizeQualifyingPoints(
|
||||||
.where(eq(schema.sportsSeasons.id, sportsSeasonId));
|
.where(eq(schema.sportsSeasons.id, sportsSeasonId));
|
||||||
|
|
||||||
// Trigger recalculation for all affected leagues
|
// Trigger recalculation for all affected leagues
|
||||||
await recalculateAffectedLeagues(sportsSeasonId, db);
|
await recalculateAffectedLeagues(sportsSeasonId, db, { eventName: "Final Standings" });
|
||||||
|
|
||||||
// Auto-trigger probability recalculation after result
|
// Auto-trigger probability recalculation after result
|
||||||
try {
|
try {
|
||||||
|
|
@ -739,7 +740,7 @@ export async function processSeasonStandings(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger recalculation for all affected leagues
|
// Trigger recalculation for all affected leagues
|
||||||
await recalculateAffectedLeagues(sportsSeasonId, db);
|
await recalculateAffectedLeagues(sportsSeasonId, db, { eventName: "Season Complete" });
|
||||||
|
|
||||||
// Auto-trigger probability recalculation after result
|
// Auto-trigger probability recalculation after result
|
||||||
try {
|
try {
|
||||||
|
|
@ -1215,7 +1216,6 @@ export async function recalculateAffectedLeagues(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const standings = afterStandings.map((s) => ({
|
const standings = afterStandings.map((s) => ({
|
||||||
teamId: s.teamId,
|
teamId: s.teamId,
|
||||||
teamName: s.team.name,
|
teamName: s.team.name,
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
isScoring: match.isScoring ?? true,
|
isScoring: match.isScoring ?? true,
|
||||||
sportsSeasonId: event.sportsSeasonId,
|
sportsSeasonId: event.sportsSeasonId,
|
||||||
bracketTemplateId: event.bracketTemplateId,
|
bracketTemplateId: event.bracketTemplateId,
|
||||||
|
eventId: event.id,
|
||||||
eventName: event.name ?? undefined,
|
eventName: event.name ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -375,7 +376,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
// Run side effects once for the whole batch (avoids N redundant recalcs).
|
// Run side effects once for the whole batch (avoids N redundant recalcs).
|
||||||
if (successCount > 0) {
|
if (successCount > 0) {
|
||||||
const db = database();
|
const db = database();
|
||||||
await recalculateAffectedLeagues(event.sportsSeasonId, db, event.name ? { eventName: event.name } : undefined);
|
await recalculateAffectedLeagues(event.sportsSeasonId, db, { eventId: event.id, eventName: event.name ?? undefined });
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -224,9 +224,11 @@ export async function action(args: Route.ActionArgs) {
|
||||||
return { error: "Invalid Discord webhook URL" };
|
return { error: "Invalid Discord webhook URL" };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const testSeason = await findCurrentSeasonWithSports(leagueId);
|
||||||
|
const seasonName = testSeason ? `${league.name} ${testSeason.year}` : league.name;
|
||||||
await sendStandingsUpdateNotification({
|
await sendStandingsUpdateNotification({
|
||||||
webhookUrl,
|
webhookUrl,
|
||||||
seasonName: league.name,
|
seasonName,
|
||||||
standings: [
|
standings: [
|
||||||
{ teamId: "1", teamName: "Team Alpha", totalPoints: 150, rank: 1 },
|
{ teamId: "1", teamName: "Team Alpha", totalPoints: 150, rank: 1 },
|
||||||
{ teamId: "2", teamName: "Team Beta", totalPoints: 125, rank: 2 },
|
{ teamId: "2", teamName: "Team Beta", totalPoints: 125, rank: 2 },
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,8 @@ describe("sendStandingsUpdateNotification", () => {
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Scored Matches**");
|
expect(desc).toContain("**Scored Matches**");
|
||||||
expect(desc).toContain("⚽ **Real Madrid** def. Bayern Munich");
|
expect(desc).toContain("• **Real Madrid** def. Bayern Munich");
|
||||||
expect(desc).toContain("⚽ **Arsenal** def. PSG");
|
expect(desc).toContain("• **Arsenal** def. PSG");
|
||||||
// Scored matches section should appear before standings
|
// Scored matches section should appear before standings
|
||||||
expect(desc.indexOf("Scored Matches")).toBeLessThan(desc.indexOf("Current Standings"));
|
expect(desc.indexOf("Scored Matches")).toBeLessThan(desc.indexOf("Current Standings"));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export async function sendStandingsUpdateNotification({
|
||||||
if (scoredMatches && scoredMatches.length > 0) {
|
if (scoredMatches && scoredMatches.length > 0) {
|
||||||
sections.push("**Scored Matches**");
|
sections.push("**Scored Matches**");
|
||||||
for (const match of scoredMatches) {
|
for (const match of scoredMatches) {
|
||||||
sections.push(`⚽ **${match.winnerName}** def. ${match.loserName}`);
|
sections.push(`• **${match.winnerName}** def. ${match.loserName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,19 +77,27 @@ export async function sendStandingsUpdateNotification({
|
||||||
sections.push("**Current Standings**");
|
sections.push("**Current Standings**");
|
||||||
for (const s of standings) {
|
for (const s of standings) {
|
||||||
const prev = previousStandings.get(s.teamId);
|
const prev = previousStandings.get(s.teamId);
|
||||||
const delta =
|
let delta = "";
|
||||||
prev !== undefined && prev !== s.totalPoints
|
if (prev !== undefined && prev !== s.totalPoints) {
|
||||||
? ` **(+${Math.round(s.totalPoints - prev)} pts)**`
|
const diff = Math.round(s.totalPoints - prev);
|
||||||
: "";
|
const sign = diff > 0 ? "+" : "";
|
||||||
|
delta = ` **(${sign}${diff} pts)**`;
|
||||||
|
}
|
||||||
const medal = RANK_MEDALS[s.rank - 1] ?? `${s.rank}.`;
|
const medal = RANK_MEDALS[s.rank - 1] ?? `${s.rank}.`;
|
||||||
sections.push(`${medal} ${s.teamName} — ${Math.round(s.totalPoints)} pts${delta}`);
|
sections.push(`${medal} ${s.teamName} — ${Math.round(s.totalPoints)} pts${delta}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_DESCRIPTION = 4096;
|
||||||
|
let description = sections.join("\n");
|
||||||
|
if (description.length > MAX_DESCRIPTION) {
|
||||||
|
description = description.slice(0, MAX_DESCRIPTION - 3) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
await sendDiscordWebhook(webhookUrl, {
|
await sendDiscordWebhook(webhookUrl, {
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
title: `📊 Standings Update — ${seasonName}`,
|
title: `📊 Standings Update — ${seasonName}`,
|
||||||
description: sections.join("\n"),
|
description,
|
||||||
color: 0x5865f2, // Discord blurple
|
color: 0x5865f2, // Discord blurple
|
||||||
footer: { text: "brackt.com" },
|
footer: { text: "brackt.com" },
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue