Skip to main content

Parsing Documents

You can parse various Brazilian fiscal documents, US passports, US driver licenses, and bank statements using the abstra.ai module. The AI-powered OCR extracts structured data from PDFs and images automatically.

Acquiring the file (use a Page)

File-upload flows belong in a Page, not a Form. In a Page the uploaded bytes arrive in-memory as file["content"], and you persist them with get_persistent_dir() so the file is still on disk when the parser runs. (A Form's FileInput returns a FileResponse whose .path / .name / .content re-materialize the upload lazily and break across step transitions.)

This canonical example uploads a document, persists it, and parses it. Every parser in the rest of this page works the same way — only the parse_* call changes.

from pathlib import Path

from abstra.pages import register_function
from abstra.common import get_persistent_dir
from abstra.ai import parse_boleto


@register_function
def analyze(file: dict):
# The upload arrives as {"filename", "content_type", "content"}. Sanitize the
# name (it comes from the client) and persist the bytes to a real path on disk.
safe_name = Path(file["filename"]).name
dest = get_persistent_dir() / "uploads" / safe_name
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(file["content"])

# Parse from the persisted Path. Parser models return real date objects, so
# call .isoformat() before returning them — a registered function's return is
# JSON-serialized and cannot encode a date/datetime directly.
boleto = parse_boleto(dest)
return {
"beneficiary": boleto.beneficiario,
"amount_reais": boleto.valor / 100,
"due_date": boleto.vencimento.isoformat() if boleto.vencimento else None,
"barcode": boleto.codigo_de_barras,
}


@register_function
def __render__():
return """
<input type="file" id="doc" accept=".pdf,.jpg,.jpeg,.png">
<button onclick="go()">Analyze</button>
<pre id="out"></pre>
<script>
async function go() {
const file = document.getElementById('doc').files[0];
const result = await analyze(file); // pass the File directly
document.getElementById('out').textContent = JSON.stringify(result, null, 2);
}
</script>
"""

The sections below show each parser and the fields it returns. In every snippet, dest is the persisted upload Path produced inside analyze above — call the parser there, right after write_bytes. Date fields come back as datetime.date/datetime.datetime, so .isoformat() them before returning.

US Passport Parsing

Extract personal information and document details from US passports:

from datetime import datetime

from abstra.ai import parse_us_passport

passport = parse_us_passport(dest)

# expiration_date is a datetime.date — compare dates directly, never strptime it
today = datetime.now().date()
exp = passport.expiration_date
is_expired = bool(exp and exp < today)
days_until_expiry = (exp - today).days if exp else None

result = {
"full_name": f"{passport.given_names} {passport.family_name}",
"passport_number": passport.document_id,
"date_of_birth": passport.date_of_birth.isoformat() if passport.date_of_birth else None,
"issue_date": passport.issue_date.isoformat() if passport.issue_date else None,
"expiration_date": exp.isoformat() if exp else None,
"expired": is_expired,
"expires_within_6_months": bool(days_until_expiry is not None and 0 <= days_until_expiry <= 180),
"mrz_code": passport.mrz_code,
}

Boleto Parsing

Process Brazilian payment slips and extract banking information:

from datetime import datetime

from abstra.ai import parse_boleto

boleto = parse_boleto(dest)

# vencimento is a datetime.date — compare dates directly, never strptime it
today = datetime.now().date()
due = boleto.vencimento
is_overdue = bool(due and due < today)

result = {
"beneficiary": boleto.beneficiario,
"amount_reais": boleto.valor / 100,
"due_date": due.isoformat() if due else None,
"barcode": boleto.codigo_de_barras,
"overdue_days": (today - due).days if is_overdue else 0,
"bank_reference": boleto.nosso_numero,
"document_number": boleto.numero_documento,
"beneficiary_tax_id": boleto.cpf_cnpj_beneficiario,
}

NFSe Parsing

Extract service invoice information from Brazilian NFSe documents:

from abstra.ai import parse_nfse

nfse = parse_nfse(dest)

# Amounts come in centavos
net_amount = nfse.valor_liquido_centavos / 100 if nfse.valor_liquido_centavos else 0
total_amount = nfse.valor_total_centavos / 100 if nfse.valor_total_centavos else 0

result = {
"invoice_number": nfse.numero_nota,
"issue_date": nfse.data_emissao.isoformat() if nfse.data_emissao else None,
"net_amount": net_amount,
"total_amount": total_amount,
"provider": {
"company": nfse.razao_social_prestador,
"cnpj": nfse.cnpj_prestador,
"address": nfse.endereco_prestador,
"email": nfse.email_prestador,
},
"recipient": {
"company": nfse.razao_social_tomador,
"cnpj": nfse.cnpj_tomador,
"address": nfse.endereco_tomador,
},
"description": nfse.descricao,
}

NFe Parsing

Process comprehensive Brazilian electronic invoices:

from abstra.ai import parse_nfe

nfe = parse_nfe(dest)

result = {
"invoice_number": nfe.numero_nota,
"series": nfe.serie,
"access_key": nfe.chave_acesso,
"issue_date": nfe.data_emissao.isoformat() if nfe.data_emissao else None,
"products_value": nfe.valor_produtos,
"total_value": nfe.valor_total,
"icms_tax": nfe.valor_icms,
"ipi_tax": nfe.valor_ipi,
"issuer": {
"company": nfe.razao_social_emitente,
"cnpj": nfe.cnpj_emitente,
"address": nfe.endereco_emitente,
},
"recipient": {
"name": nfe.nome_destinatario,
"tax_id": nfe.cpf_cnpj_destinatario,
"address": nfe.endereco_destinatario,
},
"product": {
"description": nfe.descricao_produto,
"code": nfe.codigo_produto,
"ncm_sh": nfe.ncm_sh,
"unit_value": nfe.valor_unitario,
},
"additional_info": nfe.informacoes_adicionais,
}

US Driver License

Extract personal information and licensing details from US driver's licenses:

from datetime import datetime

from abstra.ai import parse_us_driver_license

license_data = parse_us_driver_license(dest)

result = {
"given_names": license_data.given_names,
"family_name": license_data.family_name,
"date_of_birth": license_data.date_of_birth.isoformat() if license_data.date_of_birth else None,
"address": license_data.address,
"document_id": license_data.document_id,
"issue_date": license_data.issue_date.isoformat() if license_data.issue_date else None,
"expiration_date": license_data.expiration_date.isoformat() if license_data.expiration_date else None,
}

# expiration_date is a datetime.date; compare dates directly
if license_data.expiration_date:
days_until_expiry = (license_data.expiration_date - datetime.now().date()).days
result["expired"] = days_until_expiry < 0
result["days_until_expiry"] = days_until_expiry

Bank Statement

Extract transaction details and account information from bank statements:

from abstra.ai import parse_bank_statement

statement = parse_bank_statement(dest)

result = {
"bank_name": statement.bank_name,
"bank_address": statement.bank_address,
"client_name": statement.client_name,
"client_address": statement.client_address,
"account_number": statement.account_number,
"account_type": statement.account_type,
"statement_start_date": statement.statement_start_date.isoformat() if statement.statement_start_date else None,
"statement_end_date": statement.statement_end_date.isoformat() if statement.statement_end_date else None,
"starting_balance": statement.starting_balance,
"ending_balance": statement.ending_balance,
}

# Balances are strings; convert defensively before doing math
try:
start = float(statement.starting_balance.replace("$", "").replace(",", "").strip())
end = float(statement.ending_balance.replace("$", "").replace(",", "").strip())
result["net_change"] = end - start
except (ValueError, AttributeError):
result["net_change"] = None

Plain text extraction

For text-heavy documents that don't match a specialized parser (contracts, reports, letters), extract the text with extract_text and pass the resulting string to prompt — more reliable than passing the document directly:

from abstra.ai import extract_text, prompt

text = extract_text(dest)
summary = prompt([text, "Summarize this document in three bullet points."])

Tips for Best Results

  • Image Quality: Use high-resolution scans (300+ DPI)
  • File Formats: PDF files typically provide the best results
  • Lighting: Ensure even lighting without shadows or glare
  • Orientation: Keep documents properly oriented and flat
  • File Size: Keep files under 10MB for optimal processing speed
  • Complete Documents: Include all pages and sections of the document