Spaces:
Paused
Paused
| """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", | |
| ] | |