Skip to content

Commit ea45dca

Browse files
committed
feat: add comprehensive preview function tests for FSA validation; enhance error messaging and feedback for structural issues
1 parent 77a0f7a commit ea45dca

4 files changed

Lines changed: 664 additions & 101 deletions

File tree

evaluation_function/correction/correction.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ def _check_minimality(fsa: FSA) -> Tuple[bool, Optional[ValidationError]]:
3333
try:
3434
minimized = hopcroft_minimization(fsa)
3535
if len(minimized.states) < len(fsa.states):
36+
diff = len(fsa.states) - len(minimized.states)
3637
return False, ValidationError(
37-
message=f"FSA is not minimal: has {len(fsa.states)} states but can be reduced to {len(minimized.states)}",
38+
message=f"Your FSA works correctly, but it's not minimal! You have {len(fsa.states)} states, but only {len(minimized.states)} are needed. You could remove {diff} state(s).",
3839
code=ErrorCode.NOT_MINIMAL,
3940
severity="error",
40-
suggestion="Minimize your FSA by merging equivalent states"
41+
suggestion="Look for states that behave identically (same transitions and acceptance) - these can be merged into one"
4142
)
4243
return True, None
4344
except Exception:
@@ -69,9 +70,11 @@ def _build_feedback(
6970
hints = [e.suggestion for e in all_errors if e.suggestion]
7071
if structural_info:
7172
if structural_info.unreachable_states:
72-
hints.append("Consider removing unreachable states")
73+
unreachable = ", ".join(structural_info.unreachable_states)
74+
hints.append(f"Tip: States {{{unreachable}}} can't be reached from your start state - you might want to remove them or add transitions to them")
7375
if structural_info.dead_states:
74-
hints.append("Dead states can never lead to acceptance")
76+
dead = ", ".join(structural_info.dead_states)
77+
hints.append(f"Tip: States {{{dead}}} can never lead to acceptance - this might be intentional (trap states) or a bug")
7578

7679
# Build language comparison
7780
language = LanguageComparison(are_equivalent=len(equivalence_errors) == 0)
@@ -92,17 +95,20 @@ def _summarize_errors(errors: List[ValidationError]) -> str:
9295
for error in errors:
9396
msg = error.message.lower()
9497
if "alphabet" in msg:
95-
error_types.add("alphabet mismatch")
96-
elif "state" in msg and "count" in msg:
97-
error_types.add("state count mismatch")
98-
elif "accepting" in msg or "incorrectly marked" in msg:
99-
error_types.add("acceptance error")
100-
elif "transition" in msg:
101-
error_types.add("transition error")
98+
error_types.add("alphabet issue")
99+
elif "states" in msg and ("many" in msg or "few" in msg or "needed" in msg):
100+
error_types.add("incorrect number of states")
101+
elif "accepting" in msg or "accept" in msg:
102+
error_types.add("accepting states issue")
103+
elif "transition" in msg or "reading" in msg:
104+
error_types.add("transition issue")
102105

103-
if error_types:
104-
return f"Languages differ: {', '.join(error_types)}"
105-
return f"Languages differ: {len(errors)} issue(s)"
106+
if len(error_types) == 1:
107+
issue = list(error_types)[0]
108+
return f"Almost there! Your FSA has an {issue}. Check the details below."
109+
elif error_types:
110+
return f"Your FSA doesn't quite match the expected language. Issues found: {', '.join(error_types)}"
111+
return f"Your FSA doesn't accept the correct language. Found {len(errors)} issue(s) to fix."
106112

107113

108114
# =============================================================================
@@ -134,7 +140,11 @@ def analyze_fsa_correction(
134140
# Step 1: Validate student FSA structure
135141
student_errors = is_valid_fsa(student_fsa)
136142
if student_errors:
137-
summary = "FSA has structural errors"
143+
num_errors = len(student_errors)
144+
if num_errors == 1:
145+
summary = "Your FSA has a structural problem that needs to be fixed first. See the details below."
146+
else:
147+
summary = f"Your FSA has {num_errors} structural problems that need to be fixed first. See the details below."
138148
return Result(
139149
is_correct=False,
140150
feedback=summary,
@@ -146,7 +156,7 @@ def analyze_fsa_correction(
146156
if expected_errors:
147157
return Result(
148158
is_correct=False,
149-
feedback="Internal error: expected FSA is invalid"
159+
feedback="Oops! There's an issue with the expected answer. Please contact your instructor."
150160
)
151161

152162
# Step 3: Check minimality if required
@@ -162,15 +172,18 @@ def analyze_fsa_correction(
162172
equivalence_errors = fsas_accept_same_language(student_fsa, expected_fsa)
163173

164174
if not equivalence_errors and not validation_errors:
175+
# Success message with some stats
176+
state_count = len(student_fsa.states)
177+
feedback = f"Correct! Your FSA with {state_count} state(s) accepts exactly the right language. Well done!"
165178
return Result(
166179
is_correct=True,
167-
feedback="Correct! FSA accepts the expected language.",
168-
fsa_feedback=_build_feedback("FSA is correct", [], [], structural_info)
180+
feedback=feedback,
181+
fsa_feedback=_build_feedback("Your FSA is correct!", [], [], structural_info)
169182
)
170183

171184
# Build result with errors
172185
is_correct = len(equivalence_errors) == 0 and len(validation_errors) == 0
173-
summary = _summarize_errors(equivalence_errors) if equivalence_errors else "FSA has issues"
186+
summary = _summarize_errors(equivalence_errors) if equivalence_errors else "Your FSA has some issues to address."
174187

175188
return Result(
176189
is_correct=is_correct,

0 commit comments

Comments
 (0)