Skip to content

Commit

Permalink
Handle the newly introduced turing Bonus class within mathrace_intera…
Browse files Browse the repository at this point in the history
…ction
  • Loading branch information
francesco-ballarin committed Aug 18, 2024
1 parent ebd6555 commit 9bc1bd6
Show file tree
Hide file tree
Showing 20 changed files with 142 additions and 48 deletions.
2 changes: 1 addition & 1 deletion mathrace_interaction/data/2015/disfida.journal
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@
4848 110 18 13 0 PROT:449 squadra 18, quesito 13: sbagliato
4852 101 aggiorna punteggio esercizi, orologio: 81
4852 110 27 22 0 PROT:450 squadra 27, quesito 22: sbagliato
4858 130 1 44 squadra 1 bonus 44 motivazione: conflitto di ordine con scelta jolly
# 4858 130 1 44 squadra 1 bonus 44 motivazione: conflitto di ordine con scelta jolly
4866 110 17 11 1 PROT:451 squadra 17, quesito 11: giusto
4872 110 20 10 0 PROT:452 squadra 20, quesito 10: sbagliato
4873 110 10 19 1 PROT:453 squadra 10, quesito 19: giusto
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2015/disfida.score
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
911,
792,
486,
# il risultato ufficiale è 402, dato da 352 + 50 di bonus per errore di immissione, che non consideriamo in turing:
352,
402,
843,
400,
519,
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2015/kangourou.score
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
329,
229,
293,
# il risultato ufficiale è 209, dato da 199 + 10 di bonus per errore di immissione, che non consideriamo in turing:
199,
209,
181,
521,
265,
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2017/disfida.score
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
369,
240,
608,
# il risultato ufficiale è 1811, dato da 1801 + 10 di bonus per errore di immissione, che non consideriamo in turing:
1801,
1811,
601,
372,
517,
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2018/disfida.score
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
585,
344,
1184,
# il risultato ufficiale è 1624, dato da 1614 + 10 di bonus per errore di immissione, che non consideriamo in turing:
1614,
1624,
496,
540,
477,
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2023/disfida_legacy_format.score
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# http://disfida.it/2023/moviola/index_alt.php
[531,
# il risultato ufficiale è 725, dato da 715 + 10 di bonus per errore di immissione, che non consideriamo in turing:
715,
725,
535,
542,
689,
Expand Down
3 changes: 1 addition & 2 deletions mathrace_interaction/data/2023/disfida_new_format.score
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# http://disfida.it/2023/moviola/index_alt.php
[531,
# il risultato ufficiale è 725, dato da 715 + 10 di bonus per errore di immissione, che non consideriamo in turing:
715,
725,
535,
542,
689,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def reorder_lists_in_imported_turing(imported_dict: TuringDict) -> None:
The turing dictionary representing the race, imported from a mathrace journal.
"""
imported_dict["eventi"] = list(sorted(imported_dict["eventi"], key=lambda e: (
e["orario"], e["subclass"], e["squadra_id"], e["problema"])))
e["orario"], e["subclass"], e["squadra_id"], e["problema"] if "problema" in e else None)))
imported_dict["soluzioni"] = list(sorted(imported_dict["soluzioni"], key=lambda e: e["problema"]))
imported_dict["squadre"] = list(sorted(imported_dict["squadre"], key=lambda e: e["num"]))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def strip_comments_and_unhandled_events_from_journal(journal_stream: typing.Text
def _is_handled_event(timestamp: str, event_type: str, journal_reader_class: type[AbstractJournalReader]) -> bool:
"""Determine if a line is associated to an handled event."""
blacklist = [
journal_reader_class.JOLLY_TIMEOUT, journal_reader_class.TIMER_UPDATE, journal_reader_class.MANUAL_BONUS,
journal_reader_class.RACE_SUSPENDED, journal_reader_class.RACE_RESUMED,
journal_reader_class.JOLLY_TIMEOUT, journal_reader_class.TIMER_UPDATE,
journal_reader_class.RACE_SUSPENDED, journal_reader_class.RACE_RESUMED
]
if hasattr(journal_reader_class, "TIMER_UPDATE_OTHER_TIMER"):
blacklist.append(journal_reader_class.TIMER_UPDATE_OTHER_TIMER)
Expand Down
20 changes: 14 additions & 6 deletions mathrace_interaction/mathrace_interaction/journal_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ def _initialize_teams_definition_with_default_definition(self, turing_dict: Turi

def _read_race_events_section(self, turing_dict: TuringDict) -> None:
"""Read all race events."""
# Allocate a mathrace only storage for manual corrections and timestamp offset
turing_dict["mathrace_only"]["manual_bonuses"] = list()
# Allocate a mathrace only storage for timestamp offset
turing_dict["mathrace_only"]["timestamp_offset"] = ""
# Process the remaining race events until the race end one
turing_dict["eventi"] = list()
Expand Down Expand Up @@ -400,10 +399,19 @@ def _process_race_end_event(self, timestamp_str: str, event_content: str, turing

def _process_manual_bonus_event(self, timestamp_str: str, event_content: str, turing_dict: TuringDict) -> None:
"""Process a manual bonus event."""
event_datetime = self._convert_timestamp_to_datetime(
timestamp_str, self.strict_timestamp_race_events, turing_dict)
turing_dict["mathrace_only"]["manual_bonuses"].append({
"orario": event_datetime.isoformat(), "motivazione": event_content})
# Allow manual bonus to be assigned even before the offset is computed, since setting it with
# a slightly wrong timestamp does not affect the overall score of the race
event_datetime = self._convert_timestamp_to_datetime(timestamp_str, False, turing_dict)
# Process the event content
team_id, bonus_points, _ = event_content.split(" ", maxsplit=2)
if int(team_id) <= 0:
raise RuntimeError(f"Invalid event content {event_content}: invalid team number {team_id}")
# Append to output dictionary. Note that manual bonus events do not have a mathrace event ID,
# hence it is not stored here.
turing_dict["eventi"].append({
"subclass": "Bonus", "orario": event_datetime.isoformat(),
"squadra_id" : int(team_id), "punteggio" : int(bonus_points)
})

def _convert_timestamp_to_datetime(
self, timestamp_str: str, strict: bool, turing_dict: TuringDict
Expand Down
27 changes: 25 additions & 2 deletions mathrace_interaction/mathrace_interaction/journal_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class JournalWriterR5539(AbstractJournalWriter):
JOLLY_SELECTION = JournalReaderR5539.JOLLY_SELECTION
ANSWER_SUBMISSION = JournalReaderR5539.ANSWER_SUBMISSION
RACE_END = JournalReaderR5539.RACE_END
MANUAL_BONUS = JournalReaderR5539.MANUAL_BONUS

def _write_race_definition_section(self, turing_dict: TuringDict) -> None:
"""Write the race definition section."""
Expand Down Expand Up @@ -195,6 +196,8 @@ def _store_race_event_line(self, line: str, turing_dict: TuringDict, event_id: i
return self._store_jolly_selection_event(line, turing_dict, event_id)
elif event["subclass"] == "Consegna":
return self._store_answer_submission_event(line, turing_dict, event_id)
elif event["subclass"] == "Bonus":
return self._store_manual_bonus_event(line, turing_dict, event_id)
else:
raise RuntimeError(f'Unhandled event type {event["subclass"]}')

Expand Down Expand Up @@ -231,6 +234,16 @@ def _store_answer_submission_event(self, line: str, turing_dict: TuringDict, eve
line = f"{line} PROT:{event_mathrace_id}"
return f'{line} squadra {team_id}, quesito {question_id}: {"giusto" if correct_answer else "sbagliato"}'

def _store_manual_bonus_event(self, line: str, turing_dict: TuringDict, event_id: int) -> str:
"""Store a manual bonus event."""
assert line == ""
event = turing_dict["eventi"][event_id]
event_timestamp = self._convert_datetime_to_timestamp(event["orario"], turing_dict)
team_id = event["squadra_id"]
bonus_points = event["punteggio"]
line = f"{event_timestamp} {self.MANUAL_BONUS} {team_id} {bonus_points}"
return f"{line} squadra {team_id} bonus {bonus_points}"

def _store_race_end_event(self, line: str, turing_dict: TuringDict) -> str:
"""Store the race end event."""
assert line == ""
Expand Down Expand Up @@ -272,6 +285,7 @@ class JournalWriterR11167(JournalWriterR5539):
JOLLY_SELECTION = JournalReaderR11167.JOLLY_SELECTION
ANSWER_SUBMISSION = JournalReaderR11167.ANSWER_SUBMISSION
RACE_END = JournalReaderR11167.RACE_END
MANUAL_BONUS = JournalReaderR11167.MANUAL_BONUS


class JournalWriterR11184(JournalWriterR11167):
Expand All @@ -287,9 +301,18 @@ class JournalWriterR11184(JournalWriterR11167):
The I/O stream is typically generated by open().
"""

def __init__(self, journal_stream: typing.TextIO) -> None:
super().__init__(journal_stream)
self._last_mathrace_event_id = 0

def _write_race_events_section(self, turing_dict: TuringDict) -> None:
self._last_mathrace_event_id = 0
super()._write_race_events_section(turing_dict)

def _determine_mathrace_event_id(self, event_id: int) -> str:
"""Set mathrace event ID equal to turing's one."""
return str(event_id + 1)
"""Increment the last event ID in mathrace and return the current ID."""
self._last_mathrace_event_id += 1
return str(self._last_mathrace_event_id)


class JournalWriterR11189(JournalWriterR11184):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def live_journal_to_live_turing(
open_input_file
A function that opens the input file, and returns a stream.
turing_models
The python module containing the turing models Gara, Consegna and Jolly.
The python module containing the turing models Gara, Consegna, Jolly and Bonus.
turing_race_id
The ID of the turing race to follow.
sleep
Expand All @@ -53,6 +53,7 @@ def live_journal_to_live_turing(
Squadra = getattr(turing_models, "Squadra") # noqa: N806
Consegna = getattr(turing_models, "Consegna") # noqa: N806
Jolly = getattr(turing_models, "Jolly") # noqa: N806
Bonus = getattr(turing_models, "Bonus") # noqa: N806

# Get the turing race from its ID
turing_race = Gara.objects.get(pk=turing_race_id)
Expand Down Expand Up @@ -152,11 +153,13 @@ def live_journal_to_live_turing(
del event_dict_copy["squadra_id"]
# Create an object of the event subclass
event_subclass = event_dict_copy.pop("subclass")
assert event_subclass in ("Consegna", "Jolly"), f"Invalid event subclass {event_subclass}"
assert event_subclass in ("Consegna", "Jolly", "Bonus"), f"Invalid event subclass {event_subclass}"
if event_subclass == "Consegna":
event_obj = Consegna(gara=turing_race, **event_dict_copy)
elif event_subclass == "Jolly":
event_obj = Jolly(gara=turing_race, **event_dict_copy)
elif event_subclass == "Bonus":
event_obj = Bonus(gara=turing_race, **event_dict_copy)
event_obj.save()
# Django requires to explicitly set the datetime field after saving, see Gara.create_from_dict
event_obj.orario = event_dict_copy["orario"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class LiveConversionTester(abc.ABC):
num_reads
Maximum number of reads from the live journal.
turing_models
The python module containing the turing models Gara, Consegna and Jolly.
The python module containing the turing models Gara, Consegna, Jolly and Bonus.
Attributes
----------
Expand All @@ -54,7 +54,7 @@ class LiveConversionTester(abc.ABC):
_live_journal
LiveJournal instance built from the input arguments journal_stream and num_reads.
_turing_models
The python module containing the turing models Gara, Consegna and Jolly, provided as input.
The python module containing the turing models Gara, Consegna, Jolly and Bonus, provided as input.
_time_counter
Time counter as in LiveJournal
"""
Expand Down Expand Up @@ -216,6 +216,7 @@ def _update_turing_and_termination_condition(self, time_counter: int) -> bool:
Squadra = getattr(self._turing_models, "Squadra") # noqa: N806
Consegna = getattr(self._turing_models, "Consegna") # noqa: N806
Jolly = getattr(self._turing_models, "Jolly") # noqa: N806
Bonus = getattr(self._turing_models, "Bonus") # noqa: N806
assert self._turing_race_id >= 0
turing_race = Gara.objects.get(pk=self._turing_race_id)
for event_dict in turing_dict["eventi"]:
Expand All @@ -236,11 +237,13 @@ def _update_turing_and_termination_condition(self, time_counter: int) -> bool:
del event_dict_copy["mathrace_id"]
# Create an object of the event subclass
event_subclass = event_dict_copy.pop("subclass")
assert event_subclass in ("Consegna", "Jolly"), f"Invalid event subclass {event_subclass}"
assert event_subclass in ("Consegna", "Jolly", "Bonus"), f"Invalid event subclass {event_subclass}"
if event_subclass == "Consegna":
event_obj = Consegna(gara=turing_race, **event_dict_copy)
elif event_subclass == "Jolly":
event_obj = Jolly(gara=turing_race, **event_dict_copy)
elif event_subclass == "Bonus":
event_obj = Bonus(gara=turing_race, **event_dict_copy)
event_obj.save()
# Django requires to explicitly set the datetime field after saving, see Gara.create_from_dict
event_obj.orario = event_dict_copy["orario"]
Expand Down
38 changes: 34 additions & 4 deletions mathrace_interaction/mathrace_interaction/test/mock_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
self.gara: Gara | None = None
self.orario: datetime.datetime | None = None
self.squadra: Squadra | None = None
self.problema: int | None = None
for key in ("gara", "squadra", "problema"):
for key in ("gara", "squadra"):
if key in kwargs:
setattr(self, key, kwargs[key])
assert "squadra_id" not in kwargs
Expand Down Expand Up @@ -123,9 +122,11 @@ class Consegna(Evento):

def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
super().__init__(**kwargs)
self.problema: int | None = None
self.risposta: int | None = None
if "risposta" in kwargs:
setattr(self, "risposta", kwargs["risposta"])
for key in ("problema", "risposta"):
if key in kwargs:
setattr(self, key, kwargs[key])

def to_dict(self) -> TuringDict:
"""Convert to a dictionary."""
Expand All @@ -143,6 +144,12 @@ def to_dict(self) -> TuringDict:
class Jolly(Evento):
"""A mock turing Jolly class."""

def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
super().__init__(**kwargs)
self.problema: int | None = None
if "problema" in kwargs:
setattr(self, "problema", kwargs["problema"])

def to_dict(self) -> TuringDict:
"""Convert to a dictionary."""
for key in ("orario", "squadra", "problema"):
Expand All @@ -155,6 +162,27 @@ def to_dict(self) -> TuringDict:
}


class Bonus(Evento):
"""A mock turing Bonus class."""

def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
super().__init__(**kwargs)
self.punteggio: int | None = None
if "punteggio" in kwargs:
setattr(self, "punteggio", kwargs["punteggio"])

def to_dict(self) -> TuringDict:
"""Convert to a dictionary."""
for key in ("orario", "squadra", "punteggio"):
assert getattr(self, key) is not None, f"{key} is still set to None"
return {
"subclass": "Bonus",
"orario": self.orario.isoformat(), # type: ignore[union-attr]
"punteggio": self.punteggio,
"squadra_id": self.squadra.num # type: ignore[union-attr]
}


class GaraObjects(list["Gara"]):
"""A mock list of objects returned by Gara.objects."""

Expand Down Expand Up @@ -259,6 +287,8 @@ def create_from_dict(cls, data: TuringDict) -> typing.Self:
e_class = Consegna
elif e_data["subclass"] == "Jolly":
e_class = Jolly
elif e_data["subclass"] == "Bonus":
e_class = Bonus
else: # pragma: no cover
raise RuntimeError("Invalid event")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ def strip_protocol_numbers(journal_content: str) -> str:
line_before_prot, line_after_prot = line.split("PROT:")
_, line_after_prot = line_after_prot.split("squadra")
output_lines.append(f"{line_before_prot} squadra {line_after_prot}")
else:
output_lines.append(line)
return "\n".join(output_lines)


def strip_manual_bonus_reason(journal_content: str) -> str:
"""Strip reason explaining why manual bonus was added."""
input_lines = [line for line in journal_content.split("\n")]
output_lines = []
for line in input_lines:
if "motivazione:" in line:
line_before_reason, _ = line.split(" motivazione:")
output_lines.append(line_before_reason)
else:
output_lines.append(line)
return "\n".join(output_lines)


Expand All @@ -78,6 +93,9 @@ def same_version_comparison(journal: typing.TextIO, journal_name: str, exported_
# different conventions on setting the grace period for answer submission after race end
stripped_journal = strip_race_end_line(stripped_journal)
exported_journal = strip_race_end_line(exported_journal)
# We do not store the reason for manual bonus, hence it must be removed from the input journal in order
# to be able to carry out a comparison
stripped_journal = strip_manual_bonus_reason(stripped_journal)
# Some journals have an expected timestamp offset due to the TIMER_UPDATE not happening on the minute
if journal_name == "2013/disfida.journal":
timer_offset = 24
Expand Down Expand Up @@ -114,7 +132,8 @@ def same_version_comparison(journal: typing.TextIO, journal_name: str, exported_
exported_journal = strip_race_setup_code_lines(exported_journal, "004")
exported_journal = strip_race_setup_code_lines(exported_journal, "005")
elif journal_name in (
"2020/disfida.journal", "2022/cesenatico_finale.journal", "2022/cesenatico_finale_femminile.journal",
"2020/disfida.journal", "2022/cesenatico_finale.journal",
"2022/cesenatico_finale_femminile.journal", "2022/cesenatico_pubblico.journal",
"2022/cesenatico_semifinale_A.journal", "2022/cesenatico_semifinale_B.journal",
"2022/cesenatico_semifinale_C.journal", "2022/cesenatico_semifinale_D.journal",
"2022/qualificazione_arezzo_cagliari_taranto_trento.journal",
Expand All @@ -135,14 +154,16 @@ def same_version_comparison(journal: typing.TextIO, journal_name: str, exported_
stripped_journal = strip_race_setup_code_lines(stripped_journal, "003")
exported_journal = strip_race_setup_code_lines(exported_journal, "002")
if journal_name not in (
"2022/cesenatico_finale.journal", "2023/qualificazione_femminile_1.journal",
"2023/qualificazione_femminile_2.journal", "2023/qualificazione_femminile_3.journal"
"2022/cesenatico_finale.journal", "2022/cesenatico_pubblico.journal",
"2023/qualificazione_femminile_1.journal", "2023/qualificazione_femminile_2.journal",
"2023/qualificazione_femminile_3.journal"
):
exported_journal = strip_race_setup_code_lines(exported_journal, "005")
# Some journals report in a slightly different format the bonus and superbonus entries.
# This typically happens when setting a large superbonus cardinality and adding zeros at the end
if journal_name in (
"2020/disfida.journal", "2022/cesenatico_finale.journal", "2022/cesenatico_finale_femminile.journal",
"2020/disfida.journal", "2022/cesenatico_finale.journal",
"2022/cesenatico_finale_femminile.journal", "2022/cesenatico_pubblico.journal",
"2022/cesenatico_semifinale_A.journal", "2022/cesenatico_semifinale_B.journal",
"2022/cesenatico_semifinale_C.journal", "2022/cesenatico_semifinale_D.journal",
"2022/qualificazione_arezzo_cagliari_taranto_trento.journal",
Expand Down
Loading

0 comments on commit 9bc1bd6

Please sign in to comment.