3232
3333import logging
3434import re
35+ from typing import TypedDict , NotRequired
3536from abc import ABCMeta , abstractmethod
3637
3738from cms import FEEDBACK_LEVEL_RESTRICTED
@@ -192,6 +193,13 @@ class ScoreTypeAlone(ScoreType):
192193 pass
193194
194195
196+ class ScoreTypeGroupParameters (TypedDict ):
197+ max_score : float
198+ testcases : int | str
199+ threshold : NotRequired [float ]
200+ always_show_testcases : NotRequired [bool ]
201+
202+
195203class ScoreTypeGroup (ScoreTypeAlone ):
196204 """Intermediate class to manage tasks whose testcases are
197205 subdivided in groups (or subtasks). The score type parameters must
@@ -211,7 +219,7 @@ class ScoreTypeGroup(ScoreTypeAlone):
211219 # the format of parameters is impossible to type-hint correctly, it seems...
212220 # this hint is (mostly) correct for the methods this base class implements,
213221 # subclasses might need a longer tuple.
214- parameters : list [tuple [float , int | str ]]
222+ parameters : list [tuple [float , int | str ] | ScoreTypeGroupParameters ]
215223
216224 # Mark strings for localization.
217225 N_ ("Subtask %(index)s" )
@@ -333,6 +341,24 @@ class ScoreTypeGroup(ScoreTypeAlone):
333341</div>
334342{% endfor %}"""
335343
344+ def get_max_score (self , group_parameter : tuple [float , int | str ] | ScoreTypeGroupParameters ) -> float :
345+ if isinstance (group_parameter , tuple ) or isinstance (group_parameter , list ):
346+ return group_parameter [0 ]
347+ else :
348+ return group_parameter ["max_score" ]
349+
350+ def get_testcases (self , group_parameter : tuple [float , int | str ] | ScoreTypeGroupParameters ) -> int | str :
351+ if isinstance (group_parameter , tuple ) or isinstance (group_parameter , list ):
352+ return group_parameter [1 ]
353+ else :
354+ return group_parameter ["testcases" ]
355+
356+ def get_always_show_testcases (self , group_parameter : tuple [float , int | str ] | ScoreTypeGroupParameters ) -> bool :
357+ if isinstance (group_parameter , tuple ) or isinstance (group_parameter , list ):
358+ return False
359+ else :
360+ return group_parameter .get ("always_show_testcases" , False )
361+
336362 def retrieve_target_testcases (self ) -> list [list [str ]]:
337363 """Return the list of the target testcases for each subtask.
338364
@@ -345,7 +371,7 @@ def retrieve_target_testcases(self) -> list[list[str]]:
345371
346372 """
347373
348- t_params = [p [ 1 ] for p in self .parameters ]
374+ t_params = [self . get_testcases ( p ) for p in self .parameters ]
349375
350376 if all (isinstance (t , int ) for t in t_params ):
351377
@@ -393,10 +419,10 @@ def max_scores(self):
393419
394420 for st_idx , parameter in enumerate (self .parameters ):
395421 target = targets [st_idx ]
396- score += parameter [ 0 ]
422+ score += self . get_max_score ( parameter )
397423 if all (self .public_testcases [tc_idx ] for tc_idx in target ):
398- public_score += parameter [ 0 ]
399- headers += ["Subtask %d (%g)" % (st_idx , parameter [ 0 ] )]
424+ public_score += self . get_max_score ( parameter )
425+ headers += ["Subtask %d (%g)" % (st_idx , self . get_max_score ( parameter ) )]
400426
401427 return score , public_score , headers
402428
@@ -461,10 +487,10 @@ def compute_score(self, submission_result):
461487 st_score_fraction = self .reduce (
462488 [float (evaluations [tc_idx ].outcome ) for tc_idx in target ],
463489 parameter )
464- st_score = st_score_fraction * parameter [ 0 ]
490+ st_score = st_score_fraction * self . get_max_score ( parameter )
465491 rounded_score = round (st_score , score_precision )
466492
467- if tc_first_lowest_idx is not None and st_score_fraction < 1.0 :
493+ if tc_first_lowest_idx is not None and st_score_fraction < 1.0 and not self . get_always_show_testcases ( parameter ) :
468494 for tc in testcases :
469495 if not self .public_testcases [tc ["idx" ]]:
470496 continue
@@ -482,7 +508,7 @@ def compute_score(self, submission_result):
482508 "score_fraction" : st_score_fraction ,
483509 # But we also want the properly rounded score for display.
484510 "score" : rounded_score ,
485- "max_score" : parameter [ 0 ] ,
511+ "max_score" : self . get_max_score ( parameter ) ,
486512 "testcases" : testcases })
487513 if all (self .public_testcases [tc_idx ] for tc_idx in target ):
488514 public_score += st_score
@@ -495,7 +521,7 @@ def compute_score(self, submission_result):
495521 return score , subtasks , public_score , public_subtasks , ranking_details
496522
497523 @abstractmethod
498- def get_public_outcome (self , outcome : float , parameter : list ) -> str :
524+ def get_public_outcome (self , outcome : float , parameter : list | ScoreTypeGroupParameters ) -> str :
499525 """Return a public outcome from an outcome.
500526
501527 The public outcome is shown to the user, and this method
@@ -512,7 +538,7 @@ def get_public_outcome(self, outcome: float, parameter: list) -> str:
512538 pass
513539
514540 @abstractmethod
515- def reduce (self , outcomes : list [float ], parameter : list ) -> float :
541+ def reduce (self , outcomes : list [float ], parameter : list | ScoreTypeGroupParameters ) -> float :
516542 """Return the score of a subtask given the outcomes.
517543
518544 outcomes: the outcomes of the submission in
0 commit comments