reset ratings based on osu rank
This commit is contained in:
@@ -80,43 +80,40 @@ async function main() {
|
|||||||
const [acc] = await sql`SELECT account_id FROM account
|
const [acc] = await sql`SELECT account_id FROM account
|
||||||
WHERE user_id = ${r.user_id} AND provider_id = 'osu' LIMIT 1`;
|
WHERE user_id = ${r.user_id} AND provider_id = 'osu' LIMIT 1`;
|
||||||
|
|
||||||
|
let rank: number | null = null;
|
||||||
|
|
||||||
if (!acc) {
|
if (!acc) {
|
||||||
console.log(` [SKIP] user ${r.user_id} — no osu! account linked`);
|
console.log(` [WARN] user ${r.user_id} — no osu! account linked, treating as unranked`);
|
||||||
skipped++;
|
} else {
|
||||||
continue;
|
try {
|
||||||
}
|
const osuUser = await getOsuUser(acc.account_id);
|
||||||
|
rank = osuUser?.statistics?.global_rank ?? null;
|
||||||
try {
|
} catch (err: any) {
|
||||||
const osuUser = await getOsuUser(acc.account_id);
|
console.error(` [ERR] user ${r.user_id} (osu! ${acc.account_id}):`, err.message);
|
||||||
const rank = osuUser?.statistics?.global_rank;
|
|
||||||
|
|
||||||
if (!rank || rank <= 0) {
|
|
||||||
console.log(` [SKIP] user ${r.user_id} (osu! ${acc.account_id}) — unranked`);
|
|
||||||
skipped++;
|
skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seedElo = rankToElo(rank);
|
|
||||||
// Adjust by win/loss differential to preserve some earned progress
|
|
||||||
const adjustment = (r.wins - r.losses) * 12;
|
|
||||||
const finalElo = Math.max(0, Math.min(3500, seedElo + adjustment));
|
|
||||||
|
|
||||||
await sql`UPDATE player_rating SET
|
|
||||||
elo = ${finalElo},
|
|
||||||
initial_elo = ${seedElo},
|
|
||||||
osu_rank_at_seed = ${rank},
|
|
||||||
updated_at = now()
|
|
||||||
WHERE id = ${r.id}`;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
` [OK] user ${r.user_id} — rank #${rank.toLocaleString()} → seed ${seedElo}, adj ${adjustment > 0 ? '+' : ''}${adjustment} → final ${finalElo} (was ${r.elo})`
|
|
||||||
);
|
|
||||||
updated++;
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(` [ERR] user ${r.user_id} (osu! ${acc.account_id}):`, err.message);
|
|
||||||
skipped++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unranked → rank 100,000 (~1000 ELO)
|
||||||
|
const effectiveRank = rank && rank > 0 ? rank : 100_000;
|
||||||
|
const seedElo = rankToElo(effectiveRank);
|
||||||
|
// Adjust by win/loss differential to preserve some earned progress
|
||||||
|
const adjustment = (r.wins - r.losses) * 12;
|
||||||
|
const finalElo = Math.max(0, Math.min(3500, seedElo + adjustment));
|
||||||
|
|
||||||
|
await sql`UPDATE player_rating SET
|
||||||
|
elo = ${finalElo},
|
||||||
|
initial_elo = ${seedElo},
|
||||||
|
osu_rank_at_seed = ${rank},
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = ${r.id}`;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
` [OK] user ${r.user_id} — rank #${rank ? rank.toLocaleString() : 'unranked (10M)'} → seed ${seedElo}, adj ${adjustment > 0 ? '+' : ''}${adjustment} → final ${finalElo} (was ${r.elo})`
|
||||||
|
);
|
||||||
|
updated++;
|
||||||
|
|
||||||
// Rate limit: 500ms between API calls
|
// Rate limit: 500ms between API calls
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import {
|
|||||||
matchParticipant,
|
matchParticipant,
|
||||||
matchParticipantPlayer
|
matchParticipantPlayer
|
||||||
} from '$lib/server/db/schema';
|
} from '$lib/server/db/schema';
|
||||||
import { eq, desc, inArray, sql } from 'drizzle-orm';
|
import { account } from '$lib/server/db/auth.schema';
|
||||||
|
import { eq, and, desc, inArray, sql } from 'drizzle-orm';
|
||||||
import { requireRole, setUserRole, isRootAdmin } from '$lib/server/permissions';
|
import { requireRole, setUserRole, isRootAdmin } from '$lib/server/permissions';
|
||||||
|
import { getUser as getOsuUser } from '$lib/server/osu/api';
|
||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals }) => {
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
@@ -127,11 +129,40 @@ export const actions: Actions = {
|
|||||||
|
|
||||||
resetRatings: async ({ locals }) => {
|
resetRatings: async ({ locals }) => {
|
||||||
requireRole(locals, 'admin');
|
requireRole(locals, 'admin');
|
||||||
const reset = await db
|
|
||||||
.update(playerRating)
|
const allUsers = await db.query.user.findMany();
|
||||||
.set({ elo: sql`COALESCE(initial_elo, 1000)`, wins: 0, losses: 0, updatedAt: new Date() })
|
let updated = 0;
|
||||||
.returning({ id: playerRating.id });
|
|
||||||
return { success: true, message: `Reset ${reset.length} rating${reset.length !== 1 ? 's' : ''} to initial ELO` };
|
for (const u of allUsers) {
|
||||||
|
let rank: number | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const osuAccount = await db.query.account.findFirst({
|
||||||
|
where: and(eq(account.userId, u.id), eq(account.providerId, 'osu'))
|
||||||
|
});
|
||||||
|
if (osuAccount?.accountId) {
|
||||||
|
const osuUser = await getOsuUser(osuAccount.accountId);
|
||||||
|
rank = osuUser?.statistics?.global_rank ?? null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// API failure — treat as unranked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unranked → rank 100,000 (~1000 ELO)
|
||||||
|
const effectiveRank = rank && rank > 0 ? rank : 100_000;
|
||||||
|
const rawElo = 3500 - Math.log10(effectiveRank) * 500;
|
||||||
|
const elo = Math.round(Math.max(0, Math.min(3500, rawElo)));
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(playerRating)
|
||||||
|
.set({ elo, initialElo: elo, osuRankAtSeed: rank, wins: 0, losses: 0, updatedAt: new Date() })
|
||||||
|
.where(eq(playerRating.userId, u.id));
|
||||||
|
|
||||||
|
console.log(`[Admin] Reset ${u.name}: rank #${rank ?? 'unranked'} → ${elo} ELO`);
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: `Re-seeded ${updated} rating${updated !== 1 ? 's' : ''} from osu! ranks` };
|
||||||
},
|
},
|
||||||
|
|
||||||
clearMatchHistory: async ({ locals }) => {
|
clearMatchHistory: async ({ locals }) => {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
{ action: 'clearQueue', label: 'Clear Queue', confirm: 'Remove everyone from the queue?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
{ action: 'clearQueue', label: 'Clear Queue', confirm: 'Remove everyone from the queue?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
||||||
{ action: 'expireInvites', label: 'Expire All Invites', confirm: 'Expire all pending invites?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
{ action: 'expireInvites', label: 'Expire All Invites', confirm: 'Expire all pending invites?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
||||||
{ action: 'clearNotifications', label: 'Clear Notifications', confirm: 'Delete all notifications?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
{ action: 'clearNotifications', label: 'Clear Notifications', confirm: 'Delete all notifications?', color: 'border-border text-text-secondary hover:bg-surface-700' },
|
||||||
{ action: 'resetRatings', label: 'Reset Ratings', confirm: 'Reset all ELO ratings to initial values? This cannot be undone.', color: 'border-red-500/30 text-red-400 hover:bg-red-500/10' },
|
{ action: 'resetRatings', label: 'Re-seed Ratings from osu!', confirm: 'Fetch all osu! ranks and re-seed ELO ratings? W/L records will be reset. Unranked players get 10M rank. This cannot be undone.', color: 'border-red-500/30 text-red-400 hover:bg-red-500/10' },
|
||||||
{ action: 'clearMatchHistory', label: 'Clear All Match Data', confirm: 'Delete ALL matches, scores, invites, and notifications? This cannot be undone.', color: 'border-red-500/30 text-red-400 hover:bg-red-500/10' },
|
{ action: 'clearMatchHistory', label: 'Clear All Match Data', confirm: 'Delete ALL matches, scores, invites, and notifications? This cannot be undone.', color: 'border-red-500/30 text-red-400 hover:bg-red-500/10' },
|
||||||
] as tool}
|
] as tool}
|
||||||
<form
|
<form
|
||||||
|
|||||||
Reference in New Issue
Block a user