Compare commits
2 commits
5d0363a309
...
7e578c714c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e578c714c | |||
|
|
687fb6f040 |
4 changed files with 117 additions and 109 deletions
|
|
@ -790,10 +790,9 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
expect(desc.indexOf("Novak Djokovic")).toBeLessThan(desc.indexOf("Carlos Alcaraz"));
|
expect(desc.indexOf("Novak Djokovic")).toBeLessThan(desc.indexOf("Carlos Alcaraz"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists non-scoring participants (outside top 8) as one comma-separated line below Top 8", async () => {
|
it("omits zero-QP drafted participants entirely from the Drafted Participants section", async () => {
|
||||||
// Non-scoring now draws from the full scoreboard: every drafted participant NOT in
|
// Only participants who have actually scored (qpTotal > 0) are listed; a 0-QP drafted
|
||||||
// the top 8 (rank 9+ or unranked), rendered on a single line "Name (total, manager)"
|
// player no longer appears anywhere in the standings section.
|
||||||
// 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",
|
||||||
|
|
@ -808,45 +807,19 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Non-scoring Participants**");
|
expect(desc).toContain("**Drafted Participants**");
|
||||||
// Single comma-separated line: "Name (seasonTotal, manager)", higher total first.
|
// Both scorers appear as ranked rows...
|
||||||
expect(desc).toContain("Also Ran (5, chris), Winless Wonder (0, sam)");
|
expect(desc).toContain("1\\. Champ (alex) — 100 QP");
|
||||||
// Champ is in the top 8, so it must NOT appear in the non-scoring line.
|
expect(desc).toContain("9\\. Also Ran (chris) — 5 QP");
|
||||||
expect(desc).not.toContain("Champ (100");
|
// ...with a Points Bubble divider separating the rank-9 scorer.
|
||||||
// Non-scoring appears below the Top 8 section.
|
expect(desc).toContain("**═══ Points Bubble ═══**");
|
||||||
expect(desc.indexOf("Drafted Participants in Top 8")).toBeLessThan(
|
// The 0-QP player is omitted entirely.
|
||||||
desc.indexOf("Non-scoring Participants")
|
expect(desc).not.toContain("Winless Wonder");
|
||||||
);
|
// The old Non-scoring section is gone.
|
||||||
|
expect(desc).not.toContain("Non-scoring Participants");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps zero-QP participants tied into the top-8 rank band out of the Top 8 section", async () => {
|
it("shows the Drafted Participants section for scored participants sorted by rank", 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",
|
||||||
|
|
@ -855,12 +828,61 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Drafted Participants in Top 8**");
|
expect(desc).toContain("**Drafted Participants**");
|
||||||
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 () => {
|
||||||
|
|
@ -915,9 +937,9 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
expect(desc).not.toContain("1.50");
|
expect(desc).not.toContain("1.50");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("excludes participants ranked outside the top 8 from the Drafted Participants section", async () => {
|
it("lists a rank-9 scorer below the Points Bubble but omits a 0-QP participant", async () => {
|
||||||
// Only the top 8 (plus ties) of the full season field belong in this section, so a
|
// Rank-9 scorers now appear in the standings section (below the bubble), while a drafted
|
||||||
// rank-9 scorer must NOT appear in it — even though it still shows in Points Awarded.
|
// participant with no points is dropped entirely.
|
||||||
const both = [
|
const both = [
|
||||||
{
|
{
|
||||||
participantName: "Player Eight",
|
participantName: "Player Eight",
|
||||||
|
|
@ -935,6 +957,14 @@ 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,
|
||||||
|
|
@ -946,13 +976,14 @@ describe("sendQualifyingPointsUpdateNotification", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = getDescription();
|
const desc = getDescription();
|
||||||
expect(desc).toContain("**Drafted Participants in Top 8**");
|
expect(desc).toContain("**Drafted Participants**");
|
||||||
expect(desc).toContain("8\\. Player Eight (eighthowner) — 10 QP");
|
expect(desc).toContain("8\\. Player Eight (eighthowner) — 10 QP");
|
||||||
// The rank-9 player is filtered out of the standings section entirely.
|
// The rank-9 scorer now appears as a ranked row below the bubble.
|
||||||
expect(desc).not.toContain("9\\. Player Nine");
|
expect(desc).toContain("**═══ Points Bubble ═══**");
|
||||||
// ...instead the rank-9 player drops into the Non-scoring line.
|
expect(desc).toContain("9\\. Player Nine (ninthowner) — 8 QP");
|
||||||
expect(desc).toContain("Player Nine (8, ninthowner)");
|
// The 0-QP player is omitted from the standings section entirely.
|
||||||
// ...and both still earned points, so both remain in Points Awarded.
|
expect(desc).not.toContain("Player Winless");
|
||||||
|
// ...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");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -982,7 +1013,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) Top 8 standings section uses the plain username.
|
// ...while the (non-pinged) Drafted Participants standings section uses the plain username.
|
||||||
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
expect(desc).toContain("1\\. Novak Djokovic (alex) — 45 QP");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1037,7 +1068,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 in Top 8**");
|
expect(desc).not.toContain("**Drafted Participants**");
|
||||||
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 Top 8 / Non-scoring sections and must reflect the full
|
// The scoreboard powers the Drafted Participants section 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,20 +288,6 @@ 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,
|
||||||
|
|
@ -320,8 +306,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 "Top 8" and "Non-scoring Participants"
|
* those whose QP changed this sync. Drives the "Drafted Participants" standings section.
|
||||||
* sections. `entries`/`eliminated` remain scoped to this sync's changes and drive the
|
* `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[];
|
||||||
|
|
@ -372,48 +358,39 @@ export async function sendQualifyingPointsUpdateNotification({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scoreboard sections below draw from the FULL drafted field (`scoreboard`), not just
|
// Drafted Participants: every drafted participant that has actually scored (qpTotal > 0),
|
||||||
// this sync's movers, so they read as a live standings snapshot. The two sections
|
// drawn from the FULL drafted field (`scoreboard`) not just this sync's movers, so it reads
|
||||||
// partition the field exactly via `inTopEight`, so nobody is dropped or listed twice.
|
// as a live standings snapshot. Rendered as ranked lines ordered by full-season standing. A
|
||||||
|
// "Points Bubble" divider marks the cutoff between those currently in the points (rank <= 8)
|
||||||
// Top 8 = a drafted participant that has actually scored (qpTotal > 0) AND sits in the
|
// and those below it (rank >= 9). Participants with 0 QP are omitted entirely. Never pinged,
|
||||||
// top 8 of the FULL season standings (see `inTopEight`).
|
// so managers are shown by plain username, never as a <@id> mention.
|
||||||
const topEight = [...scoreboard]
|
const scored = [...scoreboard]
|
||||||
.filter(inTopEight)
|
.filter((e) => e.qpTotal > 0)
|
||||||
.toSorted((a, b) => a.globalRank - b.globalRank);
|
.toSorted((a, b) => a.globalRank - b.globalRank);
|
||||||
|
|
||||||
// Skip the section entirely when no drafted participant is in the top 8 (e.g. a
|
// Skip the section entirely when no drafted participant has scored (e.g. a sync that only
|
||||||
// sync that only reported knockouts) so we don't emit an empty header.
|
// reported knockouts) so we don't emit an empty header.
|
||||||
if (topEight.length > 0) {
|
if (scored.length > 0) {
|
||||||
sections.push("\n**Drafted Participants in Top 8**");
|
sections.push("\n**Drafted Participants**");
|
||||||
for (const e of topEight) {
|
// Insert the divider once, before the first below-the-cutoff (rank >= 9) row. `>= 9`
|
||||||
|
// (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 scoreboard sections (Top 8 / Non-scoring)
|
// Includes every drafted participant — the Drafted Participants scoreboard section
|
||||||
// list the whole drafted field, not just this sync's changed participants.
|
// lists the whole scored 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 Non-scoring line.
|
// otherwise a golf pick would surface in a tennis event's standings section.
|
||||||
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 Top 8 and Non-scoring sections so
|
// whether their QP changed this sync. Drives the Drafted Participants section so
|
||||||
// they read as a season-standings snapshot rather than only this event's movers.
|
// it reads 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