Skip to content

Commit 25df03d

Browse files
veluca93birka0
andauthored
Add new restricted feedback mode (#1378)
Co-authored-by: Németh Zsolt <37755878+birka0@users.noreply.github.com>
1 parent e5a8718 commit 25df03d

7 files changed

Lines changed: 67 additions & 18 deletions

File tree

cms/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"TOKEN_MODE_DISABLED", "TOKEN_MODE_FINITE", "TOKEN_MODE_INFINITE",
3434
"TOKEN_MODE_MIXED",
3535
"FEEDBACK_LEVEL_FULL", "FEEDBACK_LEVEL_RESTRICTED",
36+
"FEEDBACK_LEVEL_OI_RESTRICTED",
3637
# log
3738
# Nothing intended for external use, no need to advertise anything.
3839
# conf
@@ -68,6 +69,9 @@
6869
# Restricted set of information (no killing signal, time or memory, testcases
6970
# can be omitted).
7071
FEEDBACK_LEVEL_RESTRICTED = "restricted"
72+
# Restricted set of information in accordance with the Contest Rules of
73+
# the International Olympiad of Informatics
74+
FEEDBACK_LEVEL_OI_RESTRICTED = "oi_restricted"
7175

7276

7377
from .conf import Address, ServiceCoord, ConfigError, async_config, config

cms/db/task.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
Interval, Enum, BigInteger
3737

3838
from cms import TOKEN_MODE_DISABLED, TOKEN_MODE_FINITE, TOKEN_MODE_INFINITE, \
39-
FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED
39+
FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED, FEEDBACK_LEVEL_OI_RESTRICTED
4040
from cmscommon.constants import \
4141
SCORE_MODE_MAX, SCORE_MODE_MAX_SUBTASK, SCORE_MODE_MAX_TOKENED_LAST
4242
from . import Codename, Filename, FilenameSchemaArray, Digest, Base, Contest
@@ -199,6 +199,7 @@ class Task(Base):
199199
# reverse engineer task data.
200200
feedback_level: str = Column(
201201
Enum(FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED,
202+
FEEDBACK_LEVEL_OI_RESTRICTED,
202203
name="feedback_level"),
203204
nullable=False,
204205
default=FEEDBACK_LEVEL_RESTRICTED)

cms/grading/scoretypes/abc.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,13 @@ class ScoreTypeGroup(ScoreTypeAlone):
274274
</thead>
275275
<tbody>
276276
{% for tc in st["testcases"] %}
277-
{% if "outcome" in tc
278-
and (feedback_level == FEEDBACK_LEVEL_FULL
279-
or tc["show_in_restricted_feedback"]) %}
277+
{% set show_tc = "outcome" in tc
278+
and ((feedback_level == FEEDBACK_LEVEL_FULL)
279+
or (feedback_level == FEEDBACK_LEVEL_RESTRICTED
280+
and tc["show_in_restricted_feedback"])
281+
or (feedback_level == FEEDBACK_LEVEL_OI_RESTRICTED
282+
and tc["show_in_oi_restricted_feedback"])) %}
283+
{% if show_tc %}
280284
{% if tc["outcome"] == "Correct" %}
281285
<tr class="correct">
282286
{% elif tc["outcome"] == "Not correct" %}
@@ -307,16 +311,18 @@ class ScoreTypeGroup(ScoreTypeAlone):
307311
{% endif %}
308312
</tr>
309313
{% else %}
314+
{% if feedback_level != FEEDBACK_LEVEL_OI_RESTRICTED %}
310315
<tr class="undefined">
311316
<td class="idx">{{ loop.index }}</td>
312-
{% if feedback_level == FEEDBACK_LEVEL_FULL %}
317+
{% if feedback_level == FEEDBACK_LEVEL_FULL %}
313318
<td colspan="4">
314-
{% else %}
319+
{% else %}
315320
<td colspan="2">
316-
{% endif %}
321+
{% endif %}
317322
{% trans %}N/A{% endtrans %}
318323
</td>
319324
</tr>
325+
{% endif %}
320326
{% endif %}
321327
{% endfor %}
322328
</tbody>
@@ -412,25 +418,33 @@ def compute_score(self, submission_result):
412418

413419
testcases = []
414420
public_testcases = []
415-
previous_tc_all_correct = True
421+
# In "Restricted" feedback mode:
422+
# show until the first testcase with a lowest score
423+
# In "OI Restricted" feedback mode:
424+
# show only the first testcase with a lowest score
425+
426+
tc_first_lowest_idx = None
427+
tc_first_lowest_score = None
416428
for tc_idx in target:
429+
tc_score = float(evaluations[tc_idx].outcome)
417430
tc_outcome = self.get_public_outcome(
418-
float(evaluations[tc_idx].outcome), parameter)
431+
tc_score, parameter)
419432

420433
testcases.append({
421434
"idx": tc_idx,
422435
"outcome": tc_outcome,
423436
"text": evaluations[tc_idx].text,
424437
"time": evaluations[tc_idx].execution_time,
425438
"memory": evaluations[tc_idx].execution_memory,
426-
"show_in_restricted_feedback": previous_tc_all_correct})
439+
"show_in_restricted_feedback": self.public_testcases[tc_idx],
440+
"show_in_oi_restricted_feedback": self.public_testcases[tc_idx]})
441+
427442
if self.public_testcases[tc_idx]:
428443
public_testcases.append(testcases[-1])
429-
# Only block restricted feedback if this is the first
430-
# *public* non-correct testcase, otherwise we might be
431-
# leaking info on private testcases.
432-
if tc_outcome != "Correct":
433-
previous_tc_all_correct = False
444+
if tc_first_lowest_score is None or \
445+
tc_score < tc_first_lowest_score:
446+
tc_first_lowest_idx = tc_idx
447+
tc_first_lowest_score = tc_score
434448
else:
435449
public_testcases.append({"idx": tc_idx})
436450

