Skip to main content

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ãoSAO JOAO, PraçaPRACA

[3] EncodingStep — Corrige mojibake (Windows-1252/Latin-1), normaliza apóstrofos legítimos (D'AGUA, SANT'ANA) e aplica padrão $AOCAO. 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 SILVARUA RUA SILVARUA SILVA.

[5] PreprocessingStep — Remove placeholders (98 tokens do null_markers.json) e deduplica prefixos (RUA RUARUA).

[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 (APAPARTAMENTO, BLBLOCO).

[8] BairroStep — Remove marcadores genéricos, expande VLVILA, JDJARDIM, PQPARQUE.

[9] MunicipioStep — Remove sufixo de UF embutido (JUIZ DE FORA/MGJUIZ 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 (STSETOR 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

  1. Conservadorismo — Só transforma o que tem certeza. Ambiguidade → flag, não transformação.
  2. Idempotêncianormalize(normalize(x)) == normalize(x)
  3. Sem dependências externas no core — Consultas a Correios/IBGE são escopo de v3.0.
  4. 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:

  1. Verifique Python version: python --version (deve ser 3.10+)
  2. Verifique uv: uv --version
  3. Limpe cache: rm -rf .venv __pycache__ .pytest_cache
  4. Reinstale: uv pip install --force-reinstall -e .
  5. 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.toml antes 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


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

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file br_address_normalize-0.3.0.dev20260408114323.tar.gz.

File metadata

File hashes

Hashes for br_address_normalize-0.3.0.dev20260408114323.tar.gz
Algorithm Hash digest
SHA256 13bfab417b0c908acdb040e188a1b4e4b2289fa220ed753bfabe50f6c38c879e
MD5 7c84d1a3ae31768d7364e35d87cf83df
BLAKE2b-256 f40671ddb020f35bd2f983968accdaa90c19c2c7bd8867a41ba28f7dd75502ae

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl.

File metadata

File hashes

Hashes for br_address_normalize-0.3.0.dev20260408114323-py3-none-any.whl
Algorithm Hash digest
SHA256 f33d3107aa78896d84e48a9bbbf6234aa8f1eb7b39ccab0c40bd9977f98069db
MD5 bce08e04e33e045394f76385e63513ab
BLAKE2b-256 5f8b1f7ae3c4d17f7f8eb3e32adc13792d844a3f500a8986861a6153dfecc24b

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page