Pular para o conteúdo

9. Namespace `data::` — dados externos

8 funções para cruzar o conteúdo do PDF com listas e tabelas suas. Tudo local: nenhum dado sai da máquina.


Glossários e datasets aceitam caminho relativo ao diretório de execução:

data::load_glossary("termos/juridicos.txt")
data::load_dataset("dados/lotes.csv")

As bases de consulta (query_gtin, query_medicamento, query_postal_code) têm nome fixo e são procuradas nesta ordem:

  1. $PDFL_DATA_DIR (variável de ambiente)
  2. ./dados/
  3. ./
  4. Perfis instalados por pdfl add (pdfl_profiles/*/dados/)
  5. Ao lado do PDF analisado
Terminal window
# Apontando explicitamente para a pasta das bases
PDFL_DATA_DIR=/opt/bases pdfl run perfil.pdfl documento.pdf

Se a base não for encontrada, a mensagem de erro diz onde colocá-la.

Para distribuir bases junto com os perfis, use pdfl pack — veja o capítulo 11.


Um glossário é um arquivo de texto com um termo por linha. Linhas vazias e começadas com # são ignoradas.

termos/obrigatorios.txt:

# Termos que toda apólice precisa conter
prazo de carência
cobertura contratada
condições gerais

Carrega o glossário como lista de termos.

check "Glossário carregado" {
termos = data::load_glossary("termos/obrigatorios.txt")
print("termos no glossário:", termos.length)
require termos.contains("condições gerais")
}

O caminho mais direto: devolve a lista dos termos do glossário que não aparecem no documento. Lista vazia significa que está tudo lá.

check "Cláusulas obrigatórias" {
faltando = data::validate_against_reference("termos/obrigatorios.txt")
assert faltando.length == 0,
"cláusulas ausentes na apólice: #{faltando.join("; ")}"
}

A comparação ignora maiúsculas e espaçamento — “CONDIÇÕES GERAIS” satisfaz “condições gerais”.


Carrega um CSV como lista de linhas; cada linha é uma lista de colunas. Aspas são tratadas conforme o padrão (campo entre aspas pode conter vírgula).

dados/lotes.csv:

lote,descricao,validade
L2026-08,Lote homologado agosto/2026,2028-08-01
L2026-09,Lote homologado setembro/2026,2028-09-01
check "Percorrendo a tabela" {
linhas = data::load_dataset("dados/lotes.csv")
// A primeira linha é o cabeçalho
print("colunas:", linhas.first().join(" | "))
print("registros:", linhas.length - 1)
// get(n) é 1-based: get(1) é a primeira coluna
linhas.each { |linha|
print(linha.get(1), "->", linha.get(2))
}
}

Procura a chave na primeira coluna e devolve o valor da segunda. Devolve null se não encontrar — e como null é falso, dá para testar direto.

check "Lote homologado" {
lote = text::extract_from_region(1, region(400, 50, 150, 20)).trim()
descricao = data::lookup_value("dados/lotes.csv", lote)
assert descricao,
"lote #{lote} não consta na tabela de homologados"
print("lote reconhecido:", descricao)
}

Estas funções procuram arquivos de nome fixo nas pastas descritas em 9.1 e devolvem a linha inteira como lista (ou null).

Consulta gtin.csv. Ignora pontuação do código.

dados/gtin.csv:

gtin,descricao,fabricante
7891234567895,Dipirona Sódica 500mg 20cp,Lab Exemplo
check "Produto homologado" {
// Cruzando com o código lido da própria embalagem
codigo = codes::decode_barcode(1)
produto = data::query_gtin(codigo)
assert produto,
"GTIN #{codigo} não consta na base de produtos"
print("produto:", produto.get(2))
print("fabricante:", produto.get(3))
}

Consulta medicamentos.csv. Aceita o número de registro (primeira coluna) ou parte do nome (segunda coluna).

dados/medicamentos.csv:

registro,nome,principio_ativo,tarja
1.0298.0123,Dipirona Sódica,dipirona monoidratada,livre
1.0298.0456,Amoxicilina,amoxicilina tri-hidratada,vermelha
check "Tarja correta na bula" {
registro = text::extract_from_region(1, region(50, 780, 200, 15)).trim()
medicamento = data::query_medicamento(registro)
assert medicamento,
"registro #{registro} não encontrado na base ANVISA"
// Se a tarja é vermelha, o texto obrigatório precisa estar na arte
tarja = medicamento.get(4)
print("medicamento:", medicamento.get(2), "| tarja:", tarja)
assert tarja != "vermelha" || text::require_text("VENDA SOB PRESCRIÇÃO"),
"medicamento de tarja vermelha sem o texto obrigatório"
}

Consulta ceps.csv. Aceita CEP com ou sem hífen; exige 8 dígitos.

dados/ceps.csv:

cep,logradouro,bairro,cidade,uf
01310100,Avenida Paulista,Bela Vista,Sao Paulo,SP
check "Endereço do fabricante" {
endereco = data::query_postal_code("01310-100")
assert endereco, "CEP não encontrado na base"
print("logradouro:", endereco.get(2))
print("cidade:", endereco.get(4), "-", endereco.get(5))
}

Confere se o trecho informado aparece no endereço daquele CEP.

check "Endereço impresso confere com o CEP" {
// O endereço na embalagem precisa bater com o CEP declarado
assert data::validate_address("01310100", "Avenida Paulista"),
"endereço impresso não corresponde ao CEP informado"
}

// bula_com_bases.pdfl — validação cruzando PDF com bases locais
// Uso: PDFL_DATA_DIR=./bases pdfl run bula_com_bases.pdfl bula.pdf
profile "bula-com-referencias" {
check "Termos obrigatórios ANVISA" tags: ["glossario"] {
faltando = data::validate_against_reference("bases/termos_anvisa.txt")
assert faltando.length == 0,
"textos obrigatórios ausentes: #{faltando.join("; ")}"
}
check "Produto na base" tags: ["dados", "critico"] {
codigo = codes::decode_barcode(1)
produto = data::query_gtin(codigo)
assert produto, "GTIN #{codigo} não homologado"
// O nome na base tem que aparecer impresso na bula
nome = produto.get(2)
assert text::require_text(nome),
"o nome '#{nome}' da base não aparece na bula"
print("produto conferido:", nome)
}
check "Registro e tarja" tags: ["anvisa"] {
registro = text::extract_from_region(1, region(50, 780, 200, 15)).trim()
med = data::query_medicamento(registro)
assert med, "registro #{registro} não encontrado"
assert med.get(4) != "vermelha" || text::require_text("VENDA SOB PRESCRIÇÃO"),
"tarja vermelha exige o texto de prescrição"
}
check "Endereço do fabricante" tags: ["dados"] {
assert data::validate_address("01310100", "Avenida Paulista"),
"endereço do fabricante não confere com o CEP"
}
}

fix:: · Índice · Próximo: Biblioteca padrão →

DigitalOceanObrigado à DigitalOcean pela hospedagem deste site.