@@ -439,6 +453,15 @@ def compute_score(self, submission_result):
439453
parameter)
440454
st_score = st_score_fraction * parameter[0]
441455

456+
if tc_first_lowest_idx is not None and st_score_fraction < 1.0:
457+
for tc in testcases:
458+
if not self.public_testcases[tc["idx"]]:
459+
continue
460+
tc["show_in_restricted_feedback"] = (
461+
tc["idx"] <= tc_first_lowest_idx)
462+
tc["show_in_oi_restricted_feedback"] = (
463+
tc["idx"] == tc_first_lowest_idx)
464+
442465
score += st_score
443466
subtasks.append({
444467
"idx": st_idx + 1,

cms/server/admin/templates/task.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ <h2 id="title_task_configuration" class="toggling_on">Task configuration</h2>
137137
</td>
138138
<td>
139139
<select name="feedback_level">
140+
<option value={{ FEEDBACK_LEVEL_OI_RESTRICTED }} {{ "selected" if task.feedback_level == FEEDBACK_LEVEL_OI_RESTRICTED else "" }}>OI Restricted</option>
140141
<option value={{ FEEDBACK_LEVEL_RESTRICTED }} {{ "selected" if task.feedback_level == FEEDBACK_LEVEL_RESTRICTED else "" }}>Restricted</option>
141142
<option value={{ FEEDBACK_LEVEL_FULL }} {{ "selected" if task.feedback_level == FEEDBACK_LEVEL_FULL else "" }}>Full</option>
142143
</select>

cms/server/jinja2_toolbox.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from jinja2.runtime import Context
3131

3232
from cms import TOKEN_MODE_DISABLED, TOKEN_MODE_FINITE, TOKEN_MODE_INFINITE, \
33-
TOKEN_MODE_MIXED, FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED
33+
TOKEN_MODE_MIXED, FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED, \
34+
FEEDBACK_LEVEL_OI_RESTRICTED
3435
from cms.db import SubmissionResult, UserTestResult
3536
from cms.db.task import Dataset
3637
from cms.grading import format_status_text
@@ -156,6 +157,7 @@ def instrument_generic_toolbox(env: Environment):
156157

157158
env.globals["FEEDBACK_LEVEL_FULL"] = FEEDBACK_LEVEL_FULL
158159
env.globals["FEEDBACK_LEVEL_RESTRICTED"] = FEEDBACK_LEVEL_RESTRICTED
160+
env.globals["FEEDBACK_LEVEL_OI_RESTRICTED"] = FEEDBACK_LEVEL_OI_RESTRICTED
159161

160162
env.filters["all"] = all_
161163
env.filters["any"] = any_

cmscontrib/loaders/italy_yaml.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import yaml
3434

3535
from cms import TOKEN_MODE_DISABLED, TOKEN_MODE_FINITE, TOKEN_MODE_INFINITE, \
36-
FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED
36+
FEEDBACK_LEVEL_FULL, FEEDBACK_LEVEL_RESTRICTED, FEEDBACK_LEVEL_OI_RESTRICTED
3737
from cms.db import Contest, User, Task, Statement, Attachment, Team, Dataset, \
3838
Manager, Testcase
3939
from cms.grading.languagemanager import LANGUAGES, HEADER_EXTS
@@ -465,6 +465,8 @@ def get_task(self, get_statement=True) -> Task | None:
465465
args["feedback_level"] = FEEDBACK_LEVEL_FULL
466466
elif conf.get("feedback_level", None) == FEEDBACK_LEVEL_RESTRICTED:
467467
args["feedback_level"] = FEEDBACK_LEVEL_RESTRICTED
468+
elif conf.get("feedback_level", None) == FEEDBACK_LEVEL_OI_RESTRICTED:
469+
args["feedback_level"] = FEEDBACK_LEVEL_OI_RESTRICTED
468470

469471
if conf.get("score_mode", None) == SCORE_MODE_MAX:
470472
args["score_mode"] = SCORE_MODE_MAX

docs/Configuring a contest.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,23 @@ Each of these fields can be left unset to prevent the corresponding limitation f
2929
Feedback to contestants
3030
=======================
3131

32-
Each testcase can be marked as public or private. After sending a submission, a contestant can always see its results on the public testcases: a brief passed / partial / not passed status for each testcase, and the partial score that is computable from the public testcases only. Note that input and output data are always hidden.
32+
Each testcase can be marked as public or private.
33+
34+
On public testcases, the information that a contestant receives depends on
35+
the feedback mode configured for the task. More precisely:
36+
37+
- if the feedback mode is "Full", a contestant can always see its results on
38+
the public testcases: a brief passed / partial / not passed status for each
39+
testcase, and the partial score that is computable from the public testcases
40+
only. Note that input and output data are always hidden.
41+
42+
- if the feedback mode is "Restricted", information is only shown about
43+
testcases up to the first testcase that has the lowest score for a given
44+
subtask (inclusive).
45+
46+
- if the feedback mode is "OI Restricted", information is only shown about
47+
the first testcase that has the lowest score for a given subtask (inclusive).
48+
This matches IOI contest rules.
3349

3450
Tokens were introduced to provide contestants with limited access to the detailed results of their submissions on the private testcases as well. If a contestant uses a token on a submission, then they will be able to see its result on all testcases, and the global score.
3551

0 commit comments

Comments
 (0)