Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standardised scoring for judged contests #12014

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

notbakaneko
Copy link
Collaborator

@notbakaneko notbakaneko commented Mar 17, 2025

closes #11390

the option is "is_score_standardised": true.
I'm not sure how/when the scoring should be calculated (interop/admin page, etc), so I added a a command contest:calculate-scores for now.

  • store judge's standard deviation so it doesn't need to get recalculated every time
  • order by standardised score
  • better display in results?
  • add something to trigger calculation

Comment on lines +33 to 34
'score_std' => 'Standardised Score',
'total_score' => 'total score',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

casing?

Copy link
Collaborator Author

@notbakaneko notbakaneko Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is shown with Score not total score

$mean = array_sum($values) / $size;

return [
sqrt(array_sum(array_map(fn ($value) => pow($value - $mean, 2), $values)) / $size),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks correct


public function stdDev()
{
// TODO: treat missing scores as 0?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all judges are supposed to judge all entries and they're poked if they haven't
default of 0 could make sense if it's to indicate that something is wrong, though ideally we'd know by counting the number of votes

@notbakaneko notbakaneko force-pushed the feature/contents-score-std branch from 6a991b2 to 95f49e3 Compare March 18, 2025 19:26
@notbakaneko notbakaneko marked this pull request as ready for review March 21, 2025 13:34
@notbakaneko notbakaneko force-pushed the feature/contents-score-std branch from 8eaba8b to 44ef869 Compare March 25, 2025 10:18
{
protected $signature = 'contest:calculate-scores {contestId}';

protected $description = 'Cleans up expired chat keep alives.';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice description

return $query;
}

public function scopeWithScore(Builder $query, array $options): Builder
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this accept Contest model instead? So the isset($options...) and all other craps don't need to be reimplemented (apparently that's how it's initially done? but then changed a few times until this one but there's no clear reason in the commit messages)

ContestJudgeVote
::where('user_id', $user->getKey())
->whereIn('contest_entry_id', $contest->entries()->select('id'))
->select('id')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a weird indent

@@ -965,6 +965,17 @@ function ujs_redirect($url, $status = 200)
}
}

function std_dev(array $values): array
{
$size = count($values);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can probably use a minimum value of 1

Comment on lines 17 to 18
$table->double('mean')->nullable();
$table->double('std_dev')->nullable();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't these just be stored into variable during calculation?

something like this in contest calculateScores (or probably more like calculateStdScores)

(also added explicit count option for stddev calculation)

public function calculateScores(): void
{
    $entryCount = $this->entries()->count();
    foreach ($this->contestJudges as $judge) {
        $votesQuery = ContestJudgeVote
            ::where('user_id', $judge->user_id)
            ->whereHas('entry', fn ($q) => $q->where('contest_id', $this->getKey()));

        $entryTotalScores = ContestJudgeScore
            ::whereIn('contest_judge_vote_id', (clone $votesQuery)->select('id'))
            ->groupBy('contest_judge_vote_id')
            ->selectRaw('contest_judge_vote_id, SUM(value) AS total')
            ->get()
            ->keyBy('contest_judge_vote_id');

        [$stdDev, $mean] = std_dev($entryTotalScores->pluck('total')->all(), $entryCount);

        foreach ($votesQuery->get() as $vote) {
            $entryTotalScore = $entryTotalScores[$vote->getKey()]?->total ?? 0;
            $vote->update([
                'total_score_std' => $stdDev === 0.0 ? 0 : ($entryTotalScore - $mean) / $stdDev,
            ]);
        }
    }
}

@@ -29,6 +29,7 @@ export default class Header extends React.PureComponent<Props> {

render() {
const totalScore = `${this.props.entry.results.votes}/${this.props.contest.max_total_score}`;
const totalScoreStd = this.props.entry.judge_votes.reduce((total, vote) => (vote.total_score_std ?? NaN) + total, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the default when missing be 0 instead?

...or maybe just actually include and use the this.props.entry.results.score_std value

<ValueDisplay
label={trans('contest.judge_results.total_score_std')}
modifiers='judge-results'
value={totalScoreStd.toFixed(2)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatNumber

@@ -4,14 +4,15 @@
.contest-judge-results-scores {
display: grid;
align-items: center;
grid-gap: 5px;
gap: 5px;
// category-name value
grid-template-columns: minmax(auto, 120px) auto;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated but this probably should be auto 1fr otherwise this happens:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relativeVotePercentage should be adjusted to be something like 100 * (entry.results.score_std - min_score_std) / (max_score_std - min_score_std)

<div key={score.id} className='contest-judge-results-scores__row'>
<div className='contest-judge-results-scores__col'>
<div className='contest-judge-entry__description-icon' title={category?.description}>
<div className='contest-judge-entry__description-icon' title={category.description}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated but the class doesn't really do anything useful apart of being a random element of an entirely different block

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an option for standardized scoring in contest judging system
3 participants