-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclasses.py
More file actions
120 lines (99 loc) · 3.96 KB
/
Copy pathclasses.py
File metadata and controls
120 lines (99 loc) · 3.96 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
116
117
118
119
120
from dataclasses import dataclass
from typing import Any
from unidecode import unidecode
from utils import format_problem_name, levenshtein_distance
@dataclass
class Problem:
name: str
memorize: str
prompt: str
solution: str
exposure_ms: int
problem_type: str = "" # matrix or single line
def __post_init__(self) -> None:
if not isinstance(self.name, str) or not self.name.strip():
raise ValueError("name must be a non-empty, non-blank string")
if not isinstance(self.memorize, str) or not self.memorize.strip():
raise ValueError("memorize must be a non-empty, non-blank string")
if not isinstance(self.prompt, str) or not self.prompt.strip():
raise ValueError("prompt must be a non-empty, non-blank string")
if not isinstance(self.solution, str) or not self.solution.strip():
raise ValueError("solution must be a non-empty, non-blank string")
if not isinstance(self.exposure_ms, int) or self.exposure_ms <= 0:
raise ValueError("exposure_ms must be a positive integer")
if not isinstance(self.problem_type, str):
raise TypeError("problem_type must be str")
@classmethod
def display_name(cls) -> str:
return format_problem_name(cls.__name__)
@staticmethod
def create(**kwargs):
pass
def evaluate_solution(self, user_input):
"""
Evaluate how close the user's input is to the correct solution.
Returns a score between 0.0 (no match) and 1.0 (perfect match).
"""
if user_input is None:
return 0.0
# Normalize both inputs using unidecode
normalized_user = unidecode(str(user_input).strip().lower())
normalized_solution = unidecode(self.solution.strip().lower())
# Check for exact match first
if normalized_user == normalized_solution:
return 1.0
# Calculate Levenshtein distance
distance = levenshtein_distance(normalized_user, normalized_solution)
# Use the maximum length as the denominator to ensure score is between 0 and 1
max_length = max(len(normalized_user), len(normalized_solution))
# Handle edge case where both strings are empty
if max_length == 0:
return 1.0
# Calculate score: 1.0 for perfect match, approaching 0.0 for maximum distance
score = max(0.0, 1.0 - (distance / max_length))
return score
def to_dict(self):
return {
'name': self.name,
'memorize': self.memorize,
'prompt': self.prompt,
'solution': self.solution,
'exposure_ms': self.exposure_ms,
'problem_type': self.problem_type
}
def __repr__(self):
return (f'name={self.name}, '
f'memorize={self.memorize}, '
f'prompt={self.prompt}, '
f'solution={self.solution}, '
f'exposure_ms={self.exposure_ms}, '
f'problem_type={self.problem_type}')
@dataclass
class Record:
problem: Any # Problem object
response: str
response_ms: int
score: float # Score between 0.0 and 1.0
def __post_init__(self) -> None:
if not hasattr(self.problem, "to_dict"):
raise TypeError("problem must have to_dict method")
if not isinstance(self.response, str):
raise TypeError("response must be str")
if not isinstance(self.response_ms, int) or self.response_ms < 0:
raise ValueError("response_ms must be a non-negative integer")
if not isinstance(self.score, (int, float)):
raise TypeError("score must be int or float")
if not (0.0 <= self.score <= 1.0):
raise ValueError("score must be between 0.0 and 1.0")
def to_dict(self):
return {
'problem': self.problem.to_dict(),
'response': self.response,
'response_ms': self.response_ms,
'score': self.score,
'correct': self.score >= 1.0 # Keep for backward compatibility
}
@property
def correct(self):
"""Backward compatibility property - returns True if score is perfect"""
return self.score >= 1.0