Skip to content

Commit 7b20b0c

Browse files
committed
bug: fix reassignment of winner on finished match
1 parent aad9e43 commit 7b20b0c

4 files changed

Lines changed: 61 additions & 12 deletions

File tree

hasura/functions/match/match_player_elo.sql

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ DECLARE
4949
_team_avg_kda FLOAT;
5050
_player_damage_percent FLOAT;
5151
match_type text;
52+
53+
-- Series (best-of) scaling
54+
_player_map_wins INT := 0;
55+
_player_map_losses INT := 0;
56+
_series_multiplier INT := 1;
5257
BEGIN
5358
SELECT "type" INTO match_type FROM match_options WHERE id = match_record.match_options_id;
5459

@@ -79,6 +84,19 @@ BEGIN
7984
_opponent_lineup_id := match_record.lineup_1_id;
8085
END IF;
8186

87+
-- Series multiplier: scale ELO by the net map differential for this player's team.
88+
-- BO1 win gives 1x, BO3 2-0 gives 2x, BO3 2-1 gives 1x, BO5 3-0 gives 3x, etc.
89+
-- GREATEST(..., 1) keeps ELO moving for ties / zero-recorded-winner forfeits.
90+
SELECT
91+
COUNT(*) FILTER (WHERE mm.winning_lineup_id = _player_lineup_id),
92+
COUNT(*) FILTER (WHERE mm.winning_lineup_id = _opponent_lineup_id)
93+
INTO _player_map_wins, _player_map_losses
94+
FROM match_maps mm
95+
WHERE mm.match_id = match_record.id
96+
AND mm.winning_lineup_id IS NOT NULL;
97+
98+
_series_multiplier := GREATEST(ABS(_player_map_wins - _player_map_losses), 1);
99+
82100
-- Calculate average ELO for player's team
83101
-- First get the sum of all previous ELO changes for each player in the team
84102
SELECT
@@ -215,8 +233,8 @@ BEGIN
215233
END IF;
216234

217235
-- Calculate the elo change (round to nearest integer)
218-
-- ELO change formula: New Rating = Old Rating + K * (Actual Score - Expected Score) * Performance Multiplier
219-
_elo_change := ROUND(_k_factor * (_actual_score - _expected_score) * _performance_multiplier);
236+
-- ELO change formula: New Rating = Old Rating + K * (Actual Score - Expected Score) * Performance Multiplier * Series Multiplier
237+
_elo_change := ROUND(_k_factor * (_actual_score - _expected_score) * _performance_multiplier * _series_multiplier);
220238

221239
-- Return the elo change as JSON with detailed information
222240
RETURN jsonb_build_object(
@@ -234,7 +252,10 @@ BEGIN
234252
'kda', _player_kda::FLOAT,
235253
'team_avg_kda', _team_avg_kda::FLOAT,
236254
'damage_percent', _player_damage_percent,
237-
'performance_multiplier', _performance_multiplier
255+
'performance_multiplier', _performance_multiplier,
256+
'map_wins', _player_map_wins,
257+
'map_losses', _player_map_losses,
258+
'series_multiplier', _series_multiplier
238259
);
239260
END;
240261
$$ LANGUAGE plpgsql;

hasura/metadata/databases/default/tables/public_v_player_elo.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ select_permissions:
2626
- k_factor
2727
- kda
2828
- kills
29+
- map_losses
30+
- map_wins
2931
- match_created_at
3032
- match_id
3133
- match_result
@@ -34,6 +36,7 @@ select_permissions:
3436
- player_name
3537
- player_steam_id
3638
- player_team_elo_avg
39+
- series_multiplier
3740
- team_avg_kda
3841
- type
3942
- updated_elo

hasura/views/v_player_elo.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ SELECT
2727
(elo_data->>'kda')::FLOAT AS kda,
2828
(elo_data->>'team_avg_kda')::FLOAT AS team_avg_kda,
2929
(elo_data->>'damage_percent')::FLOAT AS damage_percent,
30-
(elo_data->>'performance_multiplier')::FLOAT AS performance_multiplier
30+
(elo_data->>'performance_multiplier')::FLOAT AS performance_multiplier,
31+
(elo_data->>'map_wins')::INTEGER AS map_wins,
32+
(elo_data->>'map_losses')::INTEGER AS map_losses,
33+
(elo_data->>'series_multiplier')::INTEGER AS series_multiplier
3134
FROM
3235
matches m
3336
JOIN

src/matches/matches.module.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,21 +193,28 @@ export class MatchesModule implements NestModule {
193193
}
194194

195195
/**
196-
* TODO - this is required one time
196+
* Runs once per ELO formula change. Keyed off a settings marker so upgrades
197+
* that change the ELO math (e.g. best-of series multiplier) re-generate
198+
* historical rows. Bump SERIES_MULTIPLIER_BACKFILL_MARKER when the formula
199+
* changes again.
197200
*/
198201
async generatePlayerRatings() {
199-
const { player_elo_aggregate } = await this.hasuraService.query({
200-
player_elo_aggregate: {
201-
aggregate: {
202-
count: true,
203-
},
202+
const SERIES_MULTIPLIER_BACKFILL_MARKER =
203+
"player_elo_backfill_series_multiplier_v1";
204+
205+
const { settings_by_pk } = await this.hasuraService.query({
206+
settings_by_pk: {
207+
__args: { name: SERIES_MULTIPLIER_BACKFILL_MARKER },
208+
value: true,
204209
},
205210
});
206211

207-
if (player_elo_aggregate.aggregate.count > 0) {
212+
if (settings_by_pk) {
208213
return;
209214
}
210215

216+
await this.postgres.query(`TRUNCATE TABLE player_elo`);
217+
211218
const matches = await this.hasuraService.query({
212219
matches: {
213220
__args: {
@@ -240,9 +247,24 @@ export class MatchesModule implements NestModule {
240247
`Failed to generate player ratings for match ${match.id}:`,
241248
error,
242249
);
243-
// Continue with the next match instead of failing completely
244250
}
245251
}
252+
253+
await this.hasuraService.mutation({
254+
insert_settings_one: {
255+
__args: {
256+
object: {
257+
name: SERIES_MULTIPLIER_BACKFILL_MARKER,
258+
value: new Date().toISOString(),
259+
},
260+
on_conflict: {
261+
constraint: "settings_pkey",
262+
update_columns: ["value"],
263+
},
264+
},
265+
__typename: true,
266+
},
267+
});
246268
}
247269

248270
configure(consumer: MiddlewareConsumer) {

0 commit comments

Comments
 (0)