Biblioteca de normalização de endereços brasileiros
Project description
br-address-normalize
Biblioteca Python para normalização de endereços brasileiros com foco em match rate para cruzamento de bases de dados.
Visão Geral
O objetivo é padronizar endereços de entrada para maximizar a taxa de correspondência com bases de referência. O pipeline é determinístico, idempotente e sem dependências externas no core.
Desenvolvido com base em análise profunda de 76.9M registros de 6 estados (AM, AP, GO, MG, MT, RS), identificando e resolvendo anomalias críticas que impedem matching.
Problemas Identificados
Análise de 76.9M registros revelou anomalias críticas que impedem matching:
1. Encoding Corrompido (2.8M registros)
Padrões de encoding UTF-8 quebrado: ├, $AO, caracteres de controle. Distribuição: AM (105K), GO (231K), MG (1.6M), RS (679K). Impossível fazer match com bases limpas.
2. Campos Trocados (558K registros)
- Só números no logradouro: 420K (ex:
logradouro: "123"→ deveria ser número) - Logradouro no bairro: 93K (ex:
bairro: "RUA BRASIL"→ deveria estar em logradouro) - Nome no número: 43K (ex:
numero: "NINF"→ placeholder no campo errado) - CEP no logradouro: 275 (ex:
logradouro: "69000000"→ CEP deslocado)
3. Abreviações Não Expandidas (51.6M ocorrências)
100% das abreviações de tipo de via sem ponto: R, AV, TV, PL. Títulos não expandidos: DR, GEN, CEL, MAJ. Impacto: RUA BRASIL vs R BRASIL não fazem match.
4. Estrutura Desorganizada (182K registros)
- Complemento embutido no número:
numero: "123 APT 401"→ deve separar - Variantes de S/N (44 formas):
SN,S/N,S.N,S N,SEMNUMERO - CEP com formatos inconsistentes: 5 dígitos, 9+ dígitos, genéricos (
00000000)
5. Placeholders Genéricos (3.3M registros)
Valores como NI, ND, NINF, NAO INFORMADO, [OBJECT OBJECT]. Distribuição: logradouro (3.3M), bairro (456K), município (486K).
6. CEP Divergente da UF (68K registros)
Prefixo do CEP não corresponde à UF declarada. Exemplo: CEP 13000000 (SP) em registro de MG.
Solução Implementada
Pipeline de normalização em v1.0 (finalizada), consolidando todas as transformações determinísticas em 4 camadas sequenciais:
v1.0 — Normalização Determinística Consolidada ✅
Consolida limpeza mecânica, expansão de abreviações, normalização estrutural e resolução contextual:
- Limpeza Mecânica: Encoding, case, whitespace, placeholders (~6.5M registros)
- Expansão de Abreviações: 115 tokens sem ambiguidade (~3.5M registros)
- Normalização Estrutural: Número, complemento, CEP, município, bairro (~4.5M registros)
- Resolução Contextual: Campos trocados, abreviações regionais, validação CEP×UF (~467K registros)
Impacto total: ~18.5M registros (24% da base)
v2.0 — Fallback com IA ⬜ PRÓXIMA
Maritaca AI para ambiguidades não resolvidas em v1.0. Volume esperado: ~348K registros (0.5% da base).
Resultados
Benchmark Consolidado (24.37M registros, 6 UFs)
| Cenário | Baseline | Normalizado | Ganho |
|---|---|---|---|
| C1 — Raw vs Raw | 64.53% | — | — |
| C4 — Norm vs Norm | — | 68.11% | +3.58pp |
| Registros Resgatados | — | — | 872.625 |
| Regressões | — | — | 0 |
Por Grupo de Poluição
| Grupo | Volume | Baseline | Normalizado | Ganho |
|---|---|---|---|---|
| G1 (limpo) | 18.17M | 65.57% | 66.55% | +0.98pp |
| G2 (médio) | 5.15M | 68.93% | 75.86% | +6.94pp |
| G3 (poluído) | 1.04M | 27.68% | 59.95% | +32.27pp |
Maior impacto em dados poluídos (G3), onde o ganho é 32pp — exatamente onde mais precisamos.
Próximos Passos
v2.1 — Fallback Probabilístico com Maritaca AI (Próxima)
Resolver ambiguidades que regras determinísticas não conseguem com confiança ≥ 0.95. Tarefas: benchmark de prompts, análise de custo, implementação de provider com retry/cache, logging estruturado para treinamento futuro.
v3.0 — Validação Canônica (Planejada)
Validar contra fontes oficiais (IBGE, Correios). Tarefas: integração com API IBGE, integração com ViaCEP/Correios, resolução de preposições, cache local.
v4.0 — Modelos Autorais (Futura)
Treinar modelos específicos com logs das versões anteriores. Benefícios: latência < 10ms, custo < 1% de API, sem dependência externa.
Pipeline de Normalização
O pipeline executa 13 steps sequenciais. A ordem é crítica.
Entrada (dict raw)
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ v1.0 — Limpeza Mecânica │
│ [1] UppercaseStep → [2] RemoveAccentsStep → [3] EncodingStep │
├─────────────────────────────────────────────────────────────────┤
│ v1.1 — Expansão de Abreviações │
│ [4] AbbreviationStep → [5] PreprocessingStep │
├─────────────────────────────────────────────────────────────────┤
│ v1.2 — Normalização Estrutural │
│ [6] NumeroStep → [7] ComplementoStep → [8] BairroStep │
│ [9] MunicipioStep → [10] CepStep │
├─────────────────────────────────────────────────────────────────┤
│ v2.0 — Resolução Contextual (enable_v2=True) │
│ [11] FieldSwapStep → [12] RegionalAbbreviationStep │
│ [13] CepUfValidationStep │
└─────────────────────────────────────────────────────────────────┘
│
▼
NormalizationResult
Steps do Pipeline
[1] UppercaseStep — Converte todos os campos para UPPERCASE.
Campos: logradouro, numero, complemento, bairro, municipio, uf
[2] RemoveAccentsStep — Remove acentos e diacríticos via NFD→ASCII.
Exemplos: São João → SAO JOAO, Praça → PRACA
[3] EncodingStep — Corrige mojibake (Windows-1252/Latin-1), normaliza apóstrofos legítimos (D'AGUA, SANT'ANA) e aplica padrão $AO→CAO.
Campos: logradouro, complemento, bairro, municipio
[4] AbbreviationStep — Expande abreviações usando o provider configurado (L1 determinístico + L2 opcional).
Executado antes da limpeza para que R RUA SILVA → RUA RUA SILVA → RUA SILVA.
[5] PreprocessingStep — Remove placeholders (98 tokens do null_markers.json) e deduplica prefixos (RUA RUA → RUA).
[6] NumeroStep — Normaliza variantes de S/N (44 padrões), extrai complemento embutido no número.
Exemplo: 315 APTO 304 → número 315, complemento APTO 304
[7] ComplementoStep — Expande abreviações de complemento (AP→APARTAMENTO, BL→BLOCO).
[8] BairroStep — Remove marcadores genéricos, expande VL→VILA, JD→JARDIM, PQ→PARQUE.
[9] MunicipioStep — Remove sufixo de UF embutido (JUIZ DE FORA/MG → JUIZ DE FORA).
[10] CepStep — Valida 8 dígitos, faz padding com zero à esquerda se 5 dígitos, flag para CEPs genéricos.
[11] FieldSwapStep — Detecta e corrige campos trocados (ver seção abaixo).
[12] RegionalAbbreviationStep — Expande abreviações com regras por UF (ST→SETOR em GO/DF).
[13] CepUfValidationStep — Flag CEP_DIVERGENTE_UF quando prefixo do CEP não corresponde à UF.
Uso
from address_normalizer.facade import NormalizerFacade
from address_normalizer.modules.abbreviation.local_provider import LocalAbbreviationProvider
normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True, # ativa steps v2.0 (padrão)
)
endereco = {
"logradouro": "R DR JOAO SILVA",
"numero": "123 APTO 45",
"complemento": "",
"bairro": "JD AMERICA",
"municipio": "SAO PAULO",
"uf": "SP",
"cep": "01234567",
}
result = await normalizer.normalize(endereco)
print(result.normalizado["logradouro"]) # "RUA DOUTOR JOAO SILVA"
print(result.normalizado["numero"]) # "123"
print(result.normalizado["complemento"]) # "APARTAMENTO 45"
print(result.normalizado["bairro"]) # "JARDIM AMERICA"
print(result.transformacoes) # [{campo, tipo, de, para, regra_aplicada}, ...]
print(result.metadata.flags) # ["placeholder_removido", ...]
print(result.metadata.confianca) # {"logradouro": 1.0, ...}
print(result.etapas_aplicadas) # ["uppercase", "remove_accents", ...]
# Batch
results = await normalizer.normalize_batch([endereco1, endereco2])
Parâmetros do NormalizerFacade
| Parâmetro | Tipo | Default | Descrição |
|---|---|---|---|
abbreviation_provider |
AbbreviationProvider |
— | Provider de abreviações |
enable_v2 |
bool |
True |
Ativa steps v2.0 (FieldSwap, RegionalAbbreviation, CepUfValidation) |
Contrato de Entrada/Saída
Entrada
{
"logradouro": str,
"numero": str,
"complemento": str,
"bairro": str,
"municipio": str,
"uf": str, # sigla de 2 letras
"cep": str,
}
Saída — NormalizationResult
result.original # dict — entrada original preservada
result.normalizado # dict — campos normalizados
result.etapas_aplicadas # list[str] — steps executados
result.metadata # NormalizationMetadata
result.metadata.transformacoes # list[dict] — cada transformação aplicada
result.metadata.flags # list[str] — sinalizações para etapas futuras
result.metadata.confianca # dict[str, float] — score 0-1 por campo
# Aliases retrocompatíveis
result.transformacoes # → result.metadata.transformacoes
result.flags # → result.metadata.flags
result.confianca # → result.metadata.confianca
Estrutura de cada transformação:
{
"campo": "logradouro",
"tipo": "abreviacao", # abreviacao | remocao | encoding | limpeza | ...
"de": "R.",
"para": "RUA",
"regra_aplicada": "LOCAL_DICT" # opcional
}
Propriedades do normalizador:
- Idempotente:
normalize(normalize(x)) == normalize(x) - Stateless e determinístico: sem chamadas externas, sem efeitos colaterais
- Toda transformação registrada em
metadata.transformacoes
Sistema de Abreviações
Providers Disponíveis
| Provider | Classe | Descrição |
|---|---|---|
| Local | LocalAbbreviationProvider |
Usa abbreviations_unified.json + DeterministicLayer |
| API | ApiAbbreviationProvider |
Consulta API externa |
| Orchestrator | OrchestratorAdapter |
Adapta AddressExpander (L1+L2) para a interface AbbreviationProvider |
Camada L1 — Gates Determinísticos
O DeterministicLayer processa tokens através de 8 gates em ordem:
| Gate | Nome | Ação |
|---|---|---|
| 1 | TOKENS_TO_REMOVE_FIELD |
ND, NINF → nulifica campo inteiro |
| 1.5 | SIGLA_SISTEMA |
NI, END → expande + flag |
| 2 | DO_NOT_EXPAND |
Siglas de órgãos → preserva |
| 3 | BCO |
Posição 0 → BECO, outras → BANCO |
| 4 | PENDING_SAMPLE |
Tokens em análise → preserva + flag |
| 5 | POSITIONAL_AMBIGUOUS |
CD, CM, PA, LG, GL, BX → resolução contextual |
| 6 | Dicionários por categoria | Expansão por campo e posição |
| 7 | Dicionário geral | Fallback |
| 8 | SIGLA+dígito |
Padrão ABC123 → flag código interno |
Dicionários por Categoria
| Dicionário | Campos | Posições |
|---|---|---|
TIPO_LOGRADOURO |
logradouro, bairro | 0-1 (logradouro), qualquer (bairro) |
TITULO_PROPRIO |
logradouro, bairro | qualquer |
MUNICIPIO_SAFE |
municipio | qualquer |
COMPLEMENTO |
complemento | qualquer |
Abreviações Implementadas
Tipos de Logradouro
| Sigla | Expansão | Sigla | Expansão |
|---|---|---|---|
| R | RUA | AV | AVENIDA |
| TV, TRA | TRAVESSA | RD, ROD | RODOVIA |
| AL | ALAMEDA | ET, ESTR | ESTRADA |
| PCA | PRACA | BEC, BCO | BECO |
| PSG | PASSAGEM | VIL, VLA, VL | VILA |
| CGO, CRG | CORREGO | FZ | FAZENDA |
| IG | IGARAPE | JRD, JD | JARDIM |
| CHAC | CHACARA | DIST | DISTRITO |
| LG | LARGO | PQ, PARQ, PQE | PARQUE |
| COND | CONDOMINIO | CONJ | CONJUNTO |
| BALN | BALNEARIO | ST | SETOR (GO/DF) |
| QD | QUADRA | VC | VICINAL |
| LIN, LI | LINHA | LD, LADER | LADEIRA |
| SIT | SITIO | GRJ | GRANJA |
Títulos e Tratamentos
| Sigla | Expansão | Sigla | Expansão |
|---|---|---|---|
| PRES | PRESIDENTE | GEN | GENERAL |
| MAL | MARECHAL | CAP | CAPITAO |
| CEL | CORONEL | DR | DOUTOR |
| PE | PADRE | PROF, PRF | PROFESSOR |
| VER | VEREADOR | SEN | SENADOR |
| DEP | DEPUTADO | GOV | GOVERNADOR |
| PREF | PREFEITO | DES | DESEMBARGADOR |
| DNA | DONA | FCO | FRANCISCO |
| JORN | JORNALISTA | MNS | MONSENHOR |
| NS, NSRA | NOSSA SENHORA | JK | JUSCELINO KUBITSCHEK |
| STA | SANTA | STO | SANTO |
Complemento
| Sigla | Expansão | Sigla | Expansão |
|---|---|---|---|
| APTO, APT | APARTAMENTO | BL, BLC | BLOCO |
| QD, QDA | QUADRA | LT | LOTE |
| SL | SALA | LJ | LOJA |
| AND | ANDAR | EDIF | EDIFICIO |
| FD, FUND | FUNDOS | FR | FRENTE |
| KIT | KITNET | PV | PAVIMENTO |
| SOB | SOBRADO | SS | SUBSOLO |
| KM | QUILOMETRO | BX | BOX |
Siglas Preservadas (DO_NOT_EXPAND)
ABC, CEEE, CTG, DAER, DNER, IBGE, PTB, SESI, SHIS, DF, KVA
Abreviações Regionais (v2.0)
| Token | GO/DF | Outras UFs |
|---|---|---|
| ST | SETOR | não expande |
| S (pos 0) | SETOR | não expande |
| QD | QUADRA | QUADRA |
| LT | LOTE | LOTE |
Módulo de Campos Trocados (v2.0)
Detecta e corrige anomalias onde dados foram inseridos no campo errado.
| Tipo | Padrão | Confiança | Ação |
|---|---|---|---|
LOGRADOURO_SO_NUMEROS |
Logradouro contém apenas dígitos | 1.0 | Flag apenas |
BAIRRO_COM_TIPO_VIA |
Bairro começa com RUA/AV/TV + logradouro vazio/curto | 0.95 | Corrige |
NUMERO_COM_PLACEHOLDER |
Número contém NINF, SNZ, etc. | 1.0 | Nulifica |
NUMERO_COM_COMPLEMENTO |
Número contém CASA, FUNDOS, etc. | 0.95-1.0 | Move para complemento |
CEP_NO_LOGRADOURO |
Logradouro contém padrão de CEP | 1.0 | Extrai para campo CEP |
Tipos legítimos em bairro (não detectados como campo trocado): VILA, PARQUE, JARDIM, CONJUNTO, CONDOMINIO, RESIDENCIAL, LOTEAMENTO, NUCLEO, SETOR
Validação CEP×UF (v2.0)
| Prefixo | UF(s) | Prefixo | UF(s) |
|---|---|---|---|
| 01-19 | SP | 20-28 | RJ |
| 29 | ES | 30-39 | MG |
| 40-48 | BA | 49 | SE |
| 50-56 | PE | 57 | AL |
| 58 | PB | 59 | RN |
| 60-63 | CE | 64 | PI |
| 65 | MA | 66-68 | PA |
| 69 | AM, RR | 70-73 | DF, GO |
| 74-76 | GO | 77 | TO |
| 78 | MT | 79 | MS |
| 80-87 | PR | 88-89 | SC |
| 90-99 | RS |
CEP divergente gera flag CEP_DIVERGENTE_UF. CEP ou UF vazios → nenhuma ação.
Flags de Rastreabilidade
| Flag | Versão | Significado |
|---|---|---|
ENCODING_CORRIGIDO |
v1.0 | Encoding corrompido corrigido |
PLACEHOLDER_REMOVIDO |
v1.0 | Placeholder removido |
ABREV_EXPANDIDA |
v1.1 | Abreviação expandida |
DUPLICADO_REMOVIDO |
v1.1 | Tipo de via duplicado removido |
VARIANTE_SN |
v1.2 | Variante de S/N normalizada |
CEP_GENERICO |
v1.2 | CEP genérico (00000000, etc.) |
CAMPO_TROCADO_DETECTADO |
v2.0 | Campo trocado detectado |
CAMPO_TROCADO_CORRIGIDO |
v2.0 | Campo trocado corrigido |
CAMPO_TROCADO_AMBIGUO |
v2.0 | Detectado mas não corrigido (para v2.1) |
ABREV_REGIONAL |
v2.0 | Abreviação expandida por regra regional |
ABREV_AMBIGUA |
v2.0 | Abreviação ambígua não resolvida (para v2.1) |
TYPO_BAIRRO_CORRIGIDO |
v2.0 | Typo de bairro corrigido |
CEP_DIVERGENTE_UF |
v2.0 | CEP diverge da UF declarada |
Estrutura de Diretórios
src/address_normalizer/
├── core/
│ └── base.py # Interface PipelineStep (ABC)
├── data/
│ ├── __init__.py # Funções de carregamento de dados
│ ├── abbreviations_unified.json # Dicionário unificado de abreviações
│ ├── abreviacoes_regionais.json # Regras regionais por UF
│ ├── bairro_config.json # Marcadores e typos de bairro
│ ├── brasilia_prefixes.json # Prefixos de Brasília
│ ├── cep_genericos.json # CEPs genéricos conhecidos
│ ├── cep_prefixos_uf.json # Mapeamento CEP→UF
│ ├── complemento_abrev.json # Abreviações de complemento
│ ├── field_swap_config.json # Tipos de logradouro para detecção
│ ├── null_markers.json # 98 marcadores de campo nulo
│ └── whitelist.json # Tokens que não devem ser alterados
├── schemas/
│ └── endereco.py # NormalizationResult, NormalizationMetadata, EnderecoInput
├── facade.py # NormalizerFacade — ponto de entrada público
├── modules/
│ ├── abbreviation/
│ │ ├── base.py # Interface AbbreviationProvider
│ │ ├── api_provider.py # Provider via API externa
│ │ ├── local_provider.py # Provider via JSON local (usa DeterministicLayer)
│ │ ├── orchestrator.py # AddressExpander (L1+L2)
│ │ ├── orchestrator_adapter.py # Adapta AddressExpander → AbbreviationProvider
│ │ ├── deterministic.py # Camada L1 — 8 gates determinísticos
│ │ ├── entity_rules.py # Regras por campo e posição
│ │ ├── heuristics.py # Heurísticas de confiança L2
│ │ ├── models.py # Dataclasses do módulo
│ │ ├── probabilistic.py # Camada L2 probabilística
│ │ └── regional.py # Abreviações regionais por UF
│ ├── field_swap/
│ │ ├── detector.py # Detecta 5 tipos de campos trocados
│ │ └── corrector.py # Corrige campos com confiança >= 0.95
│ ├── apostrophe.py # Normalização de apóstrofos legítimos
│ ├── bairro.py # Lógica de normalização de bairro
│ ├── cep.py # Lógica de normalização de CEP
│ ├── complemento.py # Lógica de normalização de complemento
│ ├── encoding.py # Correção de encoding corrompido
│ ├── municipio.py # Lógica de normalização de município
│ ├── numero.py # Lógica de normalização de número
│ └── preprocessing.py # Limpeza e remoção de placeholders
└── pipeline/
├── orchestrator.py # Pipeline — executa steps sequencialmente
└── steps/
├── uppercase.py
├── remove_accents.py
├── encoding.py
├── abbreviation.py
├── preprocessing.py
├── numero.py
├── complemento.py
├── bairro.py
├── municipio.py
├── cep.py
├── field_swap.py
├── regional_abbreviation.py
└── cep_uf_validation.py
tests/
├── conftest.py
├── test_integration.py
├── test_v1_2.py
├── test_v1_gaps.py
├── test_v2_0.py
└── test_regression_v1_gaps.py
Roadmap
| Versão | Status | Escopo |
|---|---|---|
| v1.0 | ✅ | Normalização determinística consolidada: limpeza, abreviações, estrutura, contexto |
| v2.0 | ⬜ | Fallback IA (Maritaca AI) para ambiguidades não resolvidas |
| v3.0 | ⬜ | Validação canônica (Correios/IBGE), preposições, tipo de via ausente |
| v4.0 | ⬜ | ML supervisionado / NER treinado com logs das versões anteriores |
Princípios de Design
- Conservadorismo — Só transforma o que tem certeza. Ambiguidade → flag, não transformação.
- Idempotência —
normalize(normalize(x)) == normalize(x) - Sem dependências externas no core — Consultas a Correios/IBGE são escopo de v3.0.
- Rastreabilidade — Toda transformação registrada em
metadata.transformacoes.
Escopo Futuro (não implementado)
- Consulta à API dos Correios → v3.0
- Resolução de preposições (DO, DA, DE) → v3.0
- Adição de tipo de logradouro ausente → v3.0
- Fuzzy matching → v4.0
- Correção de grafia por ML → v4.0
Referências de Análise
Todas as decisões de implementação são baseadas em análises quantitativas de 76.9M registros:
| Análise | Arquivo | Conteúdo |
|---|---|---|
| A-10 | planning/relatorios_tasks/A10_caracteres_anomalos.json |
Encoding corrompido, caracteres estranhos por campo e UF |
| A-20/A-21 | planning/relatorios_tasks/A20_A21_via_duplicada_pontos.json |
Vias duplicadas, abreviações sem ponto |
| A-30 | planning/relatorios_tasks/A30_numero_patologias.json |
Complemento embutido, variantes S/N |
| A-40 | planning/relatorios_tasks/A40_complemento_separadores.json |
Separadores no complemento |
| A-50 | planning/relatorios_tasks/A50_bairro_anomalias.json |
Anomalias em bairro, typos |
| A-60 | planning/relatorios_tasks/A60_municipio_anomalias.json |
Sufixo UF no município |
| A-70 | planning/relatorios_tasks/A70_cep_formatos.json |
CEPs inválidos, genéricos, divergentes |
| A-80 | planning/relatorios_tasks/A80_placeholders_globais.json |
Placeholders por campo |
| A-90 | planning/relatorios_tasks/A90_campos_trocados.json |
Campos trocados, endereços vazios |
| A-99 | planning/relatorios_tasks/A99_sintese_decisoes.json |
45 decisões consolidadas com prioridade |
| Abreviações | planning/relatorios_tasks/abreviacoes_classificadas.json |
115 tokens sem ambiguidade, 25 ambíguos |
| B-01 | planning/relatorios_tasks/B01_completude_combinada.json |
Completude por combinação de campos |
| B-02 | planning/relatorios_tasks/B02_validacao_municipios_ibge.json |
Validação contra IBGE |
| B-03 | planning/relatorios_tasks/B03_volumetria_enderecos_unicos.json |
Volumetria de endereços únicos |
| B-04 | planning/relatorios_tasks/B04_enderecos_rurais.json |
Padrões de endereços rurais |
| B-05 | planning/relatorios_tasks/B05_sobreposicao_anomalias.json |
Sobreposição entre anomalias |
| B-06 | planning/relatorios_tasks/B06_score_qualidade_baseline.json |
Score de qualidade baseline |
Roadmap detalhado: planning/PLANEJAMENTO_VERSOES.md
Setup & Deployment
Guia para desenvolvedores e DevOps configurarem e executarem o projeto independentemente.
Pré-requisitos
- Python: 3.10+
- uv: Gerenciador de pacotes Python (recomendado) ou pip
- Git: Para clonar o repositório
Instalação Rápida com uv
uv é um gerenciador de pacotes Python ultra-rápido. Se não tiver instalado:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# Ou via pip
pip install uv
Clone e Setup
# Clone o repositório
git clone <repo-url>
cd br-address-normalize
# Crie ambiente virtual com uv (automático)
uv venv
# Ative o ambiente
source .venv/bin/activate # Linux/macOS
# ou
.venv\Scripts\activate # Windows
# Instale dependências (via pyproject.toml)
uv pip install -e .
# Ou com pip direto
pip install -e .
# Para desenvolvimento (com ferramentas de teste/lint)
uv pip install -e ".[dev]"
Estrutura de Dependências
Dependências principais (definidas em pyproject.toml):
br-address-normalize
├── pydantic >= 2.0.0 # Validação de schemas e modelos
├── httpx >= 0.24.0 # Cliente HTTP assíncrono
└── redis >= 5.0.0 # Cache distribuído (opcional)
Dependências transitivas (instaladas automaticamente):
├── anyio # Suporte assíncrono
├── certifi # Certificados SSL
├── h11 # Protocolo HTTP/1.1
├── httpcore # Core HTTP
├── idna # Codificação de domínios
├── sniffio # Detecção de async runtime
└── typing-extensions # Type hints adicionais
Dependências de desenvolvimento (opcional):
├── pytest >= 7.0.0 # Framework de testes
├── pytest-cov >= 4.0.0 # Cobertura de testes
├── black >= 23.0.0 # Formatação de código
├── mypy >= 1.0.0 # Type checking
└── flake8 >= 6.0.0 # Linting
Variáveis de Ambiente
Crie um arquivo .env na raiz do projeto:
# .env
PYTHONPATH=src
LOG_LEVEL=INFO
ENABLE_V2=true
ABBREVIATION_PROVIDER=local
Desenvolvimento Local
# Instale dependências de desenvolvimento
uv pip install -e ".[dev]"
# Execute testes
pytest tests/ -v
# Execute testes com cobertura
pytest tests/ --cov=src/address_normalizer --cov-report=html
# Formate código
black src/ tests/
# Verifique tipos
mypy src/
# Lint
flake8 src/ tests/
Executar Normalização
Script Python direto:
import asyncio
from address_normalizer.facade import NormalizerFacade
from address_normalizer.modules.abbreviation.local_provider import LocalAbbreviationProvider
async def main():
normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
)
endereco = {
"logradouro": "r brasil",
"numero": "123 apt 401",
"complemento": "",
"bairro": "centr",
"municipio": "sao paulo",
"uf": "SP",
"cep": "01310100",
}
result = await normalizer.normalize(endereco)
print(result.normalizado)
asyncio.run(main())
Via CLI (se disponível):
# Normalizar um endereço
python -m address_normalizer normalize \
--logradouro "r brasil" \
--numero "123" \
--bairro "centro" \
--municipio "sao paulo" \
--uf "SP" \
--cep "01310100"
# Processar arquivo CSV
python -m address_normalizer batch \
--input enderecos.csv \
--output enderecos_normalizados.csv \
--format csv
# Processar arquivo Parquet
python -m address_normalizer batch \
--input enderecos.parquet \
--output enderecos_normalizados.parquet \
--format parquet
Docker (Opcional)
Se preferir usar Docker:
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Instale uv
RUN pip install uv
# Copie requirements
COPY requirements.txt .
# Instale dependências
RUN uv pip install --system -r requirements.txt
# Copie código
COPY src/ src/
COPY data/ data/
# Exponha porta (se for API)
EXPOSE 8000
# Comando padrão
CMD ["python", "-m", "address_normalizer"]
Build e run:
# Build
docker build -t br-address-normalize:latest .
# Run
docker run -v $(pwd)/data:/app/data br-address-normalize:latest
Deployment em Produção
1. Preparar Ambiente
# Clone em produção
git clone <repo-url> /opt/br-address-normalize
cd /opt/br-address-normalize
# Crie venv
python3.11 -m venv /opt/br-address-normalize/.venv
# Ative
source /opt/br-address-normalize/.venv/bin/activate
# Instale (via pyproject.toml)
pip install -e .
2. Systemd Service (Linux)
Crie /etc/systemd/system/address-normalizer.service:
[Unit]
Description=Address Normalizer Service
After=network.target
[Service]
Type=simple
User=normalizer
WorkingDirectory=/opt/br-address-normalize
Environment="PATH=/opt/br-address-normalize/.venv/bin"
Environment="PYTHONPATH=/opt/br-address-normalize/src"
ExecStart=/opt/br-address-normalize/.venv/bin/python -m address_normalizer.api
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Ativar:
sudo systemctl daemon-reload
sudo systemctl enable address-normalizer
sudo systemctl start address-normalizer
sudo systemctl status address-normalizer
3. Nginx Reverse Proxy (se for API)
upstream address_normalizer {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://address_normalizer;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4. Monitoramento
# Logs
tail -f /var/log/address-normalizer.log
# Health check
curl http://localhost:8000/health
# Métricas
curl http://localhost:8000/metrics
Troubleshooting
| Problema | Solução |
|---|---|
ModuleNotFoundError: No module named 'address_normalizer' |
Verifique PYTHONPATH=src no .env ou execute com python -m |
ImportError: cannot import name 'LocalAbbreviationProvider' |
Instale dependências: uv pip install -e . |
| Testes falhando | Limpe cache: rm -rf .pytest_cache __pycache__ e reinstale |
| Encoding issues | Defina PYTHONIOENCODING=utf-8 no ambiente |
| Memória insuficiente em batch | Processe em chunks: --chunk-size 10000 |
redis.ConnectionError |
Redis é opcional; se não usar, remova de pyproject.toml |
Performance
Otimizações recomendadas:
# Use cache de abreviações
normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
cache_enabled=True, # Cache em memória
cache_ttl=3600, # 1 hora
)
# Processe em paralelo
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(normalizer.normalize, addr) for addr in enderecos]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
Benchmarks (máquina típica):
| Operação | Tempo | Throughput |
|---|---|---|
| Normalizar 1 endereço | ~2-5ms | 200-500 addr/s |
| Batch 10K endereços | ~20-50s | 200-500 addr/s |
| Batch 1M endereços | ~30-50min | 200-500 addr/s |
CI/CD
GitHub Actions exemplo:
name: Test & Deploy
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install uv
- run: uv pip install -e ".[dev]"
- run: pytest tests/ --cov
- run: black --check src/
- run: flake8 src/
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: docker build -t br-address-normalize:${{ github.sha }} .
- run: docker push br-address-normalize:${{ github.sha }}
Suporte
Para problemas de setup:
- Verifique Python version:
python --version(deve ser 3.10+) - Verifique uv:
uv --version - Limpe cache:
rm -rf .venv __pycache__ .pytest_cache - Reinstale:
uv pip install --force-reinstall -e . - Abra issue no repositório com logs completos
Nota sobre dependências opcionais:
- Redis: Opcional para cache distribuído. Se não usar, remova de
pyproject.tomlantes de instalar. - httpx: Necessário para chamadas HTTP (v2.1+ com Maritaca AI)
- pydantic: Necessário para validação de schemas
Guia de Implementação em Projeto Externo
Este guia fornece um prompt estruturado e pronto para produção para integrar br-address-normalize em um projeto externo que já possui benchmark de qualidade.
Contexto
Você está implementando normalização de endereços brasileiros em um projeto que já possui benchmark de qualidade. O objetivo é maximizar o match rate em cruzamentos de bases de dados.
Você usará br-address-normalize como dependência principal. Esta biblioteca implementa um pipeline determinístico de 4 camadas testado em 76.9M registros reais.
Ganho esperado: +3.58pp em match rate (baseline 64.53% → normalizado 68.11%), com impacto de até +32.27pp em dados poluídos.
Fase 1: Setup e Integração (1-2 dias)
1.1 Instalação
# Adicione ao requirements.txt ou pyproject.toml
br-address-normalize>=1.0.0
# Ou instale direto
pip install br-address-normalize
1.2 Importação Básica
from address_normalizer.facade import NormalizerFacade
from address_normalizer.modules.abbreviation.local_provider import LocalAbbreviationProvider
import asyncio
# Inicialize o normalizador
normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True, # Ativa steps v2.0 (recomendado)
)
# Normalize um endereço
endereco = {
"logradouro": "r brasil",
"numero": "123 apt 401",
"complemento": "",
"bairro": "centr",
"municipio": "sao paulo",
"uf": "SP",
"cep": "01310100",
}
result = await normalizer.normalize(endereco)
print(result.normalizado) # Endereço normalizado
print(result.transformacoes) # Histórico de transformações
1.3 Mapeamento de Schema
Identifique os campos do seu projeto que correspondem a:
| Campo Obrigatório | Seu Campo | Tipo | Exemplo |
|---|---|---|---|
logradouro |
? | str | "RUA BRASIL" |
numero |
? | str | "123" |
complemento |
? | str | "APARTAMENTO 401" |
bairro |
? | str | "CENTRO" |
municipio |
? | str | "SAO PAULO" |
uf |
? | str | "SP" |
cep |
? | str | "01310100" |
Se seu schema é diferente, crie uma função de mapeamento:
def mapear_para_endereco(seu_registro):
"""Mapeia seu schema para o schema da lib"""
return {
"logradouro": seu_registro.get("street_name", ""),
"numero": seu_registro.get("street_number", ""),
"complemento": seu_registro.get("unit", ""),
"bairro": seu_registro.get("neighborhood", ""),
"municipio": seu_registro.get("city", ""),
"uf": seu_registro.get("state", ""),
"cep": seu_registro.get("postal_code", ""),
}
def mapear_de_endereco(resultado_normalizado):
"""Mapeia resultado normalizado de volta para seu schema"""
return {
"street_name": resultado_normalizado["logradouro"],
"street_number": resultado_normalizado["numero"],
"unit": resultado_normalizado["complemento"],
"neighborhood": resultado_normalizado["bairro"],
"city": resultado_normalizado["municipio"],
"state": resultado_normalizado["uf"],
"postal_code": resultado_normalizado["cep"],
}
Fase 2: Integração com Benchmark (2-3 dias)
2.1 Estrutura de Teste
Seu benchmark deve medir 4 cenários:
| Cenário | Query | DB | Descrição |
|---|---|---|---|
| C1 | Raw | Raw | Baseline (sem normalização) |
| C2 | Raw | Normalizado | Query bruto vs DB normalizado |
| C3 | Normalizado | Raw | Query normalizado vs DB bruto |
| C4 | Normalizado | Normalizado | Gold standard (ambos normalizados) |
Implementação:
import asyncio
from address_normalizer.facade import NormalizerFacade
from address_normalizer.modules.abbreviation.local_provider import LocalAbbreviationProvider
class BenchmarkNormalizer:
def __init__(self):
self.normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
)
self.cache = {} # Cache para evitar re-normalizar
async def normalize_batch(self, registros):
"""Normaliza lote de registros"""
resultados = []
for reg in registros:
# Crie chave de cache
chave = self._criar_chave_cache(reg)
if chave not in self.cache:
endereco = mapear_para_endereco(reg)
resultado = await self.normalizer.normalize(endereco)
self.cache[chave] = resultado
resultados.append(self.cache[chave])
return resultados
def _criar_chave_cache(self, reg):
"""Cria chave única para cache"""
return (
reg.get("street_name", ""),
reg.get("street_number", ""),
reg.get("city", ""),
reg.get("state", ""),
)
# Uso
normalizer = BenchmarkNormalizer()
# Prepare dados
query_raw = carregar_query_raw()
db_raw = carregar_db_raw()
# Normalize
query_norm = await normalizer.normalize_batch(query_raw)
db_norm = await normalizer.normalize_batch(db_raw)
# Benchmark
c1_score = benchmark_match(query_raw, db_raw) # Raw vs Raw
c2_score = benchmark_match(query_raw, db_norm) # Raw vs Norm
c3_score = benchmark_match(query_norm, db_raw) # Norm vs Raw
c4_score = benchmark_match(query_norm, db_norm) # Norm vs Norm
print(f"C1 (Raw vs Raw): {c1_score:.2%}")
print(f"C2 (Raw vs Norm): {c2_score:.2%}")
print(f"C3 (Norm vs Raw): {c3_score:.2%}")
print(f"C4 (Norm vs Norm): {c4_score:.2%}")
print(f"Ganho (C4 - C1): {(c4_score - c1_score):.2%}")
2.2 Função de Match
Defina como você mede "match" entre dois endereços.
Opção 1: Chave Mínima (Recomendado)
def criar_chave_minima(endereco):
"""Chave com campos essenciais"""
return (
endereco["logradouro"],
endereco["numero"],
endereco["municipio"],
)
def match_minimo(end1, end2):
return criar_chave_minima(end1) == criar_chave_minima(end2)
Opção 2: Chave Completa
def criar_chave_completa(endereco):
"""Chave com todos os campos"""
return (
endereco["logradouro"],
endereco["numero"],
endereco["complemento"],
endereco["bairro"],
endereco["municipio"],
endereco["cep"],
)
def match_completo(end1, end2):
return criar_chave_completa(end1) == criar_chave_completa(end2)
2.3 Medição de Impacto
def medir_impacto(query, db, match_fn):
"""Mede match rate entre query e db"""
matches = 0
total = len(query)
for q in query:
for d in db:
if match_fn(q, d):
matches += 1
break # Encontrou match, passa para próximo query
return matches / total if total > 0 else 0
# Calcule ganho
baseline = medir_impacto(query_raw, db_raw, match_minimo)
normalizado = medir_impacto(query_norm, db_norm, match_minimo)
ganho = normalizado - baseline
print(f"Baseline: {baseline:.2%}")
print(f"Normalizado: {normalizado:.2%}")
print(f"Ganho: {ganho:+.2%}")
Fase 3: Análise de Transformações (1-2 dias)
3.1 Rastreamento de Mudanças
A lib registra toda transformação em result.transformacoes:
result = await normalizer.normalize(endereco)
for transformacao in result.transformacoes:
print(f"Campo: {transformacao['campo']}")
print(f"Tipo: {transformacao['tipo']}")
print(f"De: {transformacao['de']}")
print(f"Para: {transformacao['para']}")
print(f"Regra: {transformacao.get('regra_aplicada', 'N/A')}")
print("---")
3.2 Análise de Impacto por Tipo
from collections import Counter
def analisar_transformacoes(resultados):
"""Analisa distribuição de transformações"""
tipos = Counter()
campos = Counter()
regras = Counter()
for resultado in resultados:
for trans in resultado.transformacoes:
tipos[trans['tipo']] += 1
campos[trans['campo']] += 1
regras[trans.get('regra_aplicada', 'N/A')] += 1
print("Transformações por tipo:")
for tipo, count in tipos.most_common():
print(f" {tipo}: {count}")
print("\nTransformações por campo:")
for campo, count in campos.most_common():
print(f" {campo}: {count}")
print("\nTransformações por regra:")
for regra, count in regras.most_common(10):
print(f" {regra}: {count}")
# Uso
resultados = await normalizer.normalize_batch(enderecos)
analisar_transformacoes(resultados)
3.3 Detecção de Regressões
def detectar_regressoes(query_raw, query_norm, db_raw, db_norm, match_fn):
"""Detecta registros que pioraram após normalização"""
regressoes = []
for i, q_raw in enumerate(query_raw):
q_norm = query_norm[i]
# Contar matches antes
matches_antes = sum(1 for d in db_raw if match_fn(q_raw, d))
# Contar matches depois
matches_depois = sum(1 for d in db_norm if match_fn(q_norm, d))
# Se piorou, registre
if matches_depois < matches_antes:
regressoes.append({
"query": q_raw,
"matches_antes": matches_antes,
"matches_depois": matches_depois,
})
return regressoes
# Uso
regressoes = detectar_regressoes(query_raw, query_norm, db_raw, db_norm, match_minimo)
print(f"Regressões detectadas: {len(regressoes)}")
for reg in regressoes[:10]: # Primeiras 10
print(f" {reg['query']} — antes: {reg['matches_antes']}, depois: {reg['matches_depois']}")
Fase 4: Otimizações e Tuning (1-2 dias)
4.1 Processamento em Paralelo
import asyncio
async def normalize_paralelo(enderecos, max_workers=4):
"""Normaliza endereços em paralelo"""
normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
)
# Para processamento paralelo com asyncio
tasks = [normalizer.normalize(mapear_para_endereco(e)) for e in enderecos]
resultados = await asyncio.gather(*tasks)
return resultados
# Uso
resultados = await normalize_paralelo(enderecos, max_workers=8)
4.2 Cache de Resultados
import json
from pathlib import Path
class NormalizerComCache:
def __init__(self, cache_dir="./cache"):
self.normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
)
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
def _criar_chave(self, endereco):
"""Cria chave única para cache"""
return hash(json.dumps(endereco, sort_keys=True))
async def normalize(self, endereco):
"""Normalize com cache"""
chave = self._criar_chave(endereco)
cache_file = self.cache_dir / f"{chave}.json"
if cache_file.exists():
with open(cache_file) as f:
return json.load(f)
resultado = await self.normalizer.normalize(endereco)
with open(cache_file, 'w') as f:
json.dump(resultado.normalizado, f)
return resultado
# Uso
normalizer_cache = NormalizerComCache()
resultado = await normalizer_cache.normalize(endereco)
Fase 5: Documentação e Deployment (1 dia)
5.1 Documentação de Decisões
Crie um documento com:
# Decisões de Implementação
## 1. Schema de Dados
- Mapeamento de campos: [seu schema] → [schema da lib]
- Campos opcionais: [lista]
- Tratamento de nulos: [descrição]
## 2. Estratégia de Match
- Tipo: [Chave Mínima / Chave Completa / Fuzzy]
- Threshold: [valor]
- Justificativa: [por que escolheu]
## 3. Processamento
- Batch size: [número]
- Parallelismo: [sim/não, quantos workers]
- Cache: [sim/não, onde]
## 4. Resultados
- Baseline (C1): [%]
- Normalizado (C4): [%]
- Ganho: [%]
- Regressões: [número]
## 5. Flags Rastreadas
- [lista de flags importantes]
## 6. Próximos Passos
- [melhorias futuras]
5.2 Integração em Pipeline
# pipeline.py
from address_normalizer.facade import NormalizerFacade
from address_normalizer.modules.abbreviation.local_provider import LocalAbbreviationProvider
class PipelineNormalizacao:
def __init__(self):
self.normalizer = NormalizerFacade(
abbreviation_provider=LocalAbbreviationProvider(),
enable_v2=True,
)
async def processar(self, dados):
"""Processa dados através do pipeline"""
resultados = []
for registro in dados:
# Mapeie para schema da lib
endereco = mapear_para_endereco(registro)
# Normalize
resultado = await self.normalizer.normalize(endereco)
# Mapeie de volta
normalizado = mapear_de_endereco(resultado.normalizado)
# Adicione metadata
normalizado['_transformacoes'] = resultado.transformacoes
normalizado['_flags'] = resultado.metadata.flags
normalizado['_confianca'] = resultado.metadata.confianca
resultados.append(normalizado)
return resultados
# Uso em seu pipeline
pipeline = PipelineNormalizacao()
dados_normalizados = await pipeline.processar(dados_brutos)
Checklist de Implementação
- Setup — Instalação e importação da lib
- Schema — Mapeamento de campos definido
- Benchmark — 4 cenários (C1-C4) implementados
- Match — Função de match definida e testada
- Medição — Baseline e normalizado calculados
- Análise — Transformações analisadas
- Regressões — Nenhuma regressão detectada
- Otimização — Cache e paralelismo implementados
- Documentação — Decisões documentadas
- Deployment — Integrado no pipeline de produção
- Monitoramento — Métricas sendo coletadas
Troubleshooting Comum
| Problema | Solução |
|---|---|
ModuleNotFoundError: No module named 'address_normalizer' |
Execute pip install br-address-normalize |
| Normalização muito lenta | Implemente cache e processamento paralelo |
| Muitas regressões | Revise função de match ou ajuste threshold |
| Ganho menor que esperado | Analise distribuição de dados (G1/G2/G3) |
| Encoding issues | Defina PYTHONIOENCODING=utf-8 |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file br_address_normalize-0.3.0.dev20260408114323.tar.gz.
File metadata
- Download URL: br_address_normalize-0.3.0.dev20260408114323.tar.gz
- Upload date:
- Size: 93.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13bfab417b0c908acdb040e188a1b4e4b2289fa220ed753bfabe50f6c38c879e
|
|
| MD5 |
7c84d1a3ae31768d7364e35d87cf83df
|
|
| BLAKE2b-256 |
f40671ddb020f35bd2f983968accdaa90c19c2c7bd8867a41ba28f7dd75502ae
|
Provenance
The following attestation bundles were made for br_address_normalize-0.3.0.dev20260408114323.tar.gz:
Publisher:
publish.yml on lipiw/br-address-normalize
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
br_address_normalize-0.3.0.dev20260408114323.tar.gz -
Subject digest:
13bfab417b0c908acdb040e188a1b4e4b2289fa220ed753bfabe50f6c38c879e - Sigstore transparency entry: 1253266849
- Sigstore integration time:
-
Permalink:
lipiw/br-address-normalize@db5536dea62f1400b2c6f522cdfdb2f822d952de -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lipiw
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db5536dea62f1400b2c6f522cdfdb2f822d952de -
Trigger Event:
push
-
Statement type:
File details
Details for the file br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl.
File metadata
- Download URL: br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl
- Upload date:
- Size: 74.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f33d3107aa78896d84e48a9bbbf6234aa8f1eb7b39ccab0c40bd9977f98069db
|
|
| MD5 |
bce08e04e33e045394f76385e63513ab
|
|
| BLAKE2b-256 |
5f8b1f7ae3c4d17f7f8eb3e32adc13792d844a3f500a8986861a6153dfecc24b
|
Provenance
The following attestation bundles were made for br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl:
Publisher:
publish.yml on lipiw/br-address-normalize
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl -
Subject digest:
f33d3107aa78896d84e48a9bbbf6234aa8f1eb7b39ccab0c40bd9977f98069db - Sigstore transparency entry: 1253266977
- Sigstore integration time:
-
Permalink:
lipiw/br-address-normalize@db5536dea62f1400b2c6f522cdfdb2f822d952de -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lipiw
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db5536dea62f1400b2c6f522cdfdb2f822d952de -
Trigger Event:
push
-
Statement type: