Fix Discord notification firing for advancing-only World Cup teams

In non-scoring rounds (R32 → R16 in FIFA 48), winners advance without
earning any points. The previous code set winnerUsername unconditionally
for any match where the winner had an owner, so when a loser was
eliminated (triggering showLoser=true), the advancing winner appeared in
the "Scored Matches" section and triggered a notification even though
their fantasy score was unchanged.

Gate winnerUsername on winnerScoreChanged, matching the existing guard
on winnerDiscordUserId. A manager is now only mentioned when their team
actually earned or changed points this round.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXKpR3MZJn5Zof1AKeETFM
This commit is contained in:
Claude 2026-06-30 23:31:36 +00:00
parent 3e50619629
commit 177555f043
No known key found for this signature in database
2 changed files with 10 additions and 10 deletions

View file

@ -1830,9 +1830,8 @@ export async function recalculateAffectedLeagues(
// A match is included only when it is "notable": winner earned points this round OR
// the loser is notifiable (eliminated / scored). A drafted winner that merely advanced
// without earning points is NOT enough on its own.
// On any included match, the winning team's owner is shown in the embed (winnerUsername),
// but Discord-pinged only when they actually scored (winnerDiscordUserId gated on
// winnerScoreChanged) — advancing-only wins don't warrant a ping.
// winnerUsername (and winnerDiscordUserId) are only set when the winner actually scored —
// advancing through a non-scoring round does not warrant mentioning or pinging the owner.
// Losers appear if their score changed or they were definitively eliminated (finalPosition
// set, non-partial); losers who advance to another match (loserAdvances=true, e.g. NBA
// 7v8 → PIR2) are correctly suppressed.
@ -1869,7 +1868,7 @@ export async function recalculateAffectedLeagues(
.map((x) => ({
winnerName: x.m.winnerName ?? "",
loserName: x.m.loserName ?? "",
winnerUsername: x.winnerOwnerId ? usernameByUserId.get(x.winnerOwnerId) : undefined,
winnerUsername: x.winnerScoreChanged && x.winnerOwnerId ? usernameByUserId.get(x.winnerOwnerId) : undefined,
loserUsername: x.showLoser && x.loserOwnerId ? usernameByUserId.get(x.loserOwnerId) : undefined,
winnerDiscordUserId: x.winnerScoreChanged && x.winnerOwnerId ? discordIdByUserId.get(x.winnerOwnerId) : undefined,
loserDiscordUserId: x.showLoser && x.loserOwnerId ? discordIdByUserId.get(x.loserOwnerId) : undefined,

View file

@ -186,10 +186,10 @@ describe("sendStandingsUpdateNotification", () => {
expect(desc).toContain("• **Sporting (christhrowsrocks)** def. Bodø/Glimt (apatel)");
});
it("shows winner's owner even when winner only advanced (loser was eliminated)", async () => {
// Mirrors the Brazil/Japan scenario: Brazil advanced without earning points,
// Japan was eliminated. winnerUsername is shown in the embed; winnerDiscordUserId
// is left unset (no ping) because the winner did not score this round.
it("omits winner's manager when they only advanced without earning points", async () => {
// Brazil advances through a non-scoring round (R32 → R16 in World Cup).
// scoring-calculator.ts does not set winnerUsername for advancing-only winners,
// so only Japan's manager appears in the notification.
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "Rumble League 2026",
@ -199,14 +199,15 @@ describe("sendStandingsUpdateNotification", () => {
{
winnerName: "Brazil",
loserName: "Japan",
winnerUsername: "someManager",
// winnerUsername intentionally absent — Brazil advanced without scoring
loserUsername: "ikyn",
},
],
});
const desc = getDescription();
expect(desc).toContain("• **Brazil (someManager)** def. Japan (ikyn)");
expect(desc).toContain("• **Brazil** def. Japan (ikyn)");
expect(desc).not.toContain("Brazil (");
});
it("omits scored matches where neither side has a username", async () => {