Compare commits
No commits in common. "7e578c714c2651f9561b09d334680d89e9b7e340" and "5d0363a309cbd0edae97156e8eb445f505b2f453" have entirely different histories.
7e578c714c
...
5d0363a309
4 changed files with 109 additions and 117 deletions
|
|
@ -790,9 +790,10 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
expect(desc.indexOf("Novak Djokovic")).toBeLessThan(desc.indexOf("Carlos Alcaraz"));
|
expect(desc.indexOf("Novak Djokovic")).toBeLessThan(desc.indexOf("Carlos Alcaraz"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("omits zero-QP drafted participants entirely from the Drafted Participants section", async () => {
|
it("lists non-scoring participants (outside top 8) as one comma-separated line below Top 8", async () => {
|
||||||
// Only participants who have actually scored (qpTotal > 0) are listed; a 0-QP drafted
|
// Non-scoring now draws from the full scoreboard: every drafted participant NOT in
|
||||||
// player no longer appears anywhere in the standings section.
|
// the top 8 (rank 9+ or unranked), rendered on a single line "Name (total, manager)"
|
||||||
|
// ordered by season total desc — not just this event's zero-earners.
|
||||||
await sendQualifyingPointsUpdateNotification({
|
await sendQualifyingPointsUpdateNotification({
|
||||||
webhookUrl: WEBHOOK_URL,
|
webhookUrl: WEBHOOK_URL,
|
||||||
seasonName: "Slam League 2025",
|
seasonName: "Slam League 2025",
|
||||||
|
|
@ -807,19 +808,45 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Drafted Participants**");
|
expect(desc).toContain("**Non-scoring Participants**");
|
||||||
// Both scorers appear as ranked rows...
|
// Single comma-separated line: "Name (seasonTotal, manager)", higher total first.
|
||||||
expect(desc).toContain("1\\. Champ (alex) — 100 QP");
|
expect(desc).toContain("Also Ran (5, chris), Winless Wonder (0, sam)");
|
||||||
expect(desc).toContain("9\\. Also Ran (chris) — 5 QP");
|
// Champ is in the top 8, so it must NOT appear in the non-scoring line.
|
||||||
// ...with a Points Bubble divider separating the rank-9 scorer.
|
expect(desc).not.toContain("Champ (100");
|
||||||
expect(desc).toContain("**═══ Points Bubble ═══**");
|
// Non-scoring appears below the Top 8 section.
|
||||||
// The 0-QP player is omitted entirely.
|
expect(desc.indexOf("Drafted Participants in Top 8")).toBeLessThan(
|
||||||
expect(desc).not.toContain("Winless Wonder");
|
desc.indexOf("Non-scoring Participants")
|
||||||
// The old Non-scoring section is gone.
|
);
|
||||||
expect(desc).not.toContain("Non-scoring Participants");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the Drafted Participants section for scored participants sorted by rank", async () => {
|
it("keeps zero-QP participants tied into the top-8 rank band out of the Top 8 section", async () => {
|
||||||
|
// Regression: early in a season, standard competition ranking ties every winless
|
||||||
|
// player into a low rank (here rank 2). A rank-only Top 8 filter would flood the
|
||||||
|
// section with "T2. Name — 0 QP" rows; the qpTotal>0 guard drops them to Non-scoring.
|
||||||
|
await sendQualifyingPointsUpdateNotification({
|
||||||
|
webhookUrl: WEBHOOK_URL,
|
||||||
|
seasonName: "Slam League 2025",
|
||||||
|
entries: [
|
||||||
|
{ participantName: "Only Scorer", qpEarned: 10, qpTotal: 10, globalRank: 1, globalRankTied: false, ownerUsername: "alex" },
|
||||||
|
],
|
||||||
|
scoreboard: [
|
||||||
|
{ participantName: "Only Scorer", qpEarned: 10, qpTotal: 10, globalRank: 1, globalRankTied: false, ownerUsername: "alex" },
|
||||||
|
{ participantName: "Winless A", qpEarned: 0, qpTotal: 0, globalRank: 2, globalRankTied: true, ownerUsername: "chris" },
|
||||||
|
{ participantName: "Winless B", qpEarned: 0, qpTotal: 0, globalRank: 2, globalRankTied: true, ownerUsername: "sam" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const desc = getDescription();
|
||||||
|
// Top 8 contains only the real scorer.
|
||||||
|
expect(desc).toContain("1\\. Only Scorer (alex) — 10 QP");
|
||||||
|
// The tied 0-QP players must NOT appear as top-8 rows.
|
||||||
|
expect(desc).not.toContain("T2\\. Winless A");
|
||||||
|
expect(desc).not.toContain("T2\\. Winless B");
|
||||||
|
// They land in the Non-scoring line instead.
|
||||||
|
expect(desc).toContain("Winless A (0, chris), Winless B (0, sam)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows Drafted Participants in Top 8 for scoreboard sorted by qpTotal desc", async () => {
|
||||||
await sendQualifyingPointsUpdateNotification({
|
await sendQualifyingPointsUpdateNotification({
|
||||||
webhookUrl: WEBHOOK_URL,
|
webhookUrl: WEBHOOK_URL,
|
||||||
seasonName: "Slam League 2025",
|
seasonName: "Slam League 2025",
|
||||||
|
|
@ -828,61 +855,12 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Drafted Participants**");
|
expect(desc).toContain("**Drafted Participants in Top 8**");
|
||||||
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
||||||
expect(desc).toContain("2\\. Carlos Alcaraz (chris) — 34 QP");
|
expect(desc).toContain("2\\. Carlos Alcaraz (chris) — 34 QP");
|
||||||
expect(desc).toContain("3\\. Rafael Nadal (alex) — 20 QP");
|
expect(desc).toContain("3\\. Rafael Nadal (alex) — 20 QP");
|
||||||
// Djokovic should rank above Alcaraz
|
// Djokovic should rank above Alcaraz
|
||||||
expect(desc.indexOf("1\\. Novak")).toBeLessThan(desc.indexOf("2\\. Carlos"));
|
expect(desc.indexOf("1\\. Novak")).toBeLessThan(desc.indexOf("2\\. Carlos"));
|
||||||
// Everyone is rank <= 8, so no divider is emitted.
|
|
||||||
expect(desc).not.toContain("Points Bubble");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("inserts a Points Bubble divider between the rank-8 and rank-9 scorers", async () => {
|
|
||||||
const scoreboard = [
|
|
||||||
{ participantName: "Player Eight", qpEarned: 5, qpTotal: 12, globalRank: 8, globalRankTied: false, ownerUsername: "chris" },
|
|
||||||
{ participantName: "Player Nine", qpEarned: 3, qpTotal: 8, globalRank: 9, globalRankTied: false, ownerUsername: "alex" },
|
|
||||||
];
|
|
||||||
await sendQualifyingPointsUpdateNotification({
|
|
||||||
webhookUrl: WEBHOOK_URL,
|
|
||||||
seasonName: "Slam League 2025",
|
|
||||||
entries: scoreboard,
|
|
||||||
scoreboard,
|
|
||||||
});
|
|
||||||
|
|
||||||
const desc = getDescription();
|
|
||||||
const eightIdx = desc.indexOf("8\\. Player Eight");
|
|
||||||
const bubbleIdx = desc.indexOf("**═══ Points Bubble ═══**");
|
|
||||||
const nineIdx = desc.indexOf("9\\. Player Nine");
|
|
||||||
expect(eightIdx).toBeGreaterThan(-1);
|
|
||||||
expect(bubbleIdx).toBeGreaterThan(-1);
|
|
||||||
expect(nineIdx).toBeGreaterThan(-1);
|
|
||||||
// Divider sits between the rank-8 and rank-9 rows.
|
|
||||||
expect(eightIdx).toBeLessThan(bubbleIdx);
|
|
||||||
expect(bubbleIdx).toBeLessThan(nineIdx);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("omits the Points Bubble divider when every scorer is below the cutoff", async () => {
|
|
||||||
// globalRank is a season-wide rank but the scoreboard is scoped to one league's drafts,
|
|
||||||
// so a league can have drafted nobody in the global top 8. The divider must not lead the
|
|
||||||
// section with nothing above it.
|
|
||||||
const scoreboard = [
|
|
||||||
{ participantName: "Player Nine", qpEarned: 3, qpTotal: 8, globalRank: 9, globalRankTied: false, ownerUsername: "alex" },
|
|
||||||
{ participantName: "Player Ten", qpEarned: 2, qpTotal: 5, globalRank: 10, globalRankTied: false, ownerUsername: "chris" },
|
|
||||||
];
|
|
||||||
await sendQualifyingPointsUpdateNotification({
|
|
||||||
webhookUrl: WEBHOOK_URL,
|
|
||||||
seasonName: "Slam League 2025",
|
|
||||||
entries: scoreboard,
|
|
||||||
scoreboard,
|
|
||||||
});
|
|
||||||
|
|
||||||
const desc = getDescription();
|
|
||||||
expect(desc).toContain("**Drafted Participants**");
|
|
||||||
expect(desc).toContain("9\\. Player Nine (alex) — 8 QP");
|
|
||||||
expect(desc).toContain("10\\. Player Ten (chris) — 5 QP");
|
|
||||||
// No rank <= 8 row exists, so the divider must not appear.
|
|
||||||
expect(desc).not.toContain("Points Bubble");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses T-prefix for tied QP totals in standings", async () => {
|
it("uses T-prefix for tied QP totals in standings", async () => {
|
||||||
|
|
@ -937,9 +915,9 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
expect(desc).not.toContain("1.50");
|
expect(desc).not.toContain("1.50");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists a rank-9 scorer below the Points Bubble but omits a 0-QP participant", async () => {
|
it("excludes participants ranked outside the top 8 from the Drafted Participants section", async () => {
|
||||||
// Rank-9 scorers now appear in the standings section (below the bubble), while a drafted
|
// Only the top 8 (plus ties) of the full season field belong in this section, so a
|
||||||
// participant with no points is dropped entirely.
|
// rank-9 scorer must NOT appear in it — even though it still shows in Points Awarded.
|
||||||
const both = [
|
const both = [
|
||||||
{
|
{
|
||||||
participantName: "Player Eight",
|
participantName: "Player Eight",
|
||||||
|
|
@ -957,14 +935,6 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
globalRankTied: false,
|
globalRankTied: false,
|
||||||
ownerUsername: "ninthowner",
|
ownerUsername: "ninthowner",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
participantName: "Player Winless",
|
|
||||||
qpEarned: 0,
|
|
||||||
qpTotal: 0,
|
|
||||||
globalRank: 10,
|
|
||||||
globalRankTied: false,
|
|
||||||
ownerUsername: "winlessowner",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
await sendQualifyingPointsUpdateNotification({
|
await sendQualifyingPointsUpdateNotification({
|
||||||
webhookUrl: WEBHOOK_URL,
|
webhookUrl: WEBHOOK_URL,
|
||||||
|
|
@ -976,14 +946,13 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Drafted Participants**");
|
expect(desc).toContain("**Drafted Participants in Top 8**");
|
||||||
expect(desc).toContain("8\\. Player Eight (eighthowner) — 10 QP");
|
expect(desc).toContain("8\\. Player Eight (eighthowner) — 10 QP");
|
||||||
// The rank-9 scorer now appears as a ranked row below the bubble.
|
// The rank-9 player is filtered out of the standings section entirely.
|
||||||
expect(desc).toContain("**═══ Points Bubble ═══**");
|
expect(desc).not.toContain("9\\. Player Nine");
|
||||||
expect(desc).toContain("9\\. Player Nine (ninthowner) — 8 QP");
|
// ...instead the rank-9 player drops into the Non-scoring line.
|
||||||
// The 0-QP player is omitted from the standings section entirely.
|
expect(desc).toContain("Player Nine (8, ninthowner)");
|
||||||
expect(desc).not.toContain("Player Winless");
|
// ...and both still earned points, so both remain in Points Awarded.
|
||||||
// ...but both scorers still earned points, so both remain in Points Awarded.
|
|
||||||
expect(desc).toContain("• **Player Nine (ninthowner)** — 3 QP");
|
expect(desc).toContain("• **Player Nine (ninthowner)** — 3 QP");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1013,7 +982,7 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
// Points Awarded (a pinged section) uses the Discord mention...
|
// Points Awarded (a pinged section) uses the Discord mention...
|
||||||
expect(desc).toContain("• **Novak Djokovic (<@111222333>)** — 20 QP");
|
expect(desc).toContain("• **Novak Djokovic (<@111222333>)** — 20 QP");
|
||||||
// ...while the (non-pinged) Drafted Participants standings section uses the plain username.
|
// ...while the (non-pinged) Top 8 standings section uses the plain username.
|
||||||
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1068,7 +1037,7 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetch).toHaveBeenCalledOnce();
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Knocked Out**");
|
expect(desc).toContain("**Knocked Out**");
|
||||||
expect(desc).not.toContain("**Drafted Participants**");
|
expect(desc).not.toContain("**Drafted Participants in Top 8**");
|
||||||
expect(desc).not.toContain("**Points Awarded**");
|
expect(desc).not.toContain("**Points Awarded**");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ describe("notifyQualifyingPointsUpdate", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("scoreboard includes every drafted participant even when entries are filtered", async () => {
|
it("scoreboard includes every drafted participant even when entries are filtered", async () => {
|
||||||
// The scoreboard powers the Drafted Participants section and must reflect the full
|
// The scoreboard powers the Top 8 / Non-scoring sections and must reflect the full
|
||||||
// drafted field, not just this sync's changed participants. Here Nadal (p-2) did not
|
// drafted field, not just this sync's changed participants. Here Nadal (p-2) did not
|
||||||
// change this sync (filtered out of entries) but is drafted, so he belongs on the
|
// change this sync (filtered out of entries) but is drafted, so he belongs on the
|
||||||
// scoreboard with his running total and no QP earned this event.
|
// scoreboard with his running total and no QP earned this event.
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,20 @@ export interface QPEliminatedEntry {
|
||||||
ownerDiscordUserId?: string;
|
ownerDiscordUserId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a scoreboard entry belongs in the "Drafted Participants in Top 8" section.
|
||||||
|
* Requires the participant to have actually scored (qpTotal > 0) AND sit in the top 8
|
||||||
|
* of the full season standings. The qpTotal>0 guard matters early in a season: when
|
||||||
|
* fewer than 8 players have scored, standard competition ranking ties every 0-QP player
|
||||||
|
* into a rank <= 8 band (e.g. everyone winless is "T5"), and a rank-only filter would
|
||||||
|
* flood the section with "T5. Name — 0 QP" rows. Rank ties among real scorers at rank 8
|
||||||
|
* are still kept ("top 8 plus ties"); globalRank 0 means unranked. The Non-scoring
|
||||||
|
* section is the exact complement (`!inTopEight`), so the field is partitioned cleanly.
|
||||||
|
*/
|
||||||
|
function inTopEight(e: QPEventEntry): boolean {
|
||||||
|
return e.qpTotal > 0 && e.globalRank >= 1 && e.globalRank <= 8;
|
||||||
|
}
|
||||||
|
|
||||||
export async function sendQualifyingPointsUpdateNotification({
|
export async function sendQualifyingPointsUpdateNotification({
|
||||||
webhookUrl,
|
webhookUrl,
|
||||||
seasonName,
|
seasonName,
|
||||||
|
|
@ -306,8 +320,8 @@ export async function sendQualifyingPointsUpdateNotification({
|
||||||
eliminated?: QPEliminatedEntry[];
|
eliminated?: QPEliminatedEntry[];
|
||||||
/**
|
/**
|
||||||
* The full current scoreboard for the league — every drafted participant, not just
|
* The full current scoreboard for the league — every drafted participant, not just
|
||||||
* those whose QP changed this sync. Drives the "Drafted Participants" standings section.
|
* those whose QP changed this sync. Drives the "Top 8" and "Non-scoring Participants"
|
||||||
* `entries`/`eliminated` remain scoped to this sync's changes and drive the
|
* sections. `entries`/`eliminated` remain scoped to this sync's changes and drive the
|
||||||
* "Points Awarded"/"Knocked Out" sections and the ping list.
|
* "Points Awarded"/"Knocked Out" sections and the ping list.
|
||||||
*/
|
*/
|
||||||
scoreboard?: QPEventEntry[];
|
scoreboard?: QPEventEntry[];
|
||||||
|
|
@ -358,39 +372,48 @@ export async function sendQualifyingPointsUpdateNotification({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drafted Participants: every drafted participant that has actually scored (qpTotal > 0),
|
// Scoreboard sections below draw from the FULL drafted field (`scoreboard`), not just
|
||||||
// drawn from the FULL drafted field (`scoreboard`) not just this sync's movers, so it reads
|
// this sync's movers, so they read as a live standings snapshot. The two sections
|
||||||
// as a live standings snapshot. Rendered as ranked lines ordered by full-season standing. A
|
// partition the field exactly via `inTopEight`, so nobody is dropped or listed twice.
|
||||||
// "Points Bubble" divider marks the cutoff between those currently in the points (rank <= 8)
|
|
||||||
// and those below it (rank >= 9). Participants with 0 QP are omitted entirely. Never pinged,
|
// Top 8 = a drafted participant that has actually scored (qpTotal > 0) AND sits in the
|
||||||
// so managers are shown by plain username, never as a <@id> mention.
|
// top 8 of the FULL season standings (see `inTopEight`).
|
||||||
const scored = [...scoreboard]
|
const topEight = [...scoreboard]
|
||||||
.filter((e) => e.qpTotal > 0)
|
.filter(inTopEight)
|
||||||
.toSorted((a, b) => a.globalRank - b.globalRank);
|
.toSorted((a, b) => a.globalRank - b.globalRank);
|
||||||
|
|
||||||
// Skip the section entirely when no drafted participant has scored (e.g. a sync that only
|
// Skip the section entirely when no drafted participant is in the top 8 (e.g. a
|
||||||
// reported knockouts) so we don't emit an empty header.
|
// sync that only reported knockouts) so we don't emit an empty header.
|
||||||
if (scored.length > 0) {
|
if (topEight.length > 0) {
|
||||||
sections.push("\n**Drafted Participants**");
|
sections.push("\n**Drafted Participants in Top 8**");
|
||||||
// Insert the divider once, before the first below-the-cutoff (rank >= 9) row. `>= 9`
|
for (const e of topEight) {
|
||||||
// (not `> 8`) keeps a tie AT rank 8 above the bubble ("top 8 plus ties"). Only emit it
|
|
||||||
// after at least one above-the-bubble row exists: globalRank is a season-wide rank while
|
|
||||||
// this list is scoped to one league's drafts, so a league can have drafted nobody in the
|
|
||||||
// global top 8 — guarding on rowsAbove avoids a leading divider with nothing above it.
|
|
||||||
let bubbleInserted = false;
|
|
||||||
let rowsAbove = 0;
|
|
||||||
for (const e of scored) {
|
|
||||||
if (!bubbleInserted && rowsAbove > 0 && e.globalRank >= 9) {
|
|
||||||
sections.push("**═══ Points Bubble ═══**");
|
|
||||||
bubbleInserted = true;
|
|
||||||
}
|
|
||||||
if (e.globalRank <= 8) rowsAbove++;
|
|
||||||
const rankPrefix = e.globalRankTied ? `T${e.globalRank}` : `${e.globalRank}`;
|
const rankPrefix = e.globalRankTied ? `T${e.globalRank}` : `${e.globalRank}`;
|
||||||
|
// Plain manager username (no <@id> mention): this section is not pinged.
|
||||||
const managerLabel = e.ownerUsername ? ` (${escapeMarkdown(e.ownerUsername)})` : "";
|
const managerLabel = e.ownerUsername ? ` (${escapeMarkdown(e.ownerUsername)})` : "";
|
||||||
sections.push(`${rankPrefix}\\. ${escapeMarkdown(e.participantName)}${managerLabel} — ${formatQPValue(e.qpTotal)} QP`);
|
sections.push(`${rankPrefix}\\. ${escapeMarkdown(e.participantName)}${managerLabel} — ${formatQPValue(e.qpTotal)} QP`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-scoring section — the rest of the drafted field (everyone not in the top 8, i.e.
|
||||||
|
// the exact complement of `inTopEight`: rank 9+, unranked, or scored-nothing players
|
||||||
|
// tied into the top-8 rank band). Rendered as a single compact comma-separated line of
|
||||||
|
// "Name (points, manager)", highest QP total first. Never pinged, so managers are shown
|
||||||
|
// by plain username, never as a <@id> mention.
|
||||||
|
const nonTopEight = [...scoreboard]
|
||||||
|
.filter((e) => !inTopEight(e))
|
||||||
|
.toSorted((a, b) => b.qpTotal - a.qpTotal);
|
||||||
|
|
||||||
|
if (nonTopEight.length > 0) {
|
||||||
|
sections.push("\n**Non-scoring Participants**");
|
||||||
|
const parts = nonTopEight.map((e) => {
|
||||||
|
const details = e.ownerUsername
|
||||||
|
? `${formatQPValue(e.qpTotal)}, ${escapeMarkdown(e.ownerUsername)}`
|
||||||
|
: `${formatQPValue(e.qpTotal)}`;
|
||||||
|
return `${escapeMarkdown(e.participantName)} (${details})`;
|
||||||
|
});
|
||||||
|
sections.push(parts.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_DESCRIPTION = 4096;
|
const MAX_DESCRIPTION = 4096;
|
||||||
let description = sections.join("\n");
|
let description = sections.join("\n");
|
||||||
if (description.length > MAX_DESCRIPTION) {
|
if (description.length > MAX_DESCRIPTION) {
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,8 @@ 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 participant display names once (same participants across all leagues).
|
// Batch-fetch participant display names once (same participants across all leagues).
|
||||||
// Includes every drafted participant — the Drafted Participants scoreboard section
|
// Includes every drafted participant — the scoreboard sections (Top 8 / Non-scoring)
|
||||||
// lists the whole scored field, not just this sync's changed participants.
|
// list the whole drafted field, not just this sync's changed participants.
|
||||||
const allParticipantIds = [
|
const allParticipantIds = [
|
||||||
...new Set([...qpEarnedById.keys(), ...eliminatedIds, ...draftedParticipantIds]),
|
...new Set([...qpEarnedById.keys(), ...eliminatedIds, ...draftedParticipantIds]),
|
||||||
];
|
];
|
||||||
|
|
@ -137,7 +137,7 @@ export async function notifyQualifyingPointsUpdate(
|
||||||
const participantNameById = new Map(participants.map((p) => [p.id, p.name]));
|
const participantNameById = new Map(participants.map((p) => [p.id, p.name]));
|
||||||
// A league's draft picks span every sport in that fantasy season, so the scoreboard
|
// A league's draft picks span every sport in that fantasy season, so the scoreboard
|
||||||
// must be scoped to participants belonging to the sports season being announced —
|
// must be scoped to participants belonging to the sports season being announced —
|
||||||
// otherwise a golf pick would surface in a tennis event's standings section.
|
// otherwise a golf pick would surface in a tennis event's Non-scoring line.
|
||||||
const sportsSeasonParticipantIds = new Set(
|
const sportsSeasonParticipantIds = new Set(
|
||||||
participants.filter((p) => p.sportsSeasonId === sportsSeasonId).map((p) => p.id)
|
participants.filter((p) => p.sportsSeasonId === sportsSeasonId).map((p) => p.id)
|
||||||
);
|
);
|
||||||
|
|
@ -219,8 +219,8 @@ export async function notifyQualifyingPointsUpdate(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Full current scoreboard for this league: every drafted participant, regardless of
|
// Full current scoreboard for this league: every drafted participant, regardless of
|
||||||
// whether their QP changed this sync. Drives the Drafted Participants section so
|
// whether their QP changed this sync. Drives the Top 8 and Non-scoring sections so
|
||||||
// it reads as a season-standings snapshot rather than only this event's movers.
|
// they read as a season-standings snapshot rather than only this event's movers.
|
||||||
// Never pinged, so ownerDiscordUserId is intentionally omitted.
|
// Never pinged, so ownerDiscordUserId is intentionally omitted.
|
||||||
const scoreboard: QPEventEntry[] = [...teamByParticipantId.keys()]
|
const scoreboard: QPEventEntry[] = [...teamByParticipantId.keys()]
|
||||||
.filter((participantId) => sportsSeasonParticipantIds.has(participantId))
|
.filter((participantId) => sportsSeasonParticipantIds.has(participantId))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue