-
Notifications
You must be signed in to change notification settings - Fork 391
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
base: master
Are you sure you want to change the base?
Conversation
'score_std' => 'Standardised Score', | ||
'total_score' => 'total score', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
casing?
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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
6a991b2
to
95f49e3
Compare
f7d14e6
to
691584b
Compare
8eaba8b
to
44ef869
Compare
{ | ||
protected $signature = 'contest:calculate-scores {contestId}'; | ||
|
||
protected $description = 'Cleans up expired chat keep alives.'; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)
app/Models/ContestJudgeScore.php
Outdated
ContestJudgeVote | ||
::where('user_id', $user->getKey()) | ||
->whereIn('contest_entry_id', $contest->entries()->select('id')) | ||
->select('id') |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
$table->double('mean')->nullable(); | ||
$table->double('std_dev')->nullable(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)} |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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}> |
There was a problem hiding this comment.
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
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.