Skip to content

Commit

Permalink
Improve performance estimates for finished contests (#53)
Browse files Browse the repository at this point in the history
...by considering that users with pre-contest rating 0 are in fact
unrated. Only applies to contests after the introduction of fake
ratings.
  • Loading branch information
nullchilly committed Apr 11, 2024
1 parent 20045fb commit 4e85ea8
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 3 deletions.
3 changes: 2 additions & 1 deletion carrot/src/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ function predictForRows(rows, ratingBeforeContest) {
function getFinal(contest) {
// Calculate and save the performances on the contest object if not already saved.
if (contest.performances === null) {
const ratingBeforeContest = new Map(contest.ratingChanges.map((c) => [c.handle, c.oldRating]));
const ratingBeforeContest = new Map(
contest.ratingChanges.map((c) => [c.handle, contest.oldRatings.get(c.handle)]));
const rows = contest.rows.filter((row) => {
const handle = row.party.members[0].handle;
return ratingBeforeContest.has(handle);
Expand Down
29 changes: 27 additions & 2 deletions carrot/src/background/cache/contests-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* Wrapper for all useful contest data.
*/
export class Contest {
constructor(contest, problems, rows, ratingChanges, fetchTime, isRated) {
constructor(contest, problems, rows, ratingChanges, oldRatings, fetchTime, isRated) {
this.contest = contest;
this.problems = problems;
this.rows = rows;
this.ratingChanges = ratingChanges; // undefined if isRated is not YES
this.oldRatings = oldRatings; // undefined if isRated is not YES
this.fetchTime = fetchTime;
this.isRated = isRated;
this.performances = null; // To be populated by someone who calculates the performances.
Expand Down Expand Up @@ -57,13 +58,15 @@ export class ContestsComplete {

const { contest, problems, rows } = await this.api.contest.standings(contestId);
let ratingChanges;
let oldRatings;
let isRated = Contest.IsRated.LIKELY;
if (contest.phase === 'FINISHED') {
try {
ratingChanges = await this.api.contest.ratingChanges(contestId);
if (ratingChanges) {
if (ratingChanges.length > 0) {
isRated = Contest.IsRated.YES;
oldRatings = adjustOldRatings(contestId, ratingChanges);
} else {
ratingChanges = undefined; // Reset to undefined if it was an empty array
}
Expand All @@ -79,7 +82,7 @@ export class ContestsComplete {
}
const isFinished = isRated === Contest.IsRated.NO || isRated === Contest.IsRated.YES;

const c = new Contest(contest, problems, rows, ratingChanges, Date.now(), isRated);
const c = new Contest(contest, problems, rows, ratingChanges, oldRatings, Date.now(), isRated);

// If the contest is finished, the contest data doesn't change so cache it.
// The exception is during new year's magic, when people change handles and handles on the
Expand All @@ -104,3 +107,25 @@ export class ContestsComplete {
return c;
}
}

const FAKE_RATINGS_SINCE_CONTEST = 1360;
const NEW_DEFAULT_RATING = 1400;

function adjustOldRatings(contestId, ratingChanges) {
const oldRatings = new Map();
if (contestId < FAKE_RATINGS_SINCE_CONTEST) {
for (const change of ratingChanges) {
oldRatings.set(change.handle, change.oldRating);
}
} else {
for (const change of ratingChanges) {
oldRatings.set(change.handle, change.oldRating == 0 ? NEW_DEFAULT_RATING : change.oldRating);
}
// Note: This a band-aid for CF's fake ratings (see Github #18).
// If CF tells us that a user had rating 0, we consider that the user is in fact unrated.
// This unfortunately means that a user who truly has rating 0 will be considered to have
// DEFAULT_RATING, but such cases are unlikely compared to the regular presence of unrated
// users.
}
return oldRatings;
}

0 comments on commit 4e85ea8

Please sign in to comment.