admin: Can gain points for each question answered // partial exercice solved

This commit is contained in:
nemunaire 2025-03-28 13:33:09 +01:00
parent 4ca2bc106a
commit 8e196136c3
8 changed files with 30 additions and 5 deletions

View File

@ -310,6 +310,7 @@ func ApplySettings(config *settings.Settings) {
fic.SubmissionCostBase = config.SubmissionCostBase
fic.SubmissionUniqueness = config.SubmissionUniqueness
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
fic.QuestionGainRatio = config.QuestionGainRatio
if config.DiscountedFactor != fic.DiscountedFactor {
fic.DiscountedFactor = config.DiscountedFactor
@ -329,6 +330,7 @@ func ResetSettings() error {
WChoiceCurCoefficient: 1,
GlobalScoreCoefficient: 1,
DiscountedFactor: 0,
QuestionGainRatio: 0,
UnlockedStandaloneExercices: 10,
UnlockedStandaloneExercicesByThemeStepValidation: 1,
UnlockedStandaloneExercicesByStandaloneExerciceValidation: 0,

View File

@ -104,6 +104,12 @@
<input type="text" class="form-control form-control-sm" id="submissionCostBase" ng-model="config.submissionCostBase" float ng-class="{'border-primary': config.submissionCostBase != dist_config.submissionCostBase}">
</div>
</div>
<div class="col-sm row" title="Accorder des points aux exercices partiellement résolu, par questions validée. Ce champ est le pourcentage de points que peut rapporter la complétion de toutes les questions d'un exercice. Par exemple avec 25%, un exercice avec 10 questions, chaque question validée rapportera GAIN * 25% / 10">
<label for="questionGainRatio" class="col-sm-8 col-form-label col-form-label-sm text-right text-truncate" ng-class="{'text-primary font-weight-bold': config.questionGainRatio != dist_config.questionGainRatio}">Gain par questions</label>
<div class="col-sm-4">
<input type="text" class="form-control form-control-sm" id="questionGainRatio" ng-model="config.questionGainRatio" float ng-class="{'border-primary': config.questionGainRatio != dist_config.questionGainRatio}">
</div>
</div>
</div>
<hr>

View File

@ -13,7 +13,7 @@
<th>Date</th>
</thead>
<tbody>
<tr ng-repeat="row in scores | filter: query | orderBy:'time'" ng-class="{'bg-danger': row.reason == 'Bonus flag', 'bg-ffound': row.reason == 'First blood', 'bg-wchoices': row.reason == 'Display choices', 'bg-success': row.reason == 'Validation', 'bg-info': row.reason == 'Hint', 'bg-warning': row.reason == 'Tries'}">
<tr ng-repeat="row in scores | filter: query | orderBy:'time'" ng-class="{'bg-danger': row.reason == 'Bonus flag', 'bg-ffound': row.reason == 'First blood', 'bg-wchoices': row.reason == 'Display choices', 'bg-success': row.reason == 'Validation', 'bg-info': row.reason == 'Hint', 'bg-secondary': row.reason.startsWith('Response '), 'bg-warning': row.reason == 'Tries'}">
<td>
<a ng-repeat="exercice in exercices" ng-if="exercice.id == row.id_exercice" href="exercices/{{ row.id_exercice }}">{{ exercice.title }}</a>
</td>
@ -23,9 +23,12 @@
<td>
{{ row.points * row.coeff }}
</td>
<td>
<td ng-if="!row.reason.startsWith('Response ')">
{{ row.points }} * {{ row.coeff }}
</td>
<td ng-if="row.reason.startsWith('Response ')">
{{ row.points }} * {{ settings.questionGainRatio }} / {{ settings.questionGainRatio / row.coeff }}
</td>
<td>
<nobr title="{{ row.time }}">{{ row.time | date:"mediumTime" }}</nobr>
</td>

View File

@ -90,6 +90,7 @@ func reloadSettings(config *settings.Settings) {
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
fic.DiscountedFactor = config.DiscountedFactor
fic.QuestionGainRatio = config.QuestionGainRatio
}
func main() {

View File

@ -10,6 +10,7 @@
import DateFormat from '$lib/components/DateFormat.svelte';
import { my } from '$lib/stores/my.js';
import { settings } from '$lib/stores/settings.js';
import { themes, exercices_idx } from '$lib/stores/themes.js';
let req = null;
@ -55,6 +56,10 @@
{:else if row.reason == "Display choices"}
<Badge color="secondary"><Icon name="info-square" /></Badge>
Échange champ de texte contre liste de choix
{:else if row.reason.startsWith("Response ")}
{@const fields = row.reason.split(" ")}
<Badge color="secondary"><Icon name="clipboard2-check" /></Badge>
Validation {fields[1]}
{:else}
<Badge color="primary"><Icon name="question" /></Badge>
{row.reason}
@ -66,7 +71,7 @@
{/if}
</Column>
<Column header="Détail">
<span title="Valeur initiale (cette valeur est fixe)">{Math.trunc(10*row.points)/10}</span> &times; <span title="Coefficient multiplicateur (il varie selon les événements en cours sur la plateforme)">{row.coeff}</span>
<span title="Valeur initiale (cette valeur est fixe)">{Math.trunc(10*row.points)/10}</span> &times; {#if row.reason.startsWith("Response ")}<span title="Pourcentage des points accordés pour avoir répondu aux questions d'un défi, sans avoir validé entièrement le défi">{$settings.questionGainRatio}</span> &divide; <span title="Nombre de questions du défi">{$settings.questionGainRatio / row.coeff}</span>{:else}<span title="Coefficient multiplicateur (il varie selon les événements en cours sur la plateforme)">{row.coeff}</span>{/if}
</Column>
<Column header="Points">
{Math.trunc(10*row.points * row.coeff)/10}

View File

@ -30,7 +30,7 @@ func reloadSettings(config *settings.Settings) {
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0
if allowRegistration != config.AllowRegistration || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedStandaloneExercices != config.UnlockedStandaloneExercices || fic.UnlockedStandaloneExercicesByThemeStepValidation != config.UnlockedStandaloneExercicesByThemeStepValidation || fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation != config.UnlockedStandaloneExercicesByStandaloneExerciceValidation || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor || fic.HideCaseSensitivity != config.HideCaseSensitivity {
if allowRegistration != config.AllowRegistration || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedStandaloneExercices != config.UnlockedStandaloneExercices || fic.UnlockedStandaloneExercicesByThemeStepValidation != config.UnlockedStandaloneExercicesByThemeStepValidation || fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation != config.UnlockedStandaloneExercicesByStandaloneExerciceValidation || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor || fic.QuestionGainRatio != config.QuestionGainRatio || fic.HideCaseSensitivity != config.HideCaseSensitivity {
allowRegistration = config.AllowRegistration
fic.PartialValidation = config.PartialValidation
@ -48,6 +48,7 @@ func reloadSettings(config *settings.Settings) {
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
fic.HideCaseSensitivity = config.HideCaseSensitivity
fic.DiscountedFactor = config.DiscountedFactor
fic.QuestionGainRatio = config.QuestionGainRatio
if !skipInitialGeneration {
log.Println("Generating files...")

View File

@ -23,6 +23,9 @@ var CountOnlyNotGoodTries = false
// DiscountedFactor stores the percentage of the exercice's gain lost on each validation.
var DiscountedFactor = 0.0
// QuestionGainRatio is the ratio given to a partially solved exercice in the final scoreboard.
var QuestionGainRatio = 0.0
func exoptsQuery(whereExo string) string {
tries_table := "exercice_tries"
if SubmissionUniqueness {
@ -39,8 +42,10 @@ func exoptsQuery(whereExo string) string {
}
query := `SELECT S.id_team, S.time, E.gain AS points, coeff, S.reason, S.id_exercice FROM (
SELECT id_team, F.id_exercice AS id_exercice, time, ` + fmt.Sprintf("%f", QuestionGainRatio) + ` / (COALESCE(T.total_flags, 0) + COALESCE(TMCQ.total_mcqs, 0)) AS coeff, CONCAT("Response flag ", F.id_flag) AS reason FROM flag_found B INNER JOIN exercice_flags F ON F.id_flag = B.id_flag LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_flags FROM exercice_flags GROUP BY id_exercice) T ON F.id_exercice = T.id_exercice LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_mcqs FROM exercice_mcq GROUP BY id_exercice) TMCQ ON F.id_exercice = TMCQ.id_exercice WHERE F.bonus_gain = 0 UNION
SELECT id_team, F.id_exercice AS id_exercice, time, ` + fmt.Sprintf("%f", QuestionGainRatio) + ` / (COALESCE(T.total_flags, 0) + COALESCE(TMCQ.total_mcqs, 0)) AS coeff, CONCAT("Response MCQ ", F.id_mcq) AS reason FROM mcq_found B INNER JOIN exercice_mcq F ON F.id_mcq = B.id_mcq LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_flags FROM exercice_flags GROUP BY id_exercice) T ON F.id_exercice = T.id_exercice LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_mcqs FROM exercice_mcq GROUP BY id_exercice) TMCQ ON F.id_exercice = TMCQ.id_exercice UNION
SELECT id_team, id_exercice, time, ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff, "First blood" AS reason FROM exercice_solved JOIN (SELECT id_exercice, MIN(time) time FROM exercice_solved GROUP BY id_exercice) d1 USING (id_exercice, time) UNION
SELECT id_team, id_exercice, time, coefficient AS coeff, "Validation" AS reason FROM exercice_solved
SELECT id_team, id_exercice, time, coefficient - ` + fmt.Sprintf("%f", QuestionGainRatio) + ` AS coeff, "Validation" AS reason FROM exercice_solved
) S INNER JOIN ` + exercices_table + ` E ON S.id_exercice = E.id_exercice UNION ALL
SELECT B.id_team, B.time, F.bonus_gain AS points, 1 AS coeff, "Bonus flag" AS reason, F.id_exercice FROM flag_found B INNER JOIN exercice_flags F ON F.id_flag = B.id_flag WHERE F.bonus_gain != 0 HAVING points != 0 UNION ALL
SELECT id_team, MAX(time) AS time, (FLOOR(COUNT(*)/10 - 1) * (FLOOR(COUNT(*)/10)))/0.2 + (FLOOR(COUNT(*)/10) * (COUNT(*)%10)) AS points, ` + fmt.Sprintf("%f", SubmissionCostBase*-1) + ` AS coeff, "Tries" AS reason, id_exercice FROM ` + tries_table + ` S GROUP BY id_exercice, id_team`

View File

@ -46,6 +46,8 @@ type Settings struct {
GlobalScoreCoefficient float64 `json:"globalScoreCoefficient"`
// DiscountedFactor stores the percentage of the exercice's gain lost on each validation.
DiscountedFactor float64 `json:"discountedFactor,omitempty"`
// QuestionGainRatio is the ratio given to a partially solved exercice in the final scoreboard.
QuestionGainRatio float64 `json:"questionGainRatio,omitempty"`
// AllowRegistration permits unregistered Team to register themselves.
AllowRegistration bool `json:"allowRegistration,omitempty"`