Yu Chen
simplify the answer parsing tool selection
73c0cf0
Raw
History Blame Contribute Delete
2.42 kB
"""Pluggable parser registry for answer-sheet extraction.
Auto mode: picks the best parser based on file type and data_type.
Manual mode: caller specifies parser name from `list_parsers()`.
Adding a new parser:
1. Create a module that exposes a subclass of `AnswerSheetParser`.
2. Import and register it below in `_REGISTRY`.
"""
from __future__ import annotations
from .base import AnswerSheetParser, ParserResult, get_extension
from .csv_strict import StrictCsvParser
from .llm_vision import LLMVisionParser
from .native_pdf_tables import NativePdfParser
AUTO = "auto"
_REGISTRY: dict[str, AnswerSheetParser] = {
NativePdfParser.name: NativePdfParser(),
StrictCsvParser.name: StrictCsvParser(),
LLMVisionParser.name: LLMVisionParser(),
}
def list_parsers() -> list[dict[str, str]]:
"""Return parser metadata for UI dropdowns."""
out: list[dict[str, str]] = [
{
"id": AUTO,
"name": "Auto (recommended)",
"description": "System picks the best parser for each file.",
}
]
for p in _REGISTRY.values():
out.append({"id": p.name, "name": p.display_name, "description": p.description})
return out
def get_parser(name: str) -> AnswerSheetParser:
if name not in _REGISTRY:
raise ValueError(f"Unknown parser: {name}")
return _REGISTRY[name]
def pick_auto(file_bytes: bytes, filename: str, data_type: str) -> AnswerSheetParser:
"""Auto-mode selector.
- Questions always use LLM vision (text rarely fits a clean grid).
- .csv answer-side uploads → StrictCsvParser.
- Native PDF tables for student/teacher answers when there's a text layer.
- Otherwise fall back to LLM vision.
"""
if data_type == "questions":
return _REGISTRY[LLMVisionParser.name]
# The "answers" combined zone runs the parser twice (once per subtype);
# treat it the same as student_answers for routing purposes.
routing_data_type = "student_answers" if data_type == "answers" else data_type
if get_extension(filename) == ".csv":
return _REGISTRY[StrictCsvParser.name]
native = _REGISTRY[NativePdfParser.name]
if native.can_handle(file_bytes, filename, routing_data_type):
return native
return _REGISTRY[LLMVisionParser.name]
__all__ = [
"AUTO",
"AnswerSheetParser",
"ParserResult",
"get_parser",
"list_parsers",
"pick_auto",
]