Spaces:
Runtime error
Runtime error
File size: 4,280 Bytes
8d3145b bc45e54 8d3145b bc45e54 8d3145b bc45e54 8d3145b bc45e54 0641d27 8d3145b bc45e54 8d3145b bc45e54 8d3145b bc45e54 0641d27 8d3145b 0641d27 bc45e54 0641d27 8d3145b 0641d27 8d3145b bc45e54 0641d27 8d3145b bc45e54 8d3145b bc45e54 8d3145b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """File processing orchestrator.
Delegates extraction to a pluggable parser chosen by name or auto-picked.
Saves the parsed structured data to the database, along with metadata about
which parser produced it.
"""
from __future__ import annotations
import json
from fastapi import UploadFile
from .database import delete_parsed_data, save_parsed_data
from .parsers import AUTO, get_parser, pick_auto
from .parsers.base import ParserFile, ParserResult
async def process_uploaded_files(
files: list[UploadFile],
data_type: str,
session_id: int,
description: str = "",
model: str = "gpt-5.4",
parser: str = AUTO,
) -> dict:
"""Parse uploaded files and persist the structured result.
`data_type='answers'` triggers a dual extraction: the same files are parsed
twice (once for student_answers, once for teacher_answers) and both rows are
stored. The response merges both shapes under their respective keys.
"""
parser_files: list[ParserFile] = []
for file in files:
content = await file.read()
if not content:
raise ValueError(f"File '{file.filename}' is empty.")
parser_files.append(
ParserFile(filename=file.filename or "unknown", content=content)
)
if not parser_files:
raise ValueError("No files uploaded")
if data_type == "answers":
return await _process_combined_answers(
parser_files, session_id, description, model, parser
)
result = await _run_with_fallback(parser_files, data_type, description, model, parser)
await _persist(session_id, data_type, parser_files, result)
return _response_payload(result)
async def _process_combined_answers(
parser_files: list[ParserFile],
session_id: int,
description: str,
model: str,
parser: str,
) -> dict:
student_result = await _run_with_fallback(
parser_files, "student_answers", description, model, parser
)
teacher_result = await _run_with_fallback(
parser_files, "teacher_answers", description, model, parser
)
await _persist(session_id, "student_answers", parser_files, student_result)
await _persist(session_id, "teacher_answers", parser_files, teacher_result)
return {
"student_answers": _response_payload(student_result),
"teacher_answers": _response_payload(teacher_result),
}
async def _persist(
session_id: int,
data_type: str,
parser_files: list[ParserFile],
result: ParserResult,
) -> None:
await delete_parsed_data(session_id, data_type)
raw_text = json.dumps(
{
"source_files": [f.filename for f in parser_files],
"parser": result.parser_name,
"notes": list(result.notes),
},
ensure_ascii=False,
)
await save_parsed_data(
session_id,
data_type,
parser_files[0].filename,
raw_text,
result.data,
)
def _response_payload(result: ParserResult) -> dict:
return {
**result.data,
"_meta": {
"parser": result.parser_name,
"notes": list(result.notes),
},
}
async def _run_with_fallback(
parser_files: list[ParserFile],
data_type: str,
description: str,
model: str,
parser_name: str,
) -> ParserResult:
"""Run the selected parser; in auto mode, fall back to LLM on failure."""
if parser_name == AUTO:
first_file = parser_files[0]
chosen = pick_auto(first_file.content, first_file.filename, data_type)
try:
return await chosen.parse(parser_files, data_type, description, model)
except Exception as primary_err:
if chosen.name == "llm_vision":
raise
# Fallback to LLM vision on any native-parser failure
llm = get_parser("llm_vision")
result = await llm.parse(parser_files, data_type, description, model)
return ParserResult(
data=result.data,
parser_name=result.parser_name,
notes=(f"auto-fallback from {chosen.name}: {primary_err}", *result.notes),
)
chosen = get_parser(parser_name)
return await chosen.parse(parser_files, data_type, description, model)
|