"""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", ]