Skip to main content

Receipt Extractor / Extrator de Comprovantes

v0.2.1 · Python ≥3.12 · Agno + DeepSeek (+ Groq fallback)


Installation

pip install bank-receipt-extractor
# or with uv
uv pip install bank-receipt-extractor

Configuration

Set these environment variables:

Variable Required Description Default
DEEPSEEK_API_KEY ✅ Yes DeepSeek API key (get one at platform.deepseek.com) —
MODEL_FALLBACK_LIST ❌ No Ordered provider:model fallback list, comma-separated. Supported providers: deepseek, groq deepseek:deepseek-flash
GROQ_API_KEY ❌ No Groq API key — needed only if Groq is in the fallback list (get one at console.groq.com) —
export DEEPSEEK_API_KEY=sk_your_key_here
export MODEL_FALLBACK_LIST=deepseek:deepseek-flash,groq:qwen/qwen3.8-27b

Or with a .env file (your app loads it via python-dotenv):

DEEPSEEK_API_KEY=sk_your_key_here
MODEL_FALLBACK_LIST=deepseek:deepseek-flash,groq:qwen/qwen3.8-27b

Model Fallback

The extractor tries the models in MODEL_FALLBACK_LIST in order and caches the first one that succeeds. If it later fails, it falls through to the next. Call extract_receipt(arquivo, force_retry=True) to skip the cache and restart from the list.

Model Provider Vision Cost (input/1M tok)
deepseek-flash DeepSeek ✅ $0.15
qwen/qwen3.8-27b Groq ✅ $0.80

Detecting fallback on the client: when a provider fails but a fallback succeeds, the output dict includes a fallback field so the operator can fix the relevant API key:

{
  "valor": 12.5, "tipo": "PIX", "...": "...",
  "fallback": {
    "usado": true,
    "motivo": "authentication",
    "provedores_falhos": ["deepseek:deepseek-flash"]
  }
}

motivo is a safe category (authentication, timeout, parse, unknown) — it never contains API keys, request IDs or raw provider messages. If the same model recovers on a retry, the output has no fallback field — only a real provider switch is reported. The HTTP status stays implicit (200) on success; error responses still use status/motivo with erro_para_status().

System Dependency: poppler-utils

To process PDF files, pdf2image requires the pdftoppm command from poppler-utils:

# Ubuntu/Debian
sudo apt install poppler-utils

# macOS
brew install poppler

# Fedora
sudo dnf install poppler-utils

# Docker
RUN apt-get update && apt-get install -y poppler-utils

If you process only images (PNG/JPG), poppler-utils is not needed.


English

Overview

Python module for automated extraction of Brazilian bank receipt data (PIX, TED, DOC). Accepts PDF or image, processes with vision AI via DeepSeek (with Groq fallback), and returns validated JSON with amount, date, transaction type, banks and recipient.

Stack

Layer Technology
Runtime Python ≥3.12
Agent Framework Agno
Vision Model DeepSeek (deepseek-flash), Groq fallback
PDF → Image pdf2image + poppler-utils
Image Processing Pillow
Schemas Pydantic v2
Package Manager uv

Data Flow

Input (str | Path | BytesIO | UploadedFile)
       │
       ▼
  _read_file() ───→ raw bytes
       │
       ▼
  _identify_type()
       │
       ├── .pdf  → PdfProcessor  → image (300 DPI, 1st page)
       └── .png/jpg → ImageProcessor → optimized JPEG (max 2048px)
       │
       ▼
  extract_from_image() ───→ DeepSeek Vision (deepseek-flash)
       │
       ▼
  BankReceipt (Pydantic validated)
       │
       ▼
  model_dump() ───→ dict (JSON-serializable)

Error → {"erro": "descriptive message"}

Schema: BankReceipt

Field Type Required Validation
valor Decimal ✔ > 0
data_transacao date ✔ ISO YYYY-MM-DD
numero_controle str | null ✘ max 64 chars
banco_origem str | null ✘ max 100 chars
tipo Literal ✔ PIX | TED | DOC | DESCONHECIDO
banco_destino str | null ✘ max 100 chars
favorecido str | null ✘ max 200 chars

Sample output

{
  "valor": 1500.0,
  "data_transacao": "2026-07-20",
  "numero_controle": "E4B3C2A1F5D8",
  "banco_origem": "Itaú Unibanco S.A.",
  "tipo": "PIX",
  "banco_destino": "Nubank",
  "favorecido": "Maria Oliveira Santos"
}

