Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions evaluation_function/correction/correction.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ def _check_minimality(fsa: FSA) -> Tuple[bool, Optional[ValidationError]]:
try:
minimized = hopcroft_minimization(fsa)
if len(minimized.states) < len(fsa.states):
diff = len(fsa.states) - len(minimized.states)
return False, ValidationError(
message=f"FSA is not minimal: has {len(fsa.states)} states but can be reduced to {len(minimized.states)}",
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).",
code=ErrorCode.NOT_MINIMAL,
severity="error",
suggestion="Minimize your FSA by merging equivalent states"
suggestion="Look for states that behave identically (same transitions and acceptance) - these can be merged into one"
)
return True, None
except Exception:
Expand Down Expand Up @@ -69,9 +70,11 @@ def _build_feedback(
hints = [e.suggestion for e in all_errors if e.suggestion]
if structural_info:
if structural_info.unreachable_states:
hints.append("Consider removing unreachable states")
unreachable = ", ".join(structural_info.unreachable_states)
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")
if structural_info.dead_states:
hints.append("Dead states can never lead to acceptance")
dead = ", ".join(structural_info.dead_states)
hints.append(f"Tip: States {{{dead}}} can never lead to acceptance - this might be intentional (trap states) or a bug")

# Build language comparison
language = LanguageComparison(are_equivalent=len(equivalence_errors) == 0)
Expand All @@ -92,17 +95,20 @@ def _summarize_errors(errors: List[ValidationError]) -> str:
for error in errors:
msg = error.message.lower()
if "alphabet" in msg:
error_types.add("alphabet mismatch")
elif "state" in msg and "count" in msg:
error_types.add("state count mismatch")
elif "accepting" in msg or "incorrectly marked" in msg:
error_types.add("acceptance error")
elif "transition" in msg:
error_types.add("transition error")
error_types.add("alphabet issue")
elif "states" in msg and ("many" in msg or "few" in msg or "needed" in msg):
error_types.add("incorrect number of states")
elif "accepting" in msg or "accept" in msg:
error_types.add("accepting states issue")
elif "transition" in msg or "reading" in msg:
error_types.add("transition issue")

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


# =============================================================================
Expand Down Expand Up @@ -134,7 +140,11 @@ def analyze_fsa_correction(
# Step 1: Validate student FSA structure
student_errors = is_valid_fsa(student_fsa)
if student_errors:
summary = "FSA has structural errors"
num_errors = len(student_errors)
if num_errors == 1:
summary = "Your FSA has a structural problem that needs to be fixed first. See the details below."
else:
summary = f"Your FSA has {num_errors} structural problems that need to be fixed first. See the details below."
return Result(
is_correct=False,
feedback=summary,
Expand All @@ -146,7 +156,7 @@ def analyze_fsa_correction(
if expected_errors:
return Result(
is_correct=False,
feedback="Internal error: expected FSA is invalid"
feedback="Oops! There's an issue with the expected answer. Please contact your instructor."
)

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

if not equivalence_errors and not validation_errors:
# Success message with some stats
state_count = len(student_fsa.states)
feedback = f"Correct! Your FSA with {state_count} state(s) accepts exactly the right language. Well done!"
return Result(
is_correct=True,
feedback="Correct! FSA accepts the expected language.",
fsa_feedback=_build_feedback("FSA is correct", [], [], structural_info)
feedback=feedback,
fsa_feedback=_build_feedback("Your FSA is correct!", [], [], structural_info)
)

# Build result with errors
is_correct = len(equivalence_errors) == 0 and len(validation_errors) == 0
summary = _summarize_errors(equivalence_errors) if equivalence_errors else "FSA has issues"
summary = _summarize_errors(equivalence_errors) if equivalence_errors else "Your FSA has some issues to address."

return Result(
is_correct=is_correct,
Expand Down
Loading