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.
9.1 Onde ficam os arquivos
Seção intitulada “9.1 Onde ficam os arquivos”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:
$PDFL_DATA_DIR(variável de ambiente)./dados/./- Perfis instalados por
pdfl add(pdfl_profiles/*/dados/) - Ao lado do PDF analisado
# Apontando explicitamente para a pasta das basesPDFL_DATA_DIR=/opt/bases pdfl run perfil.pdfl documento.pdfSe 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.
9.2 Glossários
Seção intitulada “9.2 Glossários”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 conterprazo de carênciacobertura contratadacondições geraisdata::load_glossary(arquivo)
Seção intitulada “data::load_glossary(arquivo)”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")}data::validate_against_reference(arquivo)
Seção intitulada “data::validate_against_reference(arquivo)”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”.
9.3 Datasets (CSV)
Seção intitulada “9.3 Datasets (CSV)”data::load_dataset(arquivo)
Seção intitulada “data::load_dataset(arquivo)”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,validadeL2026-08,Lote homologado agosto/2026,2028-08-01L2026-09,Lote homologado setembro/2026,2028-09-01check "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)) }}data::lookup_value(arquivo, chave)
Seção intitulada “data::lookup_value(arquivo, chave)”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)}9.4 Bases de consulta
Seção intitulada “9.4 Bases de consulta”Estas funções procuram arquivos de nome fixo nas pastas descritas em 9.1 e
devolvem a linha inteira como lista (ou null).
data::query_gtin(codigo)
Seção intitulada “data::query_gtin(codigo)”Consulta gtin.csv. Ignora pontuação do código.
dados/gtin.csv:
gtin,descricao,fabricante7891234567895,Dipirona Sódica 500mg 20cp,Lab Exemplocheck "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))}data::query_medicamento(registro_ou_nome)
Seção intitulada “data::query_medicamento(registro_ou_nome)”Consulta medicamentos.csv. Aceita o número de registro (primeira coluna) ou
parte do nome (segunda coluna).
dados/medicamentos.csv:
registro,nome,principio_ativo,tarja1.0298.0123,Dipirona Sódica,dipirona monoidratada,livre1.0298.0456,Amoxicilina,amoxicilina tri-hidratada,vermelhacheck "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"}data::query_postal_code(cep)
Seção intitulada “data::query_postal_code(cep)”Consulta ceps.csv. Aceita CEP com ou sem hífen; exige 8 dígitos.
dados/ceps.csv:
cep,logradouro,bairro,cidade,uf01310100,Avenida Paulista,Bela Vista,Sao Paulo,SPcheck "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))}data::validate_address(cep, "trecho")
Seção intitulada “data::validate_address(cep, "trecho")”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"}9.5 Exemplo completo
Seção intitulada “9.5 Exemplo completo”// bula_com_bases.pdfl — validação cruzando PDF com bases locais// Uso: PDFL_DATA_DIR=./bases pdfl run bula_com_bases.pdfl bula.pdfprofile "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" }}