Quickstart

Prerequisites

# System
sudo apt install poppler-utils      # Ubuntu/Debian
brew install poppler                 # macOS

# Python 3.12
python --version   # ≥ 3.12

Installation

git clone <repo>
cd receipt-extractor
uv sync

# Set up DeepSeek API key
cp .env.example .env
# Edit .env with your key: https://platform.deepseek.com/api_keys

CLI Usage

python usage_example.py ~/Downloads/receipt.pdf

Code Usage

from bank_receipt_extractor.integration import extract_receipt

# File path
data = extract_receipt("receipt.pdf")

# Path object
from pathlib import Path
data = extract_receipt(Path("receipt.jpg"))

# BytesIO (in-memory upload)
from io import BytesIO
with open("receipt.png", "rb") as f:
    data = extract_receipt(BytesIO(f.read()))

# Django UploadedFile (directly)
data = extract_receipt(request.FILES["receipt"])

API: extract_receipt()

def extract_receipt(file: str | Path | BytesIO | UploadedFile) -> dict
Scenario Return
Success {"valor": float, "data_transacao": str, "tipo": str, ...}
Provider(s) down {"erro": "Extraction service unavailable. Try again later.", "status": 502, "motivo": "provider_error"}
Unsupported format {"erro": "Unsupported format. Accepted: PDF, PNG, JPG", "status": 415, "motivo": "unsupported_format"}
File too large (>50MB) {"erro": "File exceeds the 50MB limit", "status": 413, "motivo": "file_too_large"}
Invalid/unreadable file {"erro": "Could not read the file.", "status": 400, "motivo": "invalid_file"}
Model output failed validation {"erro": "Could not extract receipt data.", "status": 422, "motivo": "parse_error"}

The function never raises — always returns a dict. Error responses are neutral: internal details (API keys, request IDs, provider messages) are logged server-side and never exposed to the client. The error dict includes status (HTTP code) and motivo (classifier) for the view.

Django Integration

Backend View

from django.http import JsonResponse
from bank_receipt_extractor.integration import extract_receipt, erro_para_status

@require_POST
def extract_receipt_data(request):
    file = request.FILES.get("receipt")
    if not file:
        return JsonResponse({"erro": "No file uploaded"}, status=400)
    data = extract_receipt(file)
    status = 200 if "erro" not in data else erro_para_status(data)
    return JsonResponse(data, status=status)

The erro_para_status() helper maps the error dict to the right HTTP status:

motivo HTTP status
provider_error 502 Bad Gateway
unsupported_format 415 Unsupported Media Type
file_too_large 413 Payload Too Large
invalid_file 400 Bad Request
parse_error 422 Unprocessable Entity

Frontend (onchange + AJAX)

document.getElementById("id_receipt").addEventListener("change", async (e) => {
    const file = e.target.files[0];
    if (!file) return;

    const formData = new FormData();
    formData.append("receipt", file);

    try {
        const response = await fetch("/api/extract-receipt/", {
            method: "POST",
            body: formData,
        });
        const data = await response.json();

        if (data.erro) {
            alert("Error: " + data.erro);
        } else {
            document.getElementById("id_valor").value = data.valor;
            document.getElementById("id_tipo").value = data.tipo;
            // Fill hidden fields...
        }
    } catch (err) {
        alert("Error processing receipt");
    }
});

Note: Vision API calls take 1–15 seconds. Add loading states on the frontend. For higher scale, consider Celery + Redis for async processing.

Project Structure

├── bank_receipt_extractor/       # Main package
│   ├── __init__.py               # Exports: BankReceipt, extract_receipt, erro_para_status
│   ├── errors.py                 # Typed extraction errors + HTTP status mapping
│   ├── schemas.py                # Pydantic BankReceipt model
│   ├── utils.py                  # Parsers: currency, date, MIME detection
│   ├── agent.py                  # Agno Agent + DeepSeek Vision (fallback, 30s timeout)
│   ├── processors/
│   │   ├── __init__.py
│   │   ├── image_processor.py    # Resize, EXIF correction, JPEG
│   │   └── pdf_processor.py      # PDF → image (pdf2image)
│   └── integration.py            # Bridge: extract_receipt() (50MB limit, logging)
│
├── tests/                        # Test suite (123 tests, 100% coverage)
│   ├── test_schemas.py
│   ├── test_utils.py
│   ├── test_agent.py
│   ├── test_image_processor.py
│   ├── test_pdf_processor.py
│   ├── test_integration.py
│   ├── test_integration_unit.py
│   └── conftest.py               # Shared fixtures & mocks
│
├── usage_example.py              # CLI demo
├── pyproject.toml                # Dependencies & metadata
├── .env.example                  # Environment template
└── specs/                        # Feature docs (Specify)

