Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/matches/season-elo-backfill.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
e_player_roles_enum,
} from "../../generated";
import { PlayerEloRecomputeService } from "./player-elo-recompute.service";
import { InjectQueue } from "@nestjs/bullmq";
import { Queue } from "bullmq";
import { TypesenseQueues } from "../type-sense/enums/TypesenseQueues";
import { RefreshAllPlayersJob } from "../type-sense/jobs/RefreshAllPlayers";

// Redis-backed so any replica can read progress / request cancellation. Backfill
// runs on a concurrency-1 queue, so a single global status blob is sufficient.
Expand Down Expand Up @@ -40,6 +44,8 @@ export class SeasonEloBackfillService {
private readonly cache: CacheService,
private readonly notifications: NotificationsService,
private readonly eloRecompute: PlayerEloRecomputeService,
@InjectQueue(TypesenseQueues.PlayerReindex)
private readonly reindexQueue: Queue,
) {}

public async isRunning(): Promise<boolean> {
Expand Down Expand Up @@ -165,12 +171,22 @@ export class SeasonEloBackfillService {
status.finished_at = new Date().toISOString();
await this.saveStatus(status, FINAL_TTL_SECONDS);
await this.cache.forget(CANCEL_KEY);
// Per-row player_elo search events stay suppressed for the whole run: the
// backfill recomputes ELO only and deliberately does NOT reindex the player
// search index (use the manual "Reindex Search" action when desired).
// Per-row player_elo search events stay suppressed for the whole run, so
// the search index would otherwise keep serving pre-backfill ELO forever.
// One deduped reindex at the end syncs it without the per-row fan-out.
await this.eloRecompute.setSuppressEvents(false);
await this.cache.forget(LOCK_KEY);

await this.reindexQueue.add(
RefreshAllPlayersJob.name,
{},
{
jobId: RefreshAllPlayersJob.name,
removeOnComplete: true,
removeOnFail: true,
},
);

this.logger.log(
`[season-backfill] finished season ${seasonId}: ${status.completed}/${status.total} processed, ${status.failed} failed${status.canceled ? " (canceled)" : ""}`,
);
Expand Down
37 changes: 37 additions & 0 deletions src/type-sense/type-sense.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ export class TypeSenseService {
},
{ name: "steam_id", type: "string", index: true },
{ name: "teams", type: "string[]", optional: true },
// Rank/range on the same number the UI shows (competitive, else wingman,
// else duel). Sorting on elo_competitive alone leaves every wingman/duel
// only player tied as "missing", so asc/desc never reorders them.
{
name: "elo",
type: "int32",
optional: true,
sort: true,
index: true,
},
{
name: "tournament_elo",
type: "int32",
optional: true,
sort: true,
index: true,
},
{
name: "elo_competitive",
type: "int32",
Expand Down Expand Up @@ -105,6 +122,7 @@ export class TypeSenseService {
{ name: "deaths", type: "int32", optional: true },
{ name: "wins", type: "int32", optional: true },
{ name: "losses", type: "int32", optional: true },
{ name: "total_matches", type: "int32", optional: true, index: true },
{ name: "country", type: "string", optional: true, index: true },
{ name: "sanctions", type: "int32", optional: true, index: true },
{ name: "is_banned", type: "bool", optional: true, index: true },
Expand Down Expand Up @@ -308,6 +326,7 @@ export class TypeSenseService {
last_sign_in_at: true,
wins: true,
losses: true,
total_matches: true,
stats: {
kills: true,
deaths: true,
Expand Down Expand Up @@ -389,6 +408,19 @@ export class TypeSenseService {
Object.assign({}, player, elo, {
id: steamId,
steam_id: steamId,
elo: TypeSenseService.primaryElo(
elo.elo_competitive,
elo.elo_wingman,
elo.elo_duel,
),
tournament_elo: TypeSenseService.primaryElo(
elo.tournament_elo_competitive,
elo.tournament_elo_wingman,
elo.tournament_elo_duel,
),
total_matches: player.total_matches
? parseInt(String(player.total_matches), 10)
: 0,
kills: player.stats?.kills
? parseInt(String(player.stats.kills), 10)
: 0,
Expand All @@ -408,6 +440,11 @@ export class TypeSenseService {
);
}

// Mirrors PlayerElo's display order so the sorted value is the one on screen.
private static primaryElo(...values: Array<number | null>): number | null {
return values.find((value) => value !== null) ?? null;
}

public async removePlayer(steamId: string) {
await this.client.collections("players").documents(steamId).delete();
}
Expand Down
Loading