2026-09-04 21:30:57 +00:00
|
|
|
/**
|
|
|
|
|
* Repair: re-seed AFL Wildcard winners into the Elimination Finals they belong in.
|
|
|
|
|
*
|
|
|
|
|
* Brackets advanced before the re-seeding fix crossed each Wildcard winner into a fixed
|
|
|
|
|
* Elimination Final — the 7v10 winner always met 6th and the 8v9 winner always met 5th —
|
|
|
|
|
* instead of pairing them by ladder position (5th hosts the lower-ranked winner). The
|
|
|
|
|
* fix only changes how new results advance, so an already-advanced bracket keeps its
|
|
|
|
|
* wrong pairings until this runs. The admin UI cannot re-trigger it: a completed match
|
|
|
|
|
* renders as "Complete", with no way to re-submit the winner.
|
|
|
|
|
*
|
|
|
|
|
* This runs the same reseedAflEliminationFinals the advancement path now uses, so it
|
|
|
|
|
* makes exactly the correction a fresh bracket would have. It only ever moves Wildcard
|
|
|
|
|
* teams between the two Elimination Final slots — no results, scores or placements are
|
|
|
|
|
* touched, and nothing else in the bracket is written. A bracket that is already correct
|
|
|
|
|
* is left alone.
|
|
|
|
|
*
|
2026-09-04 21:40:08 +00:00
|
|
|
* Admin → the event's bracket has a "Re-seed Wildcard Winners" button that does exactly
|
|
|
|
|
* this for one event; use this script to sweep every afl_10 event, or where the UI is not
|
|
|
|
|
* to hand.
|
|
|
|
|
*
|
2026-09-04 21:30:57 +00:00
|
|
|
* If an Elimination Final has already been played, its qualifier cannot be moved without
|
|
|
|
|
* rewriting who contested a recorded result; the script reports that event and skips it.
|
|
|
|
|
* Clear and regenerate that bracket in Admin instead, then Reprocess Bracket.
|
|
|
|
|
*
|
|
|
|
|
* Safe to re-run. Validate on a DB snapshot first. Reads DATABASE_URL.
|
|
|
|
|
*
|
|
|
|
|
* npx tsx scripts/fix-afl-wildcard-reseed.ts # apply to every afl_10 event
|
|
|
|
|
* npx tsx scripts/fix-afl-wildcard-reseed.ts --dry # report only
|
|
|
|
|
* npx tsx scripts/fix-afl-wildcard-reseed.ts --event <id> # one event
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
|
|
|
import postgres from "postgres";
|
|
|
|
|
import { eq } from "drizzle-orm";
|
|
|
|
|
import * as schema from "../database/schema.js";
|
|
|
|
|
import { DatabaseContext, database } from "../database/context.js";
|
|
|
|
|
import {
|
|
|
|
|
findPlayoffMatchesByEventIdAndRound,
|
|
|
|
|
reseedAflEliminationFinals,
|
|
|
|
|
} from "../app/models/playoff-match.js";
|
|
|
|
|
import { findParticipantsBySportsSeasonId } from "../app/models/season-participant.js";
|
|
|
|
|
|
|
|
|
|
const DRY = process.argv.includes("--dry");
|
|
|
|
|
const eventFlag = process.argv.indexOf("--event");
|
|
|
|
|
const ONLY_EVENT = eventFlag === -1 ? null : process.argv[eventFlag + 1];
|
|
|
|
|
const log = (...a: unknown[]) => console.log(...a);
|
|
|
|
|
|
|
|
|
|
async function run() {
|
|
|
|
|
const db = database();
|
|
|
|
|
|
|
|
|
|
const events = await db.query.scoringEvents.findMany({
|
|
|
|
|
where: eq(schema.scoringEvents.bracketTemplateId, "afl_10"),
|
|
|
|
|
});
|
|
|
|
|
const targets = ONLY_EVENT ? events.filter((e) => e.id === ONLY_EVENT) : events;
|
|
|
|
|
|
|
|
|
|
if (ONLY_EVENT && targets.length === 0) {
|
|
|
|
|
log(`No afl_10 event with id ${ONLY_EVENT}.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
log(`afl_10 events to check: ${targets.length}`);
|
|
|
|
|
|
|
|
|
|
let fixed = 0;
|
|
|
|
|
let alreadyRight = 0;
|
|
|
|
|
let skipped = 0;
|
|
|
|
|
|
|
|
|
|
for (const event of targets) {
|
|
|
|
|
const name = event.name ?? event.id;
|
|
|
|
|
|
|
|
|
|
const participants = await findParticipantsBySportsSeasonId(event.sportsSeasonId);
|
|
|
|
|
const nameOf = (id: string | null) =>
|
|
|
|
|
id === null ? "TBD" : participants.find((p) => p.id === id)?.name ?? id;
|
|
|
|
|
|
|
|
|
|
/** "M1: <host> vs <qualifier>" for both Elimination Finals. */
|
|
|
|
|
const pairings = async () => {
|
|
|
|
|
const efMatches = await findPlayoffMatchesByEventIdAndRound(event.id, "Elimination Finals");
|
|
|
|
|
return efMatches
|
|
|
|
|
.toSorted((a, b) => a.matchNumber - b.matchNumber)
|
|
|
|
|
.map((m) => `M${m.matchNumber}: ${nameOf(m.participant1Id)} vs ${nameOf(m.participant2Id)}`)
|
|
|
|
|
.join(", ");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const before = await pairings();
|
|
|
|
|
|
|
|
|
|
// The dry run still resolves the pairings — it just reports them instead of writing.
|
|
|
|
|
if (DRY) {
|
|
|
|
|
const efMatches = await findPlayoffMatchesByEventIdAndRound(event.id, "Elimination Finals");
|
|
|
|
|
const wcMatches = await findPlayoffMatchesByEventIdAndRound(event.id, "Wildcard Round");
|
|
|
|
|
const decided = wcMatches.filter((m) => m.isComplete && m.winnerId).length;
|
|
|
|
|
log(` ${name}: ${before} (${decided}/${wcMatches.length} Wildcard results, ` +
|
|
|
|
|
`${efMatches.filter((m) => m.isComplete).length} Elimination Final(s) played)`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reseed = await reseedAflEliminationFinals(event.id);
|
|
|
|
|
if (reseed.vacated.length === 0 && reseed.filled.length === 0) {
|
|
|
|
|
alreadyRight += 1;
|
|
|
|
|
log(` = ${name}: already correct — ${before}`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fixed += 1;
|
|
|
|
|
log(` ~ ${name}:`);
|
|
|
|
|
log(` was: ${before}`);
|
|
|
|
|
log(` now: ${await pairings()}`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
skipped += 1;
|
|
|
|
|
log(` ! ${name}: ${(e as Error).message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DRY) {
|
|
|
|
|
log("\nDry run — no writes. Re-run without --dry to apply.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
log(`\nDone. re-seeded=${fixed}, already correct=${alreadyRight}, skipped=${skipped}.`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
const dbUrl = process.env.DATABASE_URL;
|
|
|
|
|
if (!dbUrl) {
|
|
|
|
|
console.error("ERROR: DATABASE_URL is required");
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
const client = postgres(dbUrl, { max: 1 });
|
|
|
|
|
const db = drizzle(client, { schema });
|
|
|
|
|
try {
|
|
|
|
|
await DatabaseContext.run(db, run);
|
|
|
|
|
} finally {
|
|
|
|
|
await client.end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main().catch((e) => {
|
|
|
|
|
console.error(e);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
});
|