PDF de fornecedor nunca foi feito para ser parseado. É uma página — colunas, caixas de texto flutuantes, células mescladas, notas de rodapé. Escrever parser determinístico para PDF arbitrário é um buraco de dez anos.
O formato PDF em si é o inimigo. Um PDF não guarda 'uma tabela com 5 colunas e 12 linhas.' Ele guarda instruções para desenhar glifos em coordenadas específicas numa página. Duas células que parecem adjacentes na renderização podem estar distantes páginas no stream de texto interno. Um preço que aparece na terceira coluna visualmente pode ser o primeiro pedaço de texto no arquivo. PDF foi projetado para impressoras, não parsers — e o pipeline de exportação de cada fornecedor produz um sabor diferente de caos.
Texto primeiro, estrutura depois
A gente usa pypdf para puxar texto bruto de cada página, concatena com double newline, e deixa o LLM raciocinar sobre estrutura. Grade, grupo, unidade, coluna de quantidade — o modelo pega pelo contexto.
Essa é a decisão arquitetural chave: a gente não tenta parsear o layout interno do PDF. Extraímos texto, achatamos, e entregamos para um modelo que entende estrutura de linguagem natural. O modelo vê algo como 'SKU: AB-001 Peça A 12 un $14.50' e sabe — por contexto e treinamento — que isso é um item de linha com código, descrição, quantidade, unidade e preço. Sem template por fornecedor, sem configuração de mapeamento de coluna, sem regex frágil que quebra quando o fornecedor muda a configuração de exportação.
Rede de segurança do JSON schema
O modelo nunca devolve texto livre. O structured output da IA impõe um schema estrito: grupo, item, código, quantidade, unidade, preço. Se o PDF é só imagem (scan sem camada de texto), a gente retorna `unreadable_file` antes de gastar tokens.
O schema é o contrato. O modelo não pode devolver 'acho que isso é um item de linha mas não tenho certeza'. Ele tem que devolver um array de objetos QuoteSection, cada um contendo itens QuoteLine com campos tipados. Se o texto estiver truncado ou ambíguo, o modelo devolve sua melhor interpretação — mas sempre no schema. O código do importador nunca faz branch no conteúdo da resposta; só valida o formato. Essa separação de responsabilidades significa que o pipeline de importação é testável com JSON mockado, e o prompt da IA pode ser iterado independentemente.
Modos de falha A maioria das falhas é a montante — PDF que é foto de papel. A solução é out-of-band: pedimos o re-export do portal do fornecedor. Nenhuma IA conserta documento sem texto.
PDFs só de imagem são a falha mais comum, mas não a única. PDFs com senha, arquivos corrompidos que abrem no visualizador mas falham no parse, e arquivos com fontes embutidas que mapeiam glifos para code points Unicode inesperados — todos aparecem em produção. A gente trata cada um com código de erro específico — unreadable_file, encrypted_file, corrupted_file — para o frontend renderizar uma mensagem acionável. 'Esse arquivo está protegido por senha. Por favor remova a senha e tente de novo' é mais útil que 'Falha no upload'.