System Requirements

Dependency Required Purpose
Python ≥ 3.12 ✔ Runtime
poppler-utils ✔ (PDF only) pdf2image — converts PDF pages to images via pdftoppm
Internet ✔ Vision APIs (DeepSeek primary)
DEEPSEEK_API_KEY ✔ DeepSeek authentication

Português

Instalação

pip install bank-receipt-extractor
# ou com uv
uv pip install bank-receipt-extractor

Configuração

Defina estas variáveis de ambiente:

Variável Obrigatória Descrição Padrão
DEEPSEEK_API_KEY ✅ Sim Chave da API DeepSeek (obtenha em platform.deepseek.com) —
MODEL_FALLBACK_LIST ❌ Não Lista de fallback provedor:modelo em ordem, separada por vírgula. Provedores suportados: deepseek, groq deepseek:deepseek-flash
GROQ_API_KEY ❌ Não Chave da API Groq — necessária apenas se Groq estiver na lista de fallback (obtenha em console.groq.com) —
export DEEPSEEK_API_KEY=sk_sua_chave_aqui
export MODEL_FALLBACK_LIST=deepseek:deepseek-flash,groq:qwen/qwen3.8-27b

Ou com arquivo .env (seu app carrega via python-dotenv):

DEEPSEEK_API_KEY=sk_sua_chave_aqui
MODEL_FALLBACK_LIST=deepseek:deepseek-flash,groq:qwen/qwen3.8-27b

Modelos Recomendados

Modelo Provedor Visão Custo (input/1M tokens)
deepseek-flash DeepSeek ✅ $0,15
qwen/qwen3.8-27b Groq ✅ $0,80

O extrator tenta os modelos de MODEL_FALLBACK_LIST em ordem e cacheia o primeiro que funcionar. Se ele falhar depois, tenta o próximo da lista. Use extract_receipt(arquivo, force_retry=True) para ignorar o cache e recomeçar da lista.

Detectando o fallback no cliente: quando um provedor falha mas o fallback extrai com sucesso, o dict de saída inclui o campo fallback para o operador corrigir a chave relevante:

{
  "valor": 12.5, "tipo": "PIX", "...": "...",
  "fallback": {
    "usado": true,
    "motivo": "authentication",
    "provedores_falhos": ["deepseek:deepseek-flash"]
  }
}

motivo é sempre uma categoria segura (authentication, timeout, parse, unknown) — nunca contém chave de API, request_id ou mensagem crua do provedor. Se o mesmo modelo se recuperar em um retry, a saída não inclui o campo fallback — apenas uma troca real de provedor é reportada. O status HTTP continua implícito (200) no sucesso; respostas de erro seguem usando status/motivo com erro_para_status().

Dependência de Sistema: poppler-utils

Para processar arquivos PDF, o pdf2image precisa do comando pdftoppm do pacote poppler-utils:

# Ubuntu/Debian
sudo apt install poppler-utils

# macOS
brew install poppler

# Fedora
sudo dnf install poppler-utils

# Docker
RUN apt-get update && apt-get install -y poppler-utils

Se você processa apenas imagens (PNG/JPG), o poppler-utils não é necessário.


Visão Geral

Módulo Python para extração automatizada de dados de comprovantes bancários brasileiros (PIX, TED, DOC). Recebe PDF ou imagem, processa com IA de visão via DeepSeek (com fallback Groq) e retorna JSON validado com valor, data, tipo, bancos e favorecido.

Stack

Camada Tecnologia
Runtime Python ≥3.12
Framework de Agentes Agno
Modelo de Visão DeepSeek (deepseek-flash), fallback Groq
PDF → Imagem pdf2image + poppler-utils
Processamento de Imagem Pillow
Schemas Pydantic v2
Gerenciamento uv

Fluxo de Dados

Entrada (str | Path | BytesIO | UploadedFile)
       │
       ▼
  _read_file() ───→ bytes brutos
       │
       ▼
  _identify_type()
       │
       ├── .pdf  → PdfProcessor  → imagem (300 DPI, 1ª página)
       └── .png/jpg → ImageProcessor → JPEG otimizado (max 2048px)
       │
       ▼
  extract_from_image() ───→ DeepSeek Vision (deepseek-flash)
       │
       ▼
  BankReceipt (Pydantic validado)
       │
       ▼
  model_dump() ───→ dict (JSON-serializável)

Erro → {"erro": "mensagem descritiva"}

Schema: BankReceipt

Campo Tipo Obrigatório Validação
valor Decimal ✔ > 0
data_transacao date ✔ ISO YYYY-MM-DD
numero_controle str | null ✘ max 64 chars
banco_origem str | null ✘ max 100 chars
tipo Literal ✔ PIX | TED | DOC | DESCONHECIDO
banco_destino str | null ✘ max 100 chars
favorecido str | null ✘ max 200 chars

Exemplo de saída

{
  "valor": 1500.0,
  "data_transacao": "2026-07-20",
  "numero_controle": "E4B3C2A1F5D8",
  "banco_origem": "Itaú Unibanco S.A.",
  "tipo": "PIX",
  "banco_destino": "Nubank",
  "favorecido": "Maria Oliveira Santos"
}

Início Rápido

Pré-requisitos

# Sistema
sudo apt install poppler-utils      # Ubuntu/Debian
brew install poppler                 # macOS

# Python 3.12
python --version   # ≥ 3.12

Instalação

git clone <repo>
cd receipt-extractor
uv sync

# Configurar chave da API DeepSeek
cp .env.example .env
# Edite .env com sua chave: https://platform.deepseek.com/api_keys

Uso via CLI

python usage_example.py ~/Downloads/comprovante.pdf

Uso via código

from bank_receipt_extractor.integration import extract_receipt

# Caminho do arquivo
dados = extract_receipt("comprovante.pdf")

# Path object
from pathlib import Path
dados = extract_receipt(Path("comprovante.jpg"))

# BytesIO (upload em memória)
from io import BytesIO
with open("comprovante.png", "rb") as f:
    dados = extract_receipt(BytesIO(f.read()))

# UploadedFile do Django (diretamente)
dados = extract_receipt(request.FILES["comprovante"])

API: extract_receipt()

def extract_receipt(arquivo: str | Path | BytesIO | UploadedFile) -> dict
Situação Retorno
Sucesso {"valor": float, "data_transacao": str, "tipo": str, ...}
Provedor(es) indisponível(is) {"erro": "Serviço de extração indisponível. Tente novamente.", "status": 502, "motivo": "provider_error"}
Formato não suportado {"erro": "Formato não suportado. Aceito: PDF, PNG, JPG", "status": 415, "motivo": "unsupported_format"}
Arquivo muito grande (>50MB) {"erro": "Arquivo excede o limite de 50MB", "status": 413, "motivo": "file_too_large"}
Arquivo inválido/ilegível {"erro": "Não foi possível ler o arquivo.", "status": 400, "motivo": "invalid_file"}
Saída do modelo falhou validação {"erro": "Não foi possível extrair os dados do comprovante.", "status": 422, "motivo": "parse_error"}

A função nunca levanta exceção — sempre retorna um dict. As respostas de erro são neutras: detalhes internos (chaves de API, request IDs, mensagens do provedor) são logados no servidor e nunca expostos ao cliente. O dict de erro inclui status (código HTTP) e motivo (classificador) para a view.

Integração com Django

View (endpoint)

from django.http import JsonResponse
from bank_receipt_extractor.integration import extract_receipt, erro_para_status

@require_POST
def extrair_dados_comprovante(request):
    arquivo = request.FILES.get("comprovante")
    if not arquivo:
        return JsonResponse({"erro": "Nenhum arquivo enviado"}, status=400)
    dados = extract_receipt(arquivo)
    status = 200 if "erro" not in dados else erro_para_status(dados)
    return JsonResponse(dados, status=status)

O helper erro_para_status() mapeia o dict de erro para o código HTTP correto:

motivo Código HTTP
provider_error 502 Bad Gateway
unsupported_format 415 Unsupported Media Type
file_too_large 413 Payload Too Large
invalid_file 400 Bad Request
parse_error 422 Unprocessable Entity

Frontend (onchange + AJAX)

document.getElementById("id_comprovante").addEventListener("change", async (e) => {
    const file = e.target.files[0];
    if (!file) return;

    const formData = new FormData();
    formData.append("comprovante", file);

    try {
        const response = await fetch("/api/extrair-comprovante/", {
            method: "POST",
            body: formData,
        });
        const dados = await response.json();

        if (dados.erro) {
            alert("Erro: " + dados.erro);
        } else {
            document.getElementById("id_valor").value = dados.valor;
            document.getElementById("id_tipo").value = dados.tipo;
            // Preenche campos ocultos...
        }
    } catch (err) {
        alert("Erro ao processar comprovante");
    }
});

Atenção: Chamadas à API de visão levam de 1 a 15 segundos. Implemente loading states no frontend. Para escala maior, considere Celery + Redis para processamento assíncrono.

Estrutura do Projeto

├── bank_receipt_extractor/       # Pacote principal
│   ├── __init__.py               # Exporta: BankReceipt, extract_receipt, erro_para_status
│   ├── errors.py                 # Erros tipados de extração + status HTTP
│   ├── schemas.py                # Modelo Pydantic BankReceipt
│   ├── utils.py                  # Parsers: moeda, data, detecção MIME
│   ├── agent.py                  # Agno Agent + DeepSeek Vision (fallback, timeout 30s)
│   ├── processors/
│   │   ├── __init__.py
│   │   ├── image_processor.py    # Redimensionamento, EXIF, JPEG
│   │   └── pdf_processor.py      # PDF → imagem (pdf2image)
│   └── integration.py            # Bridge: extract_receipt() (limite 50MB, logging)
│
├── tests/                        # Suite de testes (123 testes, 100% cobertura)
│   ├── test_schemas.py
│   ├── test_utils.py
│   ├── test_agent.py
│   ├── test_image_processor.py
│   ├── test_pdf_processor.py
│   ├── test_integration.py
│   ├── test_integration_unit.py
│   └── conftest.py               # Fixtures e mocks compartilhados
│
├── usage_example.py              # CLI de demonstração
├── pyproject.toml                # Dependências e metadados
├── .env.example                  # Template de variáveis de ambiente
└── specs/                        # Documentação da feature

Pré-requisitos de Sistema

Dependência Obrigatório Para quê
Python ≥ 3.12 ✔ Runtime
poppler-utils ✔ (só PDF) pdf2image — converte páginas PDF em imagem via pdftoppm
Internet ✔ APIs de visão (DeepSeek principal)
DEEPSEEK_API_KEY ✔ Autenticação DeepSeek

Desenvolvimento

# Quality gates
ruff check bank_receipt_extractor tests   # Lint
mypy bank_receipt_extractor tests         # Type check

# Tests (123 testes, 100% cobertura)
pytest --cov=bank_receipt_extractor --cov-report=term-missing -v

Princípios / Principles

  • Modularity: Independent package, Django bridge via extract_receipt()
  • Security: API key via .env (gitignored), file size limit (50MB), 30s model timeout, MIME magic byte validation, structured logging, explicit del of sensitive data in memory, pip-audit for CVE scanning
  • Strong Typing: Pydantic schemas for input/output validation
  • Testability: 123 tests with 100% code coverage, mocked vision APIs
  • Simplicity: YAGNI — only the requested fields, no extra abstractions

Metadata

Release files for bank-receipt-extractor 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bank-receipt-extractor 0.2.1
File Size Uploaded Provenance Actions
bank_receipt_extractor-0.2.1.tar.gz 238.7 kB Without provenance Details for bank_receipt_extractor-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bank-receipt-extractor 0.2.1
File Interpreter ABI Platform Actions
bank_receipt_extractor-0.2.1-py3-none-any.whl Python 3 none any Details for bank_receipt_extractor-0.2.1-py3-none-any.whl

Total release size: 256.3 kB

Release files / bank_receipt_extractor-0.2.1.tar.gz

Download URL bank_receipt_extractor-0.2.1.tar.gz
Size 238.7 kB
Tags Source
SHA-256 checksum
How to use checksums
541a77b125163070323f20c70b1450727fcbe82431ad6a4382db46e991cbfdd5
BLAKE2b-256 checksum
How to use checksums
06ae762f522bcaf34f7c29246de7f68c0f31bf63691925e822e930786419a0d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / bank_receipt_extractor-0.2.1-py3-none-any.whl

Download URL bank_receipt_extractor-0.2.1-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3797bcaa3ba16cc94890e0b0823d3a8ccb60f9bfd38f7aa7989c7e7327ccde49
BLAKE2b-256 checksum
How to use checksums
253c9f9bf2d62b0a7ec8ceaa8755042b1ff36f3035763472026bd12b36ded1c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for bank-receipt-extractor 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page