"""Orquestra a extração de um arquivo enviado: OpenAI Vision -> fallback local. Reutiliza `lernotafiscal.extraction` (normalização de PDF/imagem, render de páginas via PyMuPDF, OCR Tesseract e a detecção heurística) como caminho de contingência quando a IA está indisponível ou falha. """ from __future__ import annotations from pathlib import Path from lernotafiscal.extraction import ( DetectedDocumentCandidate, apply_ocr_fallback, detect_documents, normalize_file, ) from .ai_extraction import RawExtraction, extract_with_ai from .config import get_settings def _candidate_to_raw(candidate: DetectedDocumentCandidate) -> RawExtraction: fields = candidate.field_confidence or {} uncertain = [name for name, level in fields.items() if level == "low"] for name, val in ( ("fornecedor", candidate.supplier_name), ("data_compra", candidate.purchase_date), ("valor_pago", candidate.total_paid), ): if val is None and name not in uncertain: uncertain.append(name) legible = candidate.confidence != "low" and len(uncertain) < 2 return RawExtraction( supplier_name=candidate.supplier_name, purchase_date_raw=candidate.purchase_date, total_paid=candidate.total_paid, legible=legible, uncertain_fields=uncertain, extractor="local", raw_text=(candidate.raw_text or "")[:4000], ) def extract_file(stored_path: Path, original_name: str) -> list[RawExtraction]: """Retorna os documentos extraídos de um arquivo (>=0). Nunca levanta exceção de extração para o chamador — em último caso devolve lista vazia.""" settings = get_settings() pages = normalize_file(stored_path, original_name, max_render_pages=settings.openai_max_pages) # Caminho preferencial: enviar as imagens (páginas renderizadas / imagem) à IA. # Só aceitamos o resultado da IA quando ela retorna ao menos um documento. # Uma resposta vazia (nada encontrado OU shape inesperado) cai no fallback # local — evita "zero documentos" silencioso por uma resposta malformada. image_paths = [p.image_path for p in pages if p.image_path is not None] if image_paths: ai_result = extract_with_ai(image_paths) if ai_result: return ai_result # Fallback: só agora rodamos o OCR (Tesseract) sobre as páginas sem texto # utilizável — evita o custo do OCR quando a IA já resolveu a extração. pages = apply_ocr_fallback(pages) candidates = detect_documents(pages, original_name) return [_candidate_to_raw(c) for c in candidates]