Compare commits

..

No commits in common. "613106cb9e742eb0cf177bddc271153afc4e19bf" and "3e50619629cfb8a97e4a5a60b7233502a130bf8a" have entirely different histories.

2 changed files with 18 additions and 16 deletions

View file

@ -1827,15 +1827,17 @@ export async function recalculateAffectedLeagues(
); );
// Build scored matches for the notification. // Build scored matches for the notification.
// A match is worth announcing when: // A match is included only when it is "notable": winner earned points this round OR
// • the winner is owned by a manager AND earned points this round, OR // the loser is notifiable (eliminated / scored). A drafted winner that merely advanced
// • the loser is owned by a manager (eliminated or scored). // without earning points is NOT enough on its own.
// A winning team that merely advanced through a non-scoring round (e.g. R32 → R16 // On any included match, the winning team's owner is shown in the embed (winnerUsername),
// in the World Cup) does not qualify on its own — their owner hasn't earned anything yet. // but Discord-pinged only when they actually scored (winnerDiscordUserId gated on
// When a match qualifies because the loser is owned, the winner's manager tag is still // winnerScoreChanged) — advancing-only wins don't warrant a ping.
// shown for context (who beat them), but the winner is not Discord-pinged. // Losers appear if their score changed or they were definitively eliminated (finalPosition
// Losers who advance to another match (loserAdvances=true, e.g. NBA 7v8 → PIR2) have // set, non-partial); losers who advance to another match (loserAdvances=true, e.g. NBA
// showLoser=false and are correctly suppressed. // 7v8 → PIR2) are correctly suppressed.
// Only entries with at least one displayable username are kept, so scoredMatches always
// reflects exactly what will be shown in the embed.
let scoredMatches: ScoredMatch[] | undefined; let scoredMatches: ScoredMatch[] | undefined;
if (allCompletedMatches.length > 0) { if (allCompletedMatches.length > 0) {
const relevant = allCompletedMatches.filter( const relevant = allCompletedMatches.filter(
@ -1863,7 +1865,7 @@ export async function recalculateAffectedLeagues(
const showLoser = isLoserNotifiable(m.loserId, loserTeamId, changedTeamIds, finalizedLoserIds); const showLoser = isLoserNotifiable(m.loserId, loserTeamId, changedTeamIds, finalizedLoserIds);
return { m, winnerScoreChanged, showLoser, winnerOwnerId, loserOwnerId }; return { m, winnerScoreChanged, showLoser, winnerOwnerId, loserOwnerId };
}) })
.filter((x) => (x.winnerScoreChanged && !!x.winnerOwnerId) || (x.showLoser && !!x.loserOwnerId)) .filter((x) => x.winnerScoreChanged || x.showLoser)
.map((x) => ({ .map((x) => ({
winnerName: x.m.winnerName ?? "", winnerName: x.m.winnerName ?? "",
loserName: x.m.loserName ?? "", loserName: x.m.loserName ?? "",

View file

@ -186,10 +186,10 @@ describe("sendStandingsUpdateNotification", () => {
expect(desc).toContain("• **Sporting (christhrowsrocks)** def. Bodø/Glimt (apatel)"); expect(desc).toContain("• **Sporting (christhrowsrocks)** def. Bodø/Glimt (apatel)");
}); });
it("shows winner's manager for context when the match fires due to an owned loser", async () => { it("shows winner's owner even when winner only advanced (loser was eliminated)", async () => {
// Brazil beats Japan (R32, non-scoring). Japan's manager is the reason for the // Mirrors the Brazil/Japan scenario: Brazil advanced without earning points,
// notification; Brazil's manager is shown for context even though they didn't score. // Japan was eliminated. winnerUsername is shown in the embed; winnerDiscordUserId
// scoring-calculator.ts controls whether to include the match; discord.ts just renders. // is left unset (no ping) because the winner did not score this round.
await sendStandingsUpdateNotification({ await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL, webhookUrl: WEBHOOK_URL,
seasonName: "Rumble League 2026", seasonName: "Rumble League 2026",
@ -199,14 +199,14 @@ describe("sendStandingsUpdateNotification", () => {
{ {
winnerName: "Brazil", winnerName: "Brazil",
loserName: "Japan", loserName: "Japan",
winnerUsername: "aliceManager", winnerUsername: "someManager",
loserUsername: "ikyn", loserUsername: "ikyn",
}, },
], ],
}); });
const desc = getDescription(); const desc = getDescription();
expect(desc).toContain("• **Brazil (aliceManager)** def. Japan (ikyn)"); expect(desc).toContain("• **Brazil (someManager)** def. Japan (ikyn)");
}); });
it("omits scored matches where neither side has a username", async () => { it("omits scored matches where neither side has a username", async () => {