From 08e29d41f9ad1473b62cb1bdea07cb4da5f9b23c Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 12 Jun 2026 21:24:52 +0300 Subject: [PATCH] Remove unused scripts --- .../core/summarizer.server.ts | 4 +- app/features/tournament/core/tiering.ts | 2 +- scripts/add-tos.ts | 15 - .../backfill-tournament-result-divisions.ts | 135 ------ scripts/backfill-tournament-tiers.ts | 258 ----------- scripts/calc-seeding-skills.ts | 89 ---- .../calc-tournament-summary-result-arrays.ts | 191 -------- scripts/delete-skills-without-results.ts | 34 -- scripts/delete-tournament-team-maps.ts | 23 - scripts/disband-team.ts | 23 - scripts/fc-lookup.ts | 37 -- scripts/map-popularity-q.ts | 126 ------ scripts/map-popularity.ts | 108 ----- scripts/migrate-tournament-logos-to-s3.ts | 170 -------- scripts/nuke-season-data.ts | 44 -- scripts/recent-fcs.ts | 58 --- scripts/sync-splatoon-rotations.ts | 18 - scripts/sync-tournament-vods.ts | 14 - scripts/sync-xp-badges.ts | 7 - scripts/tournament-tiers-experiment.ts | 406 ------------------ scripts/transfer-weapon-pools.ts | 47 -- scripts/unlock-league-matches.ts | 69 --- scripts/update-event-name.ts | 34 -- 23 files changed, 3 insertions(+), 1909 deletions(-) delete mode 100644 scripts/add-tos.ts delete mode 100644 scripts/backfill-tournament-result-divisions.ts delete mode 100644 scripts/backfill-tournament-tiers.ts delete mode 100644 scripts/calc-seeding-skills.ts delete mode 100644 scripts/calc-tournament-summary-result-arrays.ts delete mode 100644 scripts/delete-skills-without-results.ts delete mode 100644 scripts/delete-tournament-team-maps.ts delete mode 100644 scripts/disband-team.ts delete mode 100644 scripts/fc-lookup.ts delete mode 100644 scripts/map-popularity-q.ts delete mode 100644 scripts/map-popularity.ts delete mode 100644 scripts/migrate-tournament-logos-to-s3.ts delete mode 100644 scripts/nuke-season-data.ts delete mode 100644 scripts/recent-fcs.ts delete mode 100644 scripts/sync-splatoon-rotations.ts delete mode 100644 scripts/sync-tournament-vods.ts delete mode 100644 scripts/sync-xp-badges.ts delete mode 100644 scripts/tournament-tiers-experiment.ts delete mode 100644 scripts/transfer-weapon-pools.ts delete mode 100644 scripts/unlock-league-matches.ts delete mode 100644 scripts/update-event-name.ts diff --git a/app/features/tournament-bracket/core/summarizer.server.ts b/app/features/tournament-bracket/core/summarizer.server.ts index f47b9c5bb..46dd1a3f1 100644 --- a/app/features/tournament-bracket/core/summarizer.server.ts +++ b/app/features/tournament-bracket/core/summarizer.server.ts @@ -143,7 +143,7 @@ function calculateSkills(args: { return result; } -export function calculateIndividualPlayerSkills({ +function calculateIndividualPlayerSkills({ results, queryCurrentUserRating, }: { @@ -645,7 +645,7 @@ function spDiffs({ return spDiffs; } -export function setResults({ +function setResults({ results, teams, }: { diff --git a/app/features/tournament/core/tiering.ts b/app/features/tournament/core/tiering.ts index a4c0459b9..0fe39d06f 100644 --- a/app/features/tournament/core/tiering.ts +++ b/app/features/tournament/core/tiering.ts @@ -15,7 +15,7 @@ export const TIER_THRESHOLDS = { C: Number.NEGATIVE_INFINITY, } as const; -export const TOP_TEAMS_COUNT = 8; +const TOP_TEAMS_COUNT = 8; export const MIN_TEAMS_FOR_TIERING = 8; export const TIER_HISTORY_LENGTH = 5; diff --git a/scripts/add-tos.ts b/scripts/add-tos.ts deleted file mode 100644 index 8fac49ee0..000000000 --- a/scripts/add-tos.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as AdminRepository from "~/features/admin/AdminRepository.server"; -import { logger } from "~/utils/logger"; - -async function main() { - const input = process.argv[2]?.trim(); - - const userIds = input.split(",").map((id) => Number(id)); - - for (const userId of userIds) { - await AdminRepository.makeTournamentOrganizerByUserId(userId); - } - logger.info(`Added TOs: ${userIds}`); -} - -main(); diff --git a/scripts/backfill-tournament-result-divisions.ts b/scripts/backfill-tournament-result-divisions.ts deleted file mode 100644 index 7f9175e56..000000000 --- a/scripts/backfill-tournament-result-divisions.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { db } from "../app/db/sql"; -import * as Seasons from "../app/features/mmr/core/Seasons"; -import { - queryCurrentTeamRating, - queryCurrentUserRating, - queryCurrentUserSeedingRating, - queryTeamPlayerRatingAverage, -} from "../app/features/mmr/mmr-utils.server"; -import * as Standings from "../app/features/tournament/core/Standings"; -import { tournamentSummary } from "../app/features/tournament-bracket/core/summarizer.server"; -import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server"; -import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server"; -import invariant from "../app/utils/invariant"; -import { logger } from "../app/utils/logger"; - -async function main() { - logger.info("Starting to backfill tournament result divisions"); - - const tournaments = await db - .selectFrom("Tournament") - .select("id") - .where("isFinalized", "=", 1) - .execute(); - - let recalculatedCount = 0; - let skippedCount = 0; - - for (const { id: tournamentId } of tournaments) { - try { - const tournament = await tournamentFromDB({ - tournamentId, - user: undefined, - }); - - const uniqueStartingBracketIndexes = new Set( - tournament.ctx.teams - .map((team) => team.startingBracketIdx) - .filter((idx) => idx !== null && idx !== undefined), - ); - - if (uniqueStartingBracketIndexes.size <= 1) { - skippedCount++; - continue; - } - - recalculatedCount++; - - await db - .deleteFrom("TournamentResult") - .where("tournamentId", "=", tournamentId) - .execute(); - - const results = - await TournamentMatchRepository.allResultsByTournamentId(tournamentId); - invariant(results.length > 0, "No results found"); - - const season = Seasons.current(tournament.ctx.startTime)?.nth; - const seedingSkillCountsFor = tournament.skillCountsFor; - - const standingsResult = Standings.tournamentStandings(tournament); - if (standingsResult.type === "single") { - throw new Error( - `Expected multiple starting brackets for tournament ${tournamentId}`, - ); - } - const finalStandings = Standings.flattenStandings(standingsResult); - const summary = tournamentSummary({ - teams: tournament.ctx.teams, - finalStandings, - results, - calculateSeasonalStats: false, - queryCurrentTeamRating: (identifier) => - queryCurrentTeamRating({ identifier, season: season! }).rating, - queryCurrentUserRating: (userId) => - queryCurrentUserRating({ userId, season: season! }), - queryTeamPlayerRatingAverage: (identifier) => - queryTeamPlayerRatingAverage({ - identifier, - season: season!, - }), - queryCurrentSeedingRating: (userId) => - queryCurrentUserSeedingRating({ - userId, - type: seedingSkillCountsFor!, - }), - seedingSkillCountsFor, - progression: tournament.ctx.settings.bracketProgression, - }); - - logger.info( - `Inserting ${summary.tournamentResults.length} results for tournament ${tournamentId}`, - ); - for (const tournamentResult of summary.tournamentResults) { - const setResults = summary.setResults.get(tournamentResult.userId); - - if (setResults?.every((result) => !result)) { - continue; - } - - await db - .insertInto("TournamentResult") - .values({ - tournamentId, - userId: tournamentResult.userId, - placement: tournamentResult.placement, - participantCount: tournamentResult.participantCount, - tournamentTeamId: tournamentResult.tournamentTeamId, - setResults: setResults ? JSON.stringify(setResults) : "[]", - spDiff: null, - div: tournamentResult.div, - }) - .execute(); - } - - if (recalculatedCount % 10 === 0) { - logger.info( - `Processed ${recalculatedCount} tournaments with multiple starting brackets (skipped ${skippedCount})`, - ); - } - } catch (thrown) { - if (thrown instanceof Response) continue; - - logger.error(`Error processing tournament ${tournamentId}`, thrown); - } - } - - logger.info( - `Done. Recalculated ${recalculatedCount} tournaments with multiple starting brackets. Skipped ${skippedCount} tournaments.`, - ); -} - -main().catch((err) => { - logger.error("Error in backfill-tournament-result-divisions.ts", err); - process.exit(1); -}); diff --git a/scripts/backfill-tournament-tiers.ts b/scripts/backfill-tournament-tiers.ts deleted file mode 100644 index 9e01dce6e..000000000 --- a/scripts/backfill-tournament-tiers.ts +++ /dev/null @@ -1,258 +0,0 @@ -/** biome-ignore-all lint/suspicious/noConsole: CLI script output */ -/** - * Backfill Tournament Tiers Script - * - * Run with: pnpm exec vite-node scripts/backfill-tournament-tiers.ts - * - * Retroactively calculates and sets tiers for all finalized tournaments, - * then populates series tier history based on those tiers. - */ - -import { sql } from "~/db/sql"; -import { - calculateAdjustedScore, - calculateTierNumber, - MIN_TEAMS_FOR_TIERING, - TIER_HISTORY_LENGTH, - TOP_TEAMS_COUNT, -} from "../app/features/tournament/core/tiering"; - -const dryRun = process.argv.includes("--dry-run"); - -interface TournamentScore { - tournamentId: number; - teamCount: number; - top8AvgOrdinal: number | null; -} - -interface TournamentWithOrg { - tournamentId: number; - name: string; - organizationId: number | null; - startTime: number; -} - -interface Series { - id: number; - organizationId: number; - substringMatches: string; -} - -function getTournamentScores(): TournamentScore[] { - const query = ` - WITH TeamSkills AS ( - SELECT - tt.tournamentId, - tt.id as teamId, - AVG(ss.ordinal) as avg_team_ordinal - FROM TournamentTeam tt - JOIN TournamentTeamMember ttm ON ttm.tournamentTeamId = tt.id - LEFT JOIN SeedingSkill ss ON ss.userId = ttm.userId AND ss.type = 'RANKED' - WHERE tt.droppedOut = 0 - GROUP BY tt.tournamentId, tt.id - ), - TeamCounts AS ( - SELECT tournamentId, COUNT(*) as team_count - FROM TeamSkills - WHERE avg_team_ordinal IS NOT NULL - GROUP BY tournamentId - ), - RankedTeams AS ( - SELECT - ts.tournamentId, - ts.avg_team_ordinal, - tc.team_count, - ROW_NUMBER() OVER (PARTITION BY ts.tournamentId ORDER BY ts.avg_team_ordinal DESC) as rank - FROM TeamSkills ts - JOIN TeamCounts tc ON tc.tournamentId = ts.tournamentId - WHERE ts.avg_team_ordinal IS NOT NULL - ), - TournamentScores AS ( - SELECT - tournamentId, - AVG(avg_team_ordinal) as top8_avg_ordinal, - MAX(team_count) as team_count - FROM RankedTeams - WHERE rank <= ${TOP_TEAMS_COUNT} - GROUP BY tournamentId - ) - SELECT - t.id as tournamentId, - COALESCE(ts.team_count, 0) as teamCount, - ts.top8_avg_ordinal as top8AvgOrdinal - FROM Tournament t - LEFT JOIN TournamentScores ts ON ts.tournamentId = t.id - WHERE t.isFinalized = 1 - `; - - return sql.prepare(query).all() as TournamentScore[]; -} - -function getTournamentsWithOrg(): TournamentWithOrg[] { - const query = /* sql */ ` - SELECT - t.id as tournamentId, - ce.name, - ce.organizationId, - ced.startTime - FROM Tournament t - INNER JOIN CalendarEvent ce ON ce.tournamentId = t.id - INNER JOIN CalendarEventDate ced ON ced.eventId = ce.id - WHERE t.isFinalized = 1 - AND ce.hidden = 0 - ORDER BY ced.startTime ASC - `; - return sql.prepare(query).all() as TournamentWithOrg[]; -} - -function getAllSeries(): Series[] { - const query = /* sql */ ` - SELECT id, organizationId, substringMatches - FROM TournamentOrganizationSeries - `; - return sql.prepare(query).all() as Series[]; -} - -function matchesSubstring( - eventName: string, - substringMatches: string[], -): boolean { - const eventNameLower = eventName.toLowerCase(); - return substringMatches.some((match) => - eventNameLower.includes(match.toLowerCase()), - ); -} - -function main() { - console.log("=== Backfilling Tournament Tiers ===\n"); - if (dryRun) { - console.log("DRY RUN - no changes will be made\n"); - } - - const tournaments = getTournamentScores(); - console.log(`Found ${tournaments.length} finalized tournaments\n`); - - const updateTierStatement = sql.prepare( - /* sql */ `UPDATE "Tournament" SET tier = @tier WHERE id = @tournamentId`, - ); - - const tierCounts: Record = {}; - const tournamentTiers = new Map(); - let updatedCount = 0; - let skippedCount = 0; - - for (const t of tournaments) { - const meetsMinTeams = t.teamCount >= MIN_TEAMS_FOR_TIERING; - - let tierNumber: number | null = null; - if (t.top8AvgOrdinal !== null && meetsMinTeams) { - const adjustedScore = calculateAdjustedScore( - t.top8AvgOrdinal, - t.teamCount, - ); - tierNumber = calculateTierNumber(adjustedScore); - } - - if (tierNumber !== null) { - tierCounts[tierNumber] = (tierCounts[tierNumber] || 0) + 1; - tournamentTiers.set(t.tournamentId, tierNumber); - updatedCount++; - } else { - skippedCount++; - } - - if (!dryRun) { - updateTierStatement.run({ - tier: tierNumber, - tournamentId: t.tournamentId, - }); - } - } - - console.log("Tier distribution:"); - const tierNames: Record = { - 1: "X", - 2: "S+", - 3: "S", - 4: "A+", - 5: "A", - 6: "B+", - 7: "B", - 8: "C+", - 9: "C", - }; - for (let i = 1; i <= 9; i++) { - console.log(` ${tierNames[i]}: ${tierCounts[i] || 0}`); - } - - console.log(`\nUpdated: ${updatedCount} tournaments`); - console.log(`Skipped (untiered): ${skippedCount} tournaments`); - - console.log("\n=== Backfilling Series Tier History ===\n"); - - const allSeries = getAllSeries(); - const tournamentsWithOrg = getTournamentsWithOrg(); - console.log(`Found ${allSeries.length} series`); - console.log( - `Found ${tournamentsWithOrg.filter((t) => t.organizationId !== null).length} tournaments with organizations\n`, - ); - - const updateSeriesStatement = sql.prepare( - /* sql */ "UPDATE TournamentOrganizationSeries SET tierHistory = @tierHistory WHERE id = @seriesId", - ); - - const seriesByOrg = new Map(); - for (const series of allSeries) { - const existing = seriesByOrg.get(series.organizationId) ?? []; - existing.push(series); - seriesByOrg.set(series.organizationId, existing); - } - - let seriesUpdatedCount = 0; - let seriesSkippedCount = 0; - - for (const [organizationId, orgSeries] of seriesByOrg.entries()) { - const orgTournaments = tournamentsWithOrg.filter( - (t) => t.organizationId === organizationId, - ); - - for (const series of orgSeries) { - const substringMatches = JSON.parse(series.substringMatches) as string[]; - const matchingTournaments = orgTournaments - .filter((t) => matchesSubstring(t.name, substringMatches)) - .filter((t) => tournamentTiers.has(t.tournamentId)); - - if (matchingTournaments.length === 0) { - seriesSkippedCount++; - continue; - } - - const tierHistory = matchingTournaments - .slice(-TIER_HISTORY_LENGTH) - .map((t) => tournamentTiers.get(t.tournamentId)!); - - console.log( - `Series ${series.id} (org ${organizationId}): ${matchingTournaments.length} matching tournaments, tierHistory = [${tierHistory.join(", ")}]`, - ); - - if (!dryRun) { - updateSeriesStatement.run({ - seriesId: series.id, - tierHistory: JSON.stringify(tierHistory), - }); - } - seriesUpdatedCount++; - } - } - - console.log(`\nSeries updated: ${seriesUpdatedCount}`); - console.log( - `Series skipped (no matching tournaments): ${seriesSkippedCount}`, - ); - - if (dryRun) { - console.log("\nRun without --dry-run to apply changes"); - } -} - -main(); diff --git a/scripts/calc-seeding-skills.ts b/scripts/calc-seeding-skills.ts deleted file mode 100644 index 7c6acd152..000000000 --- a/scripts/calc-seeding-skills.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ordinal, type Rating, rating } from "openskill"; -import { db } from "../app/db/sql"; -import type { Tables } from "../app/db/tables"; -import { calculateIndividualPlayerSkills } from "../app/features/tournament-bracket/core/summarizer.server"; -import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server"; -import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server"; -import invariant from "../app/utils/invariant"; -import { logger } from "../app/utils/logger"; - -async function main() { - const result: Tables["SeedingSkill"][] = []; - - for (const type of ["RANKED", "UNRANKED"] as const) { - const ratings = new Map(); - let count = 0; - - for await (const tournament of tournaments(type)) { - count++; - const results = await TournamentMatchRepository.allResultsByTournamentId( - tournament.ctx.id, - ); - invariant(results.length > 0, "No results found"); - - const skills = calculateIndividualPlayerSkills({ - queryCurrentUserRating(userId) { - return { rating: ratings.get(userId) ?? rating(), matchesCount: 0 }; - }, - results, - }); - - for (const { userId, mu, sigma } of skills) { - ratings.set(userId, rating({ mu, sigma })); - } - } - logger.info(`Processed ${count} tournaments`); - - for (const [userId, { mu, sigma }] of ratings) { - result.push({ - mu, - sigma, - ordinal: ordinal(rating({ mu, sigma })), - type, - userId, - }); - } - } - - await db.transaction().execute(async (trx) => { - await trx.deleteFrom("SeedingSkill").execute(); - for (const skill of result) { - await trx.insertInto("SeedingSkill").values(skill).execute(); - } - }); - logger.info(`Done. Total of ${result.length} seeding skills inserted`); -} - -async function* tournaments(type: "RANKED" | "UNRANKED") { - const maxId = await db - .selectFrom("Tournament") - .select(({ fn }) => fn.max("id").as("maxId")) - .executeTakeFirstOrThrow() - .then((row) => row.maxId); - - for (let tournamentId = 1; tournamentId <= maxId; tournamentId++) { - try { - const tournament = await tournamentFromDB({ - tournamentId, - user: undefined, - }); - - if (!tournament.ctx.isFinalized) { - continue; - } - - if (tournament.skillCountsFor === "RANKED" && type === "RANKED") { - yield tournament; - } else if ( - tournament.skillCountsFor === "UNRANKED" && - type === "UNRANKED" - ) { - yield tournament; - } - } catch { - // logger.info(`Skipped tournament with id ${tournamentId}`); - } - } -} - -main(); diff --git a/scripts/calc-tournament-summary-result-arrays.ts b/scripts/calc-tournament-summary-result-arrays.ts deleted file mode 100644 index 0aadd4b80..000000000 --- a/scripts/calc-tournament-summary-result-arrays.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { sql } from "kysely"; -import { db } from "../app/db/sql"; -import { - setResults, - type TournamentSummary, -} from "../app/features/tournament-bracket/core/summarizer.server"; -import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server"; -import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server"; -import invariant from "../app/utils/invariant"; -import { logger } from "../app/utils/logger"; - -async function main() { - logger.info( - "Starting to fix tournamentTeamId in TournamentMatchGameResultParticipant", - ); - await tournamentTeamIdsToTournamentMatchGameResultParticipantTable(); - logger.info("Fixed tournamentTeamId in TournamentMatchGameResultParticipant"); - - const result: Array< - { tournamentId: number } & Pick - > = []; - - let count = 0; - for await (const tournament of tournaments()) { - count++; - const results = await TournamentMatchRepository.allResultsByTournamentId( - tournament.ctx.id, - ); - invariant(results.length > 0, "No results found"); - - result.push({ - tournamentId: tournament.ctx.id, - setResults: setResults({ results, teams: tournament.ctx.teams }), - }); - - if (count % 100 === 0) { - logger.info(`Processed ${count} tournaments`); - } - } - - await db.transaction().execute(async (trx) => { - await trx - .updateTable("TournamentResult") - .set({ - setResults: JSON.stringify([]), - }) - .execute(); - for (const { tournamentId, setResults } of result) { - for (const [userId, setResult] of setResults.entries()) { - await trx - .updateTable("TournamentResult") - .set({ - setResults: JSON.stringify(setResult), - }) - .where("tournamentId", "=", tournamentId) - .where("userId", "=", userId) - .execute(); - } - } - }); - logger.info(`Done. Total of ${result.length} results inserted.`); - - await wipeEmptyResults(); -} - -async function* tournaments() { - const maxId = await db - .selectFrom("Tournament") - .select(({ fn }) => fn.max("id").as("maxId")) - .executeTakeFirstOrThrow() - .then((row) => row.maxId); - - for (let tournamentId = 1; tournamentId <= maxId; tournamentId++) { - if (tournamentId === 1483) { - // broken one - continue; - } - - try { - const tournament = await tournamentFromDB({ - tournamentId, - user: undefined, - }); - - if (!tournament.ctx.isFinalized) { - continue; - } - - yield tournament; - } catch (thrown) { - if (thrown instanceof Response) continue; - - throw thrown; - } - } -} - -// https://github.com/sendou-ink/sendou.ink/commit/96781122e2c5f9cd90564c9b57a45b74557fc400 -async function tournamentTeamIdsToTournamentMatchGameResultParticipantTable() { - await db - .updateTable("TournamentMatchGameResultParticipant") - .set((eb) => ({ - tournamentTeamId: eb - .selectFrom("TournamentTeamMember") - .innerJoin( - "TournamentTeam", - "TournamentTeamMember.tournamentTeamId", - "TournamentTeam.id", - ) - // exclude teams that have not checked in - .innerJoin( - "TournamentTeamCheckIn", - "TournamentTeamCheckIn.tournamentTeamId", - "TournamentTeam.id", - ) - .select("TournamentTeam.id") - .whereRef( - "TournamentTeamMember.userId", - "=", - "TournamentMatchGameResultParticipant.userId", - ) - .whereRef( - "TournamentTeam.tournamentId", - "=", - eb - .selectFrom("TournamentMatchGameResult") - .innerJoin( - "TournamentMatch", - "TournamentMatchGameResult.matchId", - "TournamentMatch.id", - ) - .innerJoin( - "TournamentStage", - "TournamentStage.id", - "TournamentMatch.stageId", - ) - .innerJoin( - "Tournament", - "Tournament.id", - "TournamentStage.tournamentId", - ) - .whereRef( - "TournamentMatchGameResult.id", - "=", - "TournamentMatchGameResultParticipant.matchGameResultId", - ) - .select("Tournament.id") - .limit(1), - ), - })) - .where("TournamentMatchGameResultParticipant.tournamentTeamId", "is", null) - .execute(); - - // manual fixes, not sure why these are needed - await db - .updateTable("TournamentMatchGameResultParticipant") - .set({ - tournamentTeamId: 13077, - }) - .where("userId", "=", 44085) - .where("tournamentTeamId", "is", null) - .execute(); - - await db - .updateTable("TournamentMatchGameResultParticipant") - .set({ - tournamentTeamId: 14589, - }) - .where("userId", "=", 10585) - .where("tournamentTeamId", "is", null) - .execute(); -} - -async function wipeEmptyResults() { - logger.info("Wiping empty results from TournamentResult table..."); - - const { numDeletedRows } = await db - .deleteFrom("TournamentResult") - .where(sql`instr(setResults, 'W') = 0`) - .where(sql`instr(setResults, 'L') = 0`) - .executeTakeFirst(); - - logger.info( - `Wiped ${numDeletedRows} empty results from TournamentResult table.`, - ); -} - -main().catch((err) => { - logger.error("Error in calc-tournament-summary-result-arrays.ts", err); - process.exit(1); -}); diff --git a/scripts/delete-skills-without-results.ts b/scripts/delete-skills-without-results.ts deleted file mode 100644 index d87cff188..000000000 --- a/scripts/delete-skills-without-results.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { db } from "~/db/sql"; -import { logger } from "~/utils/logger"; - -async function main() { - const skills = await db - .selectFrom("Skill") - .leftJoin("TournamentResult", (join) => - join - .onRef("TournamentResult.tournamentId", "=", "Skill.tournamentId") - .onRef("TournamentResult.userId", "=", "Skill.userId"), - ) - .select(["Skill.id"]) - .where("Skill.tournamentId", "is not", null) - .where("TournamentResult.tournamentId", "is", null) - .execute(); - - logger.info(`Found ${skills.length} skills without results`); - - await db - .updateTable("Skill") - .set({ - tournamentId: null, - }) - .where( - "id", - "in", - skills.map((skill) => skill.id), - ) - .execute(); - - logger.info("Deleted skills without results"); -} - -void main(); diff --git a/scripts/delete-tournament-team-maps.ts b/scripts/delete-tournament-team-maps.ts deleted file mode 100644 index a420f8e5a..000000000 --- a/scripts/delete-tournament-team-maps.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { sql } from "~/db/sql"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const rawTournamentTeamId = process.argv[2]?.trim(); - -invariant(rawTournamentTeamId, "tournament team is required (argument 1)"); - -const tournamentTeamId = Number(rawTournamentTeamId); - -invariant( - !Number.isNaN(tournamentTeamId), - "tournament team id must be a number", -); - -const deleteMapPoolStm = sql.prepare(/*sql*/ ` - delete from "MapPoolMap" - where "tournamentTeamId" = @tournamentTeamId -`); - -deleteMapPoolStm.run({ tournamentTeamId }); - -logger.info(`Deleted map pool of tournament team with id: ${tournamentTeamId}`); diff --git a/scripts/disband-team.ts b/scripts/disband-team.ts deleted file mode 100644 index 42199e1fb..000000000 --- a/scripts/disband-team.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { db } from "~/db/sql"; -import { dateToDatabaseTimestamp } from "~/utils/dates"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const id = Number(process.argv[2]?.trim()); - -invariant(id, "team id is required (argument 1)"); -invariant(Number.isInteger(id), "team id must be an integer"); - -async function main() { - await db - .updateTable("AllTeam") - .set({ - deletedAt: dateToDatabaseTimestamp(new Date()), - }) - .where("id", "=", id) - .execute(); - - logger.info(`Disbanded team with id: ${id}`); -} - -void main(); diff --git a/scripts/fc-lookup.ts b/scripts/fc-lookup.ts deleted file mode 100644 index 0ff554055..000000000 --- a/scripts/fc-lookup.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { db } from "~/db/sql"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const friendCode = process.argv[2]?.trim(); - -invariant(friendCode, "friend code is required (argument 1)"); - -async function main() { - const allFcs = await db - .selectFrom("UserFriendCode") - .innerJoin("User", "User.id", "UserFriendCode.userId") - .select([ - "UserFriendCode.friendCode", - "User.id as userId", - "User.discordId", - "User.discordUniqueName", - ]) - .orderBy("UserFriendCode.createdAt", "asc") - .whereRef("User.id", "=", "UserFriendCode.submitterUserId") - .execute(); - - const matches = allFcs.filter((fc) => fc.friendCode === friendCode); - - if (matches.length === 0) { - logger.info("No matches found"); - return; - } - - for (const match of matches) { - logger.info( - `${match.friendCode} - ${match.discordUniqueName} - ${match.discordId}`, - ); - } -} - -void main(); diff --git a/scripts/map-popularity-q.ts b/scripts/map-popularity-q.ts deleted file mode 100644 index ea4702c18..000000000 --- a/scripts/map-popularity-q.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { db } from "~/db/sql"; -import { BANNED_MAPS } from "~/features/match-profile/banned-maps"; -import { modesShort } from "~/modules/in-game-lists/modes"; -import { stageIds } from "~/modules/in-game-lists/stage-ids"; -import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; -import { - databaseTimestampToDate, - dateToDatabaseTimestamp, -} from "~/utils/dates"; -import { logger } from "~/utils/logger"; -import names from "../locales/en/game-misc.json"; - -const SEASON_2_START = new Date("2023-12-04T17:00:00.000Z"); - -async function main() { - const appearance = await db - .selectFrom("GroupMatchMap") - .innerJoin("GroupMatch", "GroupMatchMap.matchId", "GroupMatch.id") - .select(({ fn }) => [ - "GroupMatchMap.mode", - "GroupMatchMap.stageId", - fn.countAll().as("count"), - ]) - .groupBy(["GroupMatchMap.stageId", "GroupMatchMap.mode"]) - .where("GroupMatch.createdAt", ">", dateToDatabaseTimestamp(SEASON_2_START)) - .execute(); - - const usage: Record< - ModeShort | "ALL", - { stageId: StageId; count: number }[] - > = { - TW: [], - SZ: [], - TC: [], - RM: [], - CB: [], - ALL: [], - }; - - const ageRow = await db - .selectFrom("Build") - .select((eb) => eb.fn.max("Build.updatedAt").as("age")) - .executeTakeFirstOrThrow(); - - const dbAgeDate = databaseTimestampToDate(ageRow.age); - - for (const mode of modesShort) { - for (const stageId of stageIds) { - const count = - appearance.find((row) => row.stageId === stageId && row.mode === mode) - ?.count ?? 0; - - usage[mode].push({ - stageId, - count, - }); - - const existingAllCount = usage.ALL.find((row) => row.stageId === stageId); - - if (!existingAllCount) { - usage.ALL.push({ - stageId, - count, - }); - } else { - existingAllCount.count += count; - } - } - - usage[mode].sort((a, b) => b.count - a.count); - } - - usage.ALL.sort((a, b) => b.count - a.count); - - logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`); - - // all modes - - logger.info("All"); - let banCount = 0; - for (const [i, { stageId, count }] of usage.ALL.entries()) { - const name = names[`STAGE_${stageId}`]; - - const partlyBanned = Object.values(BANNED_MAPS).some((arr) => - arr.includes(stageId as any), - ); - - if (partlyBanned) banCount++; - - logger.info( - `${i < 9 ? " " : ""}${i + 1}) ${ - partlyBanned ? "🔴" : " " - } ${name}: ${count}`, - ); - } - - logger.info(`Banned maps (at least one mode): ${banCount}`); - logger.info(); - - // modes - for (const mode of modesShort) { - // if (usage[mode].every((e) => e.count === 0)) continue; - - logger.info(mode); - let banCount = 0; - for (const [i, { stageId, count }] of usage[mode].entries()) { - const name = names[`STAGE_${stageId}`]; - - const isBanned = BANNED_MAPS[mode].includes(stageId); - if (isBanned) banCount++; - - logger.info( - `${i < 9 ? " " : ""}${i + 1}) ${ - isBanned ? "❌" : " " - } ${name}: ${count}`, - ); - } - - logger.info(`Banned maps: ${banCount}`); - logger.info( - Object.values(usage[mode]).reduce((acc, cur) => acc + cur.count, 0), - ); - } -} - -void main(); diff --git a/scripts/map-popularity.ts b/scripts/map-popularity.ts deleted file mode 100644 index 321b44457..000000000 --- a/scripts/map-popularity.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { db } from "~/db/sql"; -import { BANNED_MAPS } from "~/features/match-profile/banned-maps"; -import { modesShort } from "~/modules/in-game-lists/modes"; -import { stageIds } from "~/modules/in-game-lists/stage-ids"; -import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; -import { databaseTimestampToDate } from "~/utils/dates"; -import { logger } from "~/utils/logger"; -import { cutToNDecimalPlaces } from "~/utils/number"; -import names from "../locales/en/game-misc.json"; - -async function main() { - const appearance = await db - .selectFrom("MapPoolMap") - .select(({ fn }) => [ - "MapPoolMap.stageId", - "MapPoolMap.mode", - fn.countAll().as("count"), - ]) - .where("MapPoolMap.calendarEventId", "is not", null) - .groupBy(["MapPoolMap.stageId", "MapPoolMap.mode"]) - .execute(); - - const usage: Record< - ModeShort, - { stageId: StageId; count: number; relativeCount: number }[] - > = { - TW: [], - SZ: [], - TC: [], - RM: [], - CB: [], - }; - - const ageRow = await db - .selectFrom("Build") - .select((eb) => eb.fn.max("Build.updatedAt").as("age")) - .executeTakeFirstOrThrow(); - - const dbAgeDate = databaseTimestampToDate(ageRow.age); - - for (const mode of modesShort) { - for (const stageId of stageIds) { - const count = - appearance.find((row) => row.stageId === stageId && row.mode === mode) - ?.count ?? 0; - - const firstAppear = await db - .selectFrom("MapPoolMap") - .innerJoin( - "CalendarEvent", - "MapPoolMap.calendarEventId", - "CalendarEvent.id", - ) - .innerJoin( - "CalendarEventDate", - "CalendarEvent.id", - "CalendarEventDate.eventId", - ) - .select((eb) => - eb.fn.min("CalendarEventDate.startTime").as("firstAppear"), - ) - .executeTakeFirst(); - - const firstAppearDate = firstAppear - ? databaseTimestampToDate(firstAppear.firstAppear) - : null; - - const datesSinceFirstAppear = firstAppearDate - ? Math.floor((dbAgeDate.getTime() - firstAppearDate.getTime()) / 864e5) - : null; - - usage[mode].push({ - stageId, - count, - relativeCount: datesSinceFirstAppear - ? cutToNDecimalPlaces((count / datesSinceFirstAppear) * 30, 3) - : 0, - }); - } - - usage[mode].sort((a, b) => b.relativeCount - a.relativeCount); - } - - logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`); - for (const mode of modesShort) { - logger.info(mode); - let banCount = 0; - for (const [i, { stageId, count, relativeCount }] of usage[ - mode - ].entries()) { - const name = names[`STAGE_${stageId}`]; - - const isBanned = BANNED_MAPS[mode].includes(stageId); - if (isBanned) banCount++; - - logger.info( - `${i < 9 ? " " : ""}${i + 1}) ${ - isBanned ? "❌" : " " - } ${name}: ${relativeCount} (${count})`, - ); - } - - logger.info(`Banned maps: ${banCount}`); - logger.info(); - } -} - -void main(); diff --git a/scripts/migrate-tournament-logos-to-s3.ts b/scripts/migrate-tournament-logos-to-s3.ts deleted file mode 100644 index 9070990f0..000000000 --- a/scripts/migrate-tournament-logos-to-s3.ts +++ /dev/null @@ -1,170 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { Readable } from "node:stream"; -import { db } from "~/db/sql"; -import { uploadStreamToS3 } from "~/features/img-upload/s3.server"; -import { databaseTimestampNow } from "~/utils/dates"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const TOURNAMENT_LOGO_PATH = "public/static-assets/img/tournament-logos"; -const ADMIN_ID = 1; - -const LOGO_IDENTIFIER_TO_PATTERN: Record = { - sf: ["sendouq"], - pp: ["paddling pool"], - itz: ["in the zone"], - pn: ["picnic"], - pg: ["proving grounds"], - tc: ["triton"], - sos: ["swim or sink"], - ftiu: ["from the ink up"], - cc: ["coral clash"], - lu: ["level up"], - a41: ["all 4 one"], - fb: ["fry basket"], - d: ["the depths"], - e: ["eclipse"], - hc: ["homecoming"], - bio: ["bad ideas"], - ai: ["tenoch"], - mm: ["megalodon monday"], - ho: ["heaven 2 ocean"], - kr: ["kraken royale"], - mr: ["menu royale"], - bc: ["barracuda co"], - ci: ["crimson ink"], - me: ["mesozoic mayhem"], - ros: ["rain or shine"], - sj: ["squid junction"], - ss: ["silly sausage"], - ul: ["united-lan"], - sc: ["soul cup"], -}; - -async function uploadLogoFile( - filePath: string, - identifier: string, -): Promise { - logger.info(`Uploading ${identifier}.png to S3...`); - - const fileBuffer = fs.readFileSync(filePath); - const stream = Readable.from(fileBuffer); - - const fileName = `tournament-logo-${identifier}.png`; - - const s3Url = await uploadStreamToS3(stream, fileName); - - invariant(s3Url, `Failed to upload ${identifier}.png to S3`); - - logger.info(`Uploaded ${identifier}.png to ${s3Url}`); - return fileName; -} - -async function createImageRecord(url: string): Promise { - const result = await db - .insertInto("UnvalidatedUserSubmittedImage") - .values({ - url, - validatedAt: databaseTimestampNow(), - submitterUserId: ADMIN_ID, - }) - .returning("id") - .executeTakeFirstOrThrow(); - - return result.id; -} - -async function updateCalendarEvents( - imageId: number, - patterns: string[], -): Promise { - const events = await db - .selectFrom("CalendarEvent") - .select(["id", "name"]) - .where("avatarImgId", "is", null) - .execute(); - - let updateCount = 0; - - for (const event of events) { - const normalizedEventName = event.name.toLowerCase(); - const matches = patterns.some((pattern) => - normalizedEventName.includes(pattern), - ); - - if (matches) { - await db - .updateTable("CalendarEvent") - .set({ avatarImgId: imageId }) - .where("id", "=", event.id) - .execute(); - - logger.info(`Updated CalendarEvent ${event.id}: "${event.name}"`); - updateCount++; - } - } - - return updateCount; -} - -async function main() { - logger.info("Starting tournament logo migration to S3..."); - - const defaultLogoPath = path.join(TOURNAMENT_LOGO_PATH, "default.png"); - if (fs.existsSync(defaultLogoPath)) { - logger.info("\n=== Uploading default tournament logo ==="); - const defaultS3Url = await uploadLogoFile(defaultLogoPath, "default"); - const defaultImageId = await createImageRecord(defaultS3Url); - logger.info( - `Created UnvalidatedUserSubmittedImage record for default logo with ID ${defaultImageId}\n`, - ); - } - - const logoFiles = fs - .readdirSync(TOURNAMENT_LOGO_PATH) - .filter((file) => file.endsWith(".png") && file !== "default.png"); - - logger.info(`Found ${logoFiles.length} logo files to migrate`); - - let totalUpdated = 0; - - for (const file of logoFiles) { - const identifier = file.replace(".png", ""); - const patterns = LOGO_IDENTIFIER_TO_PATTERN[identifier]; - - if (!patterns) { - logger.warn(`No pattern mapping found for ${identifier}, skipping...`); - continue; - } - - const filePath = path.join(TOURNAMENT_LOGO_PATH, file); - - const s3Url = await uploadLogoFile(filePath, identifier); - - const imageId = await createImageRecord(s3Url); - logger.info( - `Created UnvalidatedUserSubmittedImage record with ID ${imageId}`, - ); - - const updatedCount = await updateCalendarEvents(imageId, patterns); - totalUpdated += updatedCount; - - logger.info( - `Updated ${updatedCount} CalendarEvent records for ${identifier}\n`, - ); - } - - logger.info("\n=== Migration Complete ==="); - logger.info(`Total CalendarEvent records updated: ${totalUpdated}`); -} - -main() - .then(() => { - logger.info("Script completed successfully"); - process.exit(0); - }) - .catch((error) => { - logger.error("Script failed:", error); - process.exit(1); - }); diff --git a/scripts/nuke-season-data.ts b/scripts/nuke-season-data.ts deleted file mode 100644 index 8a6656345..000000000 --- a/scripts/nuke-season-data.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { db } from "~/db/sql"; -import * as Seasons from "~/features/mmr/core/Seasons"; -import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server"; -import { dateToDatabaseTimestamp } from "~/utils/dates"; -import { logger } from "~/utils/logger"; - -async function main() { - if (Seasons.current()) { - throw new Error("There is a current season"); - } - - if (Seasons.next()?.nth !== 8) { - throw new Error("Next season is not 8. Script needs modifying"); - } - - const wrongSeasonStartDate = new Date("2025-06-14T18:00:00.000Z"); - - const allMatches = await db - .selectFrom("GroupMatch") - .selectAll() - .where( - "GroupMatch.createdAt", - ">=", - dateToDatabaseTimestamp(wrongSeasonStartDate), - ) - .execute(); - - await db - .deleteFrom("Skill") - .where( - "Skill.groupMatchId", - "in", - allMatches.map((m) => m.id), - ) - .execute(); - - for (const match of allMatches) { - await SQMatchRepository.lockMatchWithoutSkillChange(match.id); - } - - logger.info(`All done with nuking the season (${allMatches.length} matches)`); -} - -void main(); diff --git a/scripts/recent-fcs.ts b/scripts/recent-fcs.ts deleted file mode 100644 index 1d280aecc..000000000 --- a/scripts/recent-fcs.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { db } from "~/db/sql"; -import { databaseTimestampToDate } from "~/utils/dates"; -import { logger } from "~/utils/logger"; - -async function main() { - const allFcs = await db - .selectFrom("UserFriendCode") - .innerJoin("User", "User.id", "UserFriendCode.userId") - .select(["UserFriendCode.friendCode", "User.id as userId"]) - .orderBy("UserFriendCode.createdAt", "asc") - .whereRef("User.id", "=", "UserFriendCode.submitterUserId") - .execute(); - - const fcMap = new Map(); - for (const fc of allFcs) { - const fcs = fcMap.get(fc.friendCode) ?? []; - fcs.push(fc.userId); - fcMap.set(fc.friendCode, fcs); - } - - const friendCodeAdders = await db - .selectFrom("UserFriendCode") - .innerJoin("User", "User.id", "UserFriendCode.userId") - .select([ - "UserFriendCode.friendCode", - "UserFriendCode.createdAt", - "User.id", - "User.discordId", - "User.discordUniqueName", - ]) - .orderBy("UserFriendCode.createdAt", "desc") - .whereRef("User.id", "=", "UserFriendCode.submitterUserId") - .limit(90) - .execute(); - - let result = ""; - - let date = ""; - for (const [i, friendCodeAdder] of friendCodeAdders.entries()) { - const utc = databaseTimestampToDate( - friendCodeAdder.createdAt, - ).toUTCString(); - const newDate = utc.split(",")[0]; - if (date !== newDate) { - date = newDate; - result += "\n"; - } - - const isDuplicate = - (fcMap.get(friendCodeAdder.friendCode) ?? [])?.length > 1; - - result += `${i < 9 ? "0" : ""}${i + 1}) ${utc} - ${friendCodeAdder.friendCode}${isDuplicate ? " >>DUPLICATE<<" : ""} - ${friendCodeAdder.discordUniqueName} - ${friendCodeAdder.discordId}\n`; - } - - logger.info(result); -} - -void main(); diff --git a/scripts/sync-splatoon-rotations.ts b/scripts/sync-splatoon-rotations.ts deleted file mode 100644 index fd184efa9..000000000 --- a/scripts/sync-splatoon-rotations.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** biome-ignore-all lint/suspicious/noConsole: CLI script */ -import * as SplatoonRotationRepository from "~/features/splatoon-rotations/SplatoonRotationRepository.server"; -import { fetchRotations } from "~/features/splatoon-rotations/splatoon-rotations.server"; - -async function main() { - console.log("Fetching splatoon rotations from splatoon3.ink..."); - - const rotations = await fetchRotations(); - console.log(`Fetched ${rotations.length} rotations`); - - await SplatoonRotationRepository.replaceAll(rotations); - console.log("Rotations synced to database"); -} - -main().catch((err) => { - console.error("Failed to sync rotations:", err); - process.exit(1); -}); diff --git a/scripts/sync-tournament-vods.ts b/scripts/sync-tournament-vods.ts deleted file mode 100644 index 0ef97c2c9..000000000 --- a/scripts/sync-tournament-vods.ts +++ /dev/null @@ -1,14 +0,0 @@ -// usage: npx vite-node ./scripts/sync-tournament-vods.ts -import { processOneTournament } from "~/routines/syncTournamentVods"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const tournamentId = Number(process.argv[2]?.trim()); -invariant( - tournamentId && !Number.isNaN(tournamentId), - "tournament id is required (argument 1)", -); - -logger.info(`Syncing VODs for tournament ${tournamentId}...`); -await processOneTournament(tournamentId); -logger.info("Done"); diff --git a/scripts/sync-xp-badges.ts b/scripts/sync-xp-badges.ts deleted file mode 100644 index 085ee7828..000000000 --- a/scripts/sync-xp-badges.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as BadgeRepository from "~/features/badges/BadgeRepository.server"; -import { logger } from "~/utils/logger"; - -void (async () => { - await BadgeRepository.syncXPBadges(); - logger.info("Synced XP badges"); -})(); diff --git a/scripts/tournament-tiers-experiment.ts b/scripts/tournament-tiers-experiment.ts deleted file mode 100644 index 9f2cccdd0..000000000 --- a/scripts/tournament-tiers-experiment.ts +++ /dev/null @@ -1,406 +0,0 @@ -/** biome-ignore-all lint/suspicious/noConsole: CLI script output */ -/** - * Tournament Tiering Experiment Script - * - * Run with: pnpm exec vite-node scripts/tournament-tiers-experiment.ts - * - * Calculates tournament tiers based on top teams' average SeedingSkill. - * Tweak the THRESHOLDS object to experiment with different tier distributions. - */ - -import Database from "better-sqlite3"; - -const db = new Database("db-prod.sqlite3", { readonly: true }); - -// ============================================================================ -// CONFIGURATION - Tweak these values to experiment -// ============================================================================ - -/** - * Minimum ordinal thresholds for each tier. - * A tournament is assigned the highest tier where its score meets the threshold. - * - * Current values are based on percentile analysis: - * - X: Top ~1% of tournaments - * - S+: Top ~3% - * - S: Top ~8% - * - A+: Top ~15% - * - A: Top ~25% - * - B+: Top ~40% - * - B: Top ~55% - * - C+: Top ~75% - * - C: Everything else - */ -const THRESHOLDS = { - X: 32, - "S+": 29, - S: 26, - "A+": 24, - A: 21, - "B+": 15, - B: 10, - "C+": 5, - C: Number.NEGATIVE_INFINITY, // Catch-all -} as const; - -/** - * How many top teams to consider for the tournament score. - * Using 8 captures the competitive core of most tournaments. - */ -const TOP_TEAMS_COUNT = 8; - -/** - * Minimum number of teams required for a tournament to be tiered. - * Tournaments with fewer teams will be marked as "UNTIERED". - */ -const MIN_TEAMS_FOR_TIERING = 8; - -/** - * Size bonus configuration. - * The bonus scales inversely with skill level - X-tier gets no bonus, - * lower tiers get increasingly more bonus for larger tournaments. - * - * NO_BONUS_ABOVE: Score threshold above which no size bonus applies (X-tier) - * MAX_BONUS_PER_10_TEAMS: Maximum bonus per 10 teams above minimum (applied at score 0) - * - * The bonus scales linearly: at NO_BONUS_ABOVE, multiplier is 0. - * As score decreases toward 0, multiplier approaches MAX_BONUS_PER_10_TEAMS. - * - * Formula: bonus = scaleFactor * MAX_BONUS * (teamsAboveMin / 10) - * where scaleFactor = max(0, (NO_BONUS_ABOVE - rawScore) / NO_BONUS_ABOVE) - */ -const SIZE_BONUS = { - NO_BONUS_ABOVE: 32, // X-tier threshold - no bonus at this level - MAX_BONUS_PER_10_TEAMS: 1.5, // Max points added per 10 extra teams (at score ~0) -}; - -/** - * Filter to only include tournaments after this date (ISO string). - * Set to null to include all tournaments. - */ -const MIN_DATE: string | null = null; // e.g., "2024-01-01" - -// ============================================================================ -// IMPLEMENTATION -// ============================================================================ - -type Tier = keyof typeof THRESHOLDS; - -interface TournamentData { - tournamentId: number; - eventId: number; - name: string; - startTime: number; - teamCount: number; - top8AvgOrdinal: number | null; - adjustedScore: number | null; - tier: Tier | "UNTIERED"; -} - -function calculateTier(score: number | null): Tier | "UNTIERED" { - if (score === null) return "UNTIERED"; - - const tiers = Object.entries(THRESHOLDS) as [Tier, number][]; - for (const [tier, threshold] of tiers) { - if (score >= threshold) return tier; - } - return "C"; -} - -function calculateAdjustedScore(rawScore: number, teamCount: number): number { - if (SIZE_BONUS.MAX_BONUS_PER_10_TEAMS === 0) return rawScore; - - // Scale factor: 0 at NO_BONUS_ABOVE, approaches 1 as score approaches 0 - const scaleFactor = Math.max( - 0, - (SIZE_BONUS.NO_BONUS_ABOVE - rawScore) / SIZE_BONUS.NO_BONUS_ABOVE, - ); - - const teamsAboveMin = Math.max(0, teamCount - MIN_TEAMS_FOR_TIERING); - const bonus = - scaleFactor * SIZE_BONUS.MAX_BONUS_PER_10_TEAMS * (teamsAboveMin / 10); - - return rawScore + bonus; -} - -function getTournamentData(): TournamentData[] { - const query = ` - WITH TeamSkills AS ( - SELECT - tt.tournamentId, - tt.id as teamId, - AVG(ss.ordinal) as avg_team_ordinal - FROM TournamentTeam tt - JOIN TournamentTeamMember ttm ON ttm.tournamentTeamId = tt.id - LEFT JOIN SeedingSkill ss ON ss.userId = ttm.userId AND ss.type = 'RANKED' - WHERE tt.droppedOut = 0 - GROUP BY tt.tournamentId, tt.id - ), - TeamCounts AS ( - SELECT tournamentId, COUNT(*) as team_count - FROM TeamSkills - WHERE avg_team_ordinal IS NOT NULL - GROUP BY tournamentId - ), - RankedTeams AS ( - SELECT - ts.tournamentId, - ts.avg_team_ordinal, - tc.team_count, - ROW_NUMBER() OVER (PARTITION BY ts.tournamentId ORDER BY ts.avg_team_ordinal DESC) as rank - FROM TeamSkills ts - JOIN TeamCounts tc ON tc.tournamentId = ts.tournamentId - WHERE ts.avg_team_ordinal IS NOT NULL - ), - TournamentScores AS ( - SELECT - tournamentId, - AVG(avg_team_ordinal) as top8_avg_ordinal, - MAX(team_count) as team_count - FROM RankedTeams - WHERE rank <= ${TOP_TEAMS_COUNT} - GROUP BY tournamentId - ) - SELECT - t.id as tournamentId, - ce.id as eventId, - ce.name, - ced.startTime, - ts.team_count as teamCount, - ts.top8_avg_ordinal as top8AvgOrdinal - FROM Tournament t - JOIN CalendarEvent ce ON ce.tournamentId = t.id - JOIN CalendarEventDate ced ON ced.eventId = ce.id - LEFT JOIN TournamentScores ts ON ts.tournamentId = t.id - WHERE t.isFinalized = 1 - ${MIN_DATE ? `AND ced.startTime >= strftime('%s', '${MIN_DATE}') * 1000` : ""} - GROUP BY t.id - ORDER BY ced.startTime DESC - `; - - const rows = db.prepare(query).all() as Array<{ - tournamentId: number; - eventId: number; - name: string; - startTime: number; - teamCount: number | null; - top8AvgOrdinal: number | null; - }>; - - return rows.map((row) => { - const teamCount = row.teamCount ?? 0; - const meetsMinTeams = teamCount >= MIN_TEAMS_FOR_TIERING; - - let adjustedScore: number | null = null; - if (row.top8AvgOrdinal !== null && meetsMinTeams) { - adjustedScore = calculateAdjustedScore(row.top8AvgOrdinal, teamCount); - } - - return { - tournamentId: row.tournamentId, - eventId: row.eventId, - name: row.name, - startTime: row.startTime, - teamCount, - top8AvgOrdinal: row.top8AvgOrdinal, - adjustedScore, - tier: meetsMinTeams ? calculateTier(adjustedScore) : "UNTIERED", - }; - }); -} - -function printDistribution(tournaments: TournamentData[]) { - const distribution: Record = {}; - const tiers = [...Object.keys(THRESHOLDS), "UNTIERED"]; - - for (const tier of tiers) { - distribution[tier] = 0; - } - - for (const t of tournaments) { - distribution[t.tier]++; - } - - const total = tournaments.length; - const tiered = total - distribution.UNTIERED; - - console.log(`\n${"=".repeat(60)}`); - console.log("TIER DISTRIBUTION"); - console.log("=".repeat(60)); - console.log(`Total tournaments: ${total}`); - console.log(`Tiered (${MIN_TEAMS_FOR_TIERING}+ teams): ${tiered}`); - console.log( - `Untiered (< ${MIN_TEAMS_FOR_TIERING} teams): ${distribution.UNTIERED}`, - ); - console.log("-".repeat(60)); - - for (const tier of tiers) { - if (tier === "UNTIERED") continue; - const count = distribution[tier]; - const pctOfTiered = tiered > 0 ? ((count / tiered) * 100).toFixed(1) : "0"; - const bar = "█".repeat(Math.round(count / 20)); - console.log( - `${tier.padEnd(3)} | ${String(count).padStart(4)} | ${pctOfTiered.padStart(5)}% of tiered | ${bar}`, - ); - } -} - -function formatDate(timestamp: number): string { - // timestamps are stored in seconds, not milliseconds - return new Date(timestamp * 1000).toISOString().split("T")[0]; -} - -function printTopTournaments( - tournaments: TournamentData[], - tier: Tier, - limit = 10, -) { - const filtered = tournaments - .filter((t) => t.tier === tier) - .sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0)) - .slice(0, limit); - - console.log(`\n${"=".repeat(60)}`); - console.log(`TOP ${limit} ${tier}-TIER TOURNAMENTS`); - console.log("=".repeat(60)); - - for (const t of filtered) { - const date = formatDate(t.startTime); - console.log( - `[${date}] ${t.name.substring(0, 40).padEnd(40)} | ${t.teamCount} teams | score: ${t.adjustedScore?.toFixed(1)}`, - ); - } -} - -function printBottomOfTier( - tournaments: TournamentData[], - tier: Tier, - limit = 5, -) { - const filtered = tournaments - .filter((t) => t.tier === tier) - .sort((a, b) => (a.adjustedScore ?? 0) - (b.adjustedScore ?? 0)) - .slice(0, limit); - - console.log(`\n${"-".repeat(60)}`); - console.log(`BOTTOM ${limit} OF ${tier}-TIER (borderline)`); - console.log("-".repeat(60)); - - for (const t of filtered) { - const date = formatDate(t.startTime); - console.log( - `[${date}] ${t.name.substring(0, 40).padEnd(40)} | ${t.teamCount} teams | score: ${t.adjustedScore?.toFixed(1)}`, - ); - } -} - -function printThresholds() { - console.log(`\n${"=".repeat(60)}`); - console.log("CURRENT THRESHOLDS"); - console.log("=".repeat(60)); - for (const [tier, threshold] of Object.entries(THRESHOLDS)) { - if (threshold === Number.NEGATIVE_INFINITY) { - console.log(`${tier}: < ${THRESHOLDS["C+"]}`); - } else { - console.log(`${tier}: >= ${threshold}`); - } - } - console.log(`\nTop teams considered: ${TOP_TEAMS_COUNT}`); - console.log(`Min teams for tiering: ${MIN_TEAMS_FOR_TIERING}`); - console.log("\nSize bonus (scales inversely with skill):"); - console.log(` No bonus above score: ${SIZE_BONUS.NO_BONUS_ABOVE}`); - console.log( - ` Max bonus per 10 teams: ${SIZE_BONUS.MAX_BONUS_PER_10_TEAMS} points`, - ); - - // Show example bonus calculations - console.log("\nExample bonuses for 50-team tournament:"); - const exampleTeams = 50; - for (const rawScore of [32, 28, 24, 20, 15, 10, 5, 0]) { - const adjusted = calculateAdjustedScore(rawScore, exampleTeams); - const bonus = adjusted - rawScore; - console.log( - ` Raw ${rawScore.toString().padStart(2)} -> ${adjusted.toFixed(2)} (+${bonus.toFixed(2)})`, - ); - } -} - -// ============================================================================ -// MAIN -// ============================================================================ - -function main() { - console.log("Tournament Tiering Experiment"); - console.log("==============================\n"); - - printThresholds(); - - const tournaments = getTournamentData(); - printDistribution(tournaments); - - // Show examples from top tiers - printTopTournaments(tournaments, "X", 15); - printBottomOfTier(tournaments, "X", 5); - - printTopTournaments(tournaments, "S+", 10); - printBottomOfTier(tournaments, "S+", 5); - - printTopTournaments(tournaments, "S", 10); - - // Show tournaments promoted by size bonus - console.log(`\n${"=".repeat(60)}`); - console.log("TOURNAMENTS PROMOTED BY SIZE BONUS"); - console.log("=".repeat(60)); - const promoted = tournaments - .filter((t) => t.tier !== "UNTIERED" && t.top8AvgOrdinal !== null) - .filter((t) => { - const rawTier = calculateTier(t.top8AvgOrdinal); - return rawTier !== t.tier; - }) - .sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0)) - .slice(0, 20); - - if (promoted.length === 0) { - console.log("No tournaments were promoted by size bonus."); - } else { - for (const t of promoted) { - const rawTier = calculateTier(t.top8AvgOrdinal); - const bonus = (t.adjustedScore ?? 0) - (t.top8AvgOrdinal ?? 0); - console.log( - `${rawTier.toString().padEnd(3)} -> ${t.tier.padEnd(3)} | ${t.name.substring(0, 35).padEnd(35)} | ${t.teamCount} teams | +${bonus.toFixed(2)}`, - ); - } - } - - // Recent tournaments analysis - console.log(`\n${"=".repeat(60)}`); - console.log("RECENT TOURNAMENTS (last 30 tiered)"); - console.log("=".repeat(60)); - tournaments - .filter((t) => t.tier !== "UNTIERED") - .sort((a, b) => b.startTime - a.startTime) - .slice(0, 30) - .forEach((t) => { - const date = formatDate(t.startTime); - const safeName = t.name.substring(0, 35).padEnd(35); - console.log( - `${t.tier.padEnd(3)} | [${date}] ${safeName} | ${String(t.teamCount).padStart(3)} teams | ${t.adjustedScore?.toFixed(1)}`, - ); - }); - - // Full CSV dump ordered by score - console.log(`\n${"=".repeat(60)}`); - console.log("FULL CSV DUMP (ordered by score descending)"); - console.log("=".repeat(60)); - console.log("name,score,tier"); - tournaments - .filter((t) => t.tier !== "UNTIERED") - .sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0)) - .forEach((t) => { - const safeName = t.name.replace(/,/g, ";").replace(/"/g, "'"); - console.log(`"${safeName}",${t.adjustedScore?.toFixed(2)},${t.tier}`); - }); -} - -main(); -db.close(); diff --git a/scripts/transfer-weapon-pools.ts b/scripts/transfer-weapon-pools.ts deleted file mode 100644 index 4df64b9ab..000000000 --- a/scripts/transfer-weapon-pools.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { db } from "~/db/sql"; -import { logger } from "~/utils/logger"; - -async function main() { - const weaponPools = await db - .selectFrom("UserWeapon") - .select([ - "UserWeapon.userId", - "UserWeapon.weaponSplId", - "UserWeapon.userId", - ]) - .where("UserWeapon.order", "!=", 5) - .orderBy("UserWeapon.order", "asc") - .execute(); - - // group by userId - const weaponPoolsByUserId = weaponPools.reduce( - (acc, weaponPool) => { - if (!acc[weaponPool.userId]) { - acc[weaponPool.userId] = []; - } - - acc[weaponPool.userId].push(weaponPool); - - return acc; - }, - {} as Record, - ); - - for (const [userId, weaponPools] of Object.entries(weaponPoolsByUserId)) { - const weaponPoolIds = weaponPools.map( - (weaponPool) => weaponPool.weaponSplId, - ); - - await db - .updateTable("User") - .set({ - weaponPool: JSON.stringify(weaponPoolIds), - }) - .where("User.id", "=", Number(userId)) - .execute(); - } - - logger.info("done with the transfer"); -} - -void main(); diff --git a/scripts/unlock-league-matches.ts b/scripts/unlock-league-matches.ts deleted file mode 100644 index e572f504a..000000000 --- a/scripts/unlock-league-matches.ts +++ /dev/null @@ -1,69 +0,0 @@ -// usage: pnpm exec vite-node ./scripts/unlock-league-matches.ts -import { sql } from "~/db/sql"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const parentTournamentId = Number(process.argv[2]?.trim()); -invariant( - parentTournamentId && !Number.isNaN(parentTournamentId), - "parent tournament id is required (argument 1)", -); - -const divisions = sql - .prepare( - 'SELECT id FROM "Tournament" WHERE parentTournamentId = @parentTournamentId', - ) - .all({ parentTournamentId }) as { id: number }[]; - -invariant( - divisions.length > 0, - `No divisions found for tournament ${parentTournamentId}`, -); -logger.info(`Found ${divisions.length} divisions`); - -const updateMatch = sql.prepare( - 'UPDATE "TournamentMatch" SET status = 2 WHERE id = @id', -); - -const unlockAll = sql.transaction(() => { - let totalUnlocked = 0; - - for (const division of divisions) { - const stages = sql - .prepare( - 'SELECT id FROM "TournamentStage" WHERE tournamentId = @tournamentId', - ) - .all({ tournamentId: division.id }) as { id: number }[]; - - if (stages.length === 0) continue; - const stageIds = stages.map((s) => s.id); - - const lockedMatches = sql - .prepare( - `SELECT id, opponentOne, opponentTwo FROM "TournamentMatch" - WHERE stageId IN (${stageIds.map(() => "?").join(",")}) - AND status = 0`, - ) - .all(...stageIds) as { - id: number; - opponentOne: string; - opponentTwo: string; - }[]; - - let divUnlocked = 0; - for (const match of lockedMatches) { - const o1 = JSON.parse(match.opponentOne); - const o2 = JSON.parse(match.opponentTwo); - if (o1?.id == null || o2?.id == null) continue; - updateMatch.run({ id: match.id }); - divUnlocked++; - } - - logger.info(`Division ${division.id}: unlocked ${divUnlocked} match(es)`); - totalUnlocked += divUnlocked; - } - - logger.info(`Total unlocked: ${totalUnlocked} match(es)`); -}); - -unlockAll(); diff --git a/scripts/update-event-name.ts b/scripts/update-event-name.ts deleted file mode 100644 index d3207acd4..000000000 --- a/scripts/update-event-name.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { db } from "~/db/sql"; -import invariant from "~/utils/invariant"; -import { logger } from "~/utils/logger"; - -const rawEventId = process.argv[2]?.trim(); -invariant(rawEventId, "eventId is required (argument 1)"); - -const eventId = Number(rawEventId); -invariant(!Number.isNaN(eventId), "eventId must be a number"); - -const newName = process.argv[3]?.trim(); -invariant(newName, "newName is required (argument 2)"); - -async function main() { - const oldName = ( - await db - .selectFrom("CalendarEvent") - .select(["CalendarEvent.name"]) - .where("id", "=", eventId) - .executeTakeFirstOrThrow() - ).name; - - await db - .updateTable("CalendarEvent") - .set({ name: newName }) - .where("CalendarEvent.id", "=", eventId) - .execute(); - - logger.info( - `Event name updated from "${oldName}" to "${newName}" for event ID: ${eventId}`, - ); -} - -main();