Metadata-Version: 2.4
Name: br-address-normalize
Version: 0.3.0.dev20260408114323
Summary: Biblioteca de normalização de endereços brasileiros
Author-email: Inspire / F1 Qualidade <contact@inspire-f1.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/inspire-f1/br-address-normalize
Project-URL: Bug Tracker, https://github.com/inspire-f1/br-address-normalize/issues
Project-URL: Repository, https://github.com/inspire-f1/br-address-normalize.git
Keywords: address,normalization,brazilian,endereco
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.0.0
Requires-Dist: httpx>=0.24.0
Requires-Dist: redis>=5.0.0
Dynamic: license-file

# 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

```python
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

```python
{
    "logradouro": str,
    "numero": str,
    "complemento": str,
    "bairro": str,
    "municipio": str,
    "uf": str,   # sigla de 2 letras
    "cep": str,
}
```

### Saída — `NormalizationResult`

```python
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:
```python
{
    "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ência** — `normalize(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:

```bash
# 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

```bash
# 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:

```bash
# .env
PYTHONPATH=src
LOG_LEVEL=INFO
ENABLE_V2=true
ABBREVIATION_PROVIDER=local
```

### Desenvolvimento Local

```bash
# 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:**

```python
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):**

```bash
# 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
# 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:**

```bash
# 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

```bash
# 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`:

```ini
[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:**

```bash
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)

```nginx
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

```bash
# 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:**

```python
# 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:**

```yaml
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

```bash
# 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

```python
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:

```python
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:

```python
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)**
```python
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**
```python
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

```python
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`:

```python
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

```python
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

```python
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

```python
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

```python
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:

```markdown
# 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

```python
# 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` |

