sendou.ink/scripts/calc-seeding-skills.ts

89 lines
2.5 KiB
TypeScript

import "dotenv/config";
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 { allMatchResultsByTournamentId } from "../app/features/tournament-bracket/queries/allMatchResultsByTournamentId.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<number, Rating>();
let count = 0;
for await (const tournament of tournaments(type)) {
count++;
const results = allMatchResultsByTournamentId(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();