test.py
4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""
Test - instances of this class are individual tests
"""
# python standard library
from datetime import datetime
import json
import logging
from math import nan
logger = logging.getLogger(__name__)
# ============================================================================
class Test(dict):
"""
Each instance Test() is a concrete test of a single student.
A test can be in one of the states: ACTIVE, SUBMITTED, CORRECTED, QUIT
Methods:
t.start(student) - marks start of test (register time and state)
t.reset_answers() - remove all answers from the test
t.update_answer(ref, ans) - update answer of a given question
t.submit(answers_dict) - update answers, register time and state
t.correct_async()
t.correct() - corrects questions and compute grade, register state
t.giveup() - register the test as given up, answers are not corrected
t.save_json(filename) - save the current test to file in JSON format
"""
# ------------------------------------------------------------------------
def __init__(self, d: dict):
super().__init__(d)
self["grade"] = nan
self["comment"] = ""
# ------------------------------------------------------------------------
def start(self, uid: str) -> None:
"""
Register student id and start time in the test
"""
self["student"] = uid
self["start_time"] = datetime.now()
self["finish_time"] = None
self["state"] = "ACTIVE"
# ------------------------------------------------------------------------
def reset_answers(self) -> None:
"""Removes all answers from the test (clean)"""
for question in self["questions"]:
question["answer"] = None
# ------------------------------------------------------------------------
def update_answer(self, ref: str, ans) -> None:
"""updates one answer in the test"""
self["questions"][ref].set_answer(ans)
# ------------------------------------------------------------------------
def submit(self, answers: dict) -> None:
"""
Given a dictionary ans={'ref': 'some answer'} updates the answers of
multiple questions in the test.
Only affects the questions referred in the dictionary.
"""
self["finish_time"] = datetime.now()
for ref, ans in answers.items():
self["questions"][ref].set_answer(ans)
self["state"] = "SUBMITTED"
# ------------------------------------------------------------------------
async def correct_async(self) -> None:
"""Corrects all the answers of the test and computes the final grade"""
grade = 0.0
for question in self["questions"]:
await question.correct_async()
grade += question["grade"] * question["points"]
logger.debug(
"Correcting %30s: %3g%%", question["ref"], question["grade"] * 100
)
# truncate to avoid negative final grade and adjust scale
self["grade"] = max(0.0, grade) + self["scale"][0]
self["state"] = "CORRECTED"
# ------------------------------------------------------------------------
def correct(self) -> None:
"""Corrects all the answers of the test and computes the final grade"""
grade = 0.0
for question in self["questions"]:
question.correct()
grade += question["grade"] * question["points"]
logger.debug(
"Correcting %30s: %3g%%", question["ref"], question["grade"] * 100
)
# truncate to avoid negative final grade and adjust scale
self["grade"] = max(0.0, grade) + self["scale"][0]
self["state"] = "CORRECTED"
# ------------------------------------------------------------------------
def giveup(self) -> None:
"""Test is marqued as QUIT and is not corrected"""
self["finish_time"] = datetime.now()
self["state"] = "QUIT"
self["grade"] = 0.0
# ------------------------------------------------------------------------
def save_json(self, filename: str) -> None:
"""save test in JSON format"""
with open(filename, "w", encoding="utf-8") as file:
json.dump(self, file, indent=2, default=str) # str for datetime
# ------------------------------------------------------------------------
def __str__(self) -> str:
return "\n".join([f"{k}: {v}" for k, v in self.items()])