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
14 changes: 9 additions & 5 deletions evaluation_function/evaluation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any
from lf_toolkit.evaluation import Result as LFResult, Params

from .schemas import FSA
from .schemas import FSA, FSAFrontend
from .schemas.result import Result
from .correction import analyze_fsa_correction

Expand All @@ -16,17 +16,21 @@ def evaluation_function(
Evaluate a student's FSA response against the expected answer.

Args:
response: Student's FSA (dict with states, alphabet, transitions, etc.)
answer: Expected FSA
response: Student's FSA (dict with states, alphabet, transitions, etc.), since frontend constriants, this is FSAFrontend
answer: Expected FSA still, FSAFrontend for the same reason
params: Extra parameters (e.g., require_minimal)

Returns:
LFResult with is_correct and feedback
"""
try:
# Parse FSAs from input
student_fsa = FSA.model_validate(response)
expected_fsa = FSA.model_validate(answer)
student_fsa_ = FSAFrontend.model_validate(response)
expected_fsa_ = FSAFrontend.model_validate(answer)

student_fsa = student_fsa_.from_flattened()
expected_fsa = expected_fsa_.from_flattened()


# Get require_minimal from params if present
require_minimal = params.get("require_minimal", False) if hasattr(params, "get") else False
Expand Down
2 changes: 2 additions & 0 deletions evaluation_function/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .fsa import FSA, Transition
from .params import Params
from .result import Result, ValidationError, ElementHighlight, FSAFeedback, ErrorCode, StructuralInfo
from .fsaFrontend import FSAFrontend

__all__ = [
# FSA representation
Expand All @@ -22,4 +23,5 @@
"ErrorCode",
"StructuralInfo",
"FSAFeedback",
"FSAFrontend"
]
99 changes: 99 additions & 0 deletions evaluation_function/schemas/fsaFrontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from typing import List
from pydantic import BaseModel, Field
from .fsa import FSA, Transition

# frontend zod restricts typing, this is the current workaround

class FSAFrontend(BaseModel):
"""
Finite State Automaton representation.

Represents a 5-tuple (Q, Σ, δ, q0, F) where:
- Q = states
- Σ = alphabet
- δ = transitions (transition function)
- q0 = initial_state
- F = accept_states

Example:
{
"states": ["q0", "q1", "q2"],
"alphabet": ["a", "b"],
"transitions": [
{"from_state": "q0", "to_state": "q1", "symbol": "a"},
{"from_state": "q1", "to_state": "q2", "symbol": "b"}
],
"initial_state": "q0",
"accept_states": ["q2"]
}
"""
states: List[str] = Field(
...,
min_length=1,
description="Q: Set of all state identifiers"
)

alphabet: List[str] = Field(
...,
min_length=1,
description="Σ: Input alphabet symbols (excluding epsilon)"
)

transitions: List[str] = Field(
default_factory=list,
description="δ: Transition function as a list of (from_state, symbol, to_state) tuples"
)

initial_state: str = Field(
...,
description="q0: The starting state"
)

accept_states: List[str] = Field(
default_factory=list,
description="F: Set of accepting/final states"
)

class Config:
schema_extra = {
"example": {
"states": ["q0", "q1", "q2"],
"alphabet": ["a", "b"],
"transitions": [
"q0|a|q1|",
"q1|b|q2",
],
"initial_state": "q0",
"accept_states": ["q2"]
}
}

@classmethod
def from_flattened(cls, data: dict) -> FSA:
"""
Convert frontend FSA payload (with transitions as "from|symbol|to")
into the FSABackend model with proper Transition objects.
"""
states = data.get("states", [])
alphabet = data.get("alphabet", [])
initial_state = data.get("initial_state", "q0")
accept_states = data.get("accept_states", [])

flat_transitions = data.get("transitions", [])
transitions: List[Transition] = []
for t in flat_transitions:
try:
from_state, symbol, to_state = t.split("|")
transitions.append(
Transition(from_state=from_state, symbol=symbol, to_state=to_state)
)
except ValueError:
raise ValueError(f"Invalid transition format: '{t}'")

return FSA(
states=states,
alphabet=alphabet,
transitions=transitions,
initial_state=initial_state,
accept_states=accept_states,
)