CommunityPesquisa e Análise de Dadosgithub.com

dq-parser (Analyzador de dados)

Analisa e atomiza dados em bancos SQL: divide valores compostos (endereços, nomes, datas) em colunas dedicadas, corrige espaços intercalados e erros de codificação, e prepara os dados para padronização e imputação.

O que é dq-parser (Analyzador de dados)?

O `dq-parser` é a primeira etapa de remediação em um pipeline de qualidade de dados SQL, projetado para inspecionar e atomizar valores compostos antes da padronização. Ele suporta qualquer banco SQL (MySQL, PostgreSQL, etc.) e pipelines pandas em Python. O parser divide endereços (rua, número predial, número orientador), nomes (primeiro nome, nome do meio, título), e corrige espaços intercalados (como 'D A V I D') e erros de codificação sistemáticos (como 'Ą' no início de palavra). Ele não faz heurísticas: se não há certeza, deixa NULL e registra a linha em uma tabela de auditoria. O output inclui novos campos `_STD` com valores atômicos, origem preservada, e métricas de taxa de parsing para cada componente. Use após a auditoria de dados e antes da padronização e imputação.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add koprjaa/dq-skills

Installed? Explore more Pesquisa e Análise de Dados skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Parser — Atomização

Primeiro passo da remediação. Não padronize nada que não esteja atômico. Padronizar o campo ADDR_STREET contendo "Na Budíně 854" não adianta — o registro de endereços conhece rua e número separadamente.

Pipeline: auditoria → analyzador → padronizador → catálogo → imputador → deduplicador.

Regras

  • O original não é apagado. A saída analisada vai para novas colunas (_STD, _NUM1, _NUM2). O original serve como evidência para análise antes/depois e como rede de segurança quando o parser errar.
  • Determinístico, não heurístico. Um parser que acerta 97 % das vezes mas erra 3 % causa danos silenciosos. Se não tiver certeza sobre uma regra, deixe NULL e conte quantos restaram. Vale para campos estruturados com padrão conhecido — endereço, nome, data. Para texto livre, onde padrão não existe, o reconhecimento estatístico de entidades (NER, spaCy) é uma ferramenta legítima, mas seu output é uma estimativa: acompanha um score de confiança e um flag, e não vai para coluna de produção sem revisão.
  • Meça a taxa de parsing — quantas linhas foram decompostas. Essa é uma métrica para o relatório.
  • Parse antes de qualquer outra coisa: padronização, imputação e deduplicação dependem de campos atômicos.

Endereço

Defeito mais comum: colunas dedicadas para números de imóveis estão 100 % vazias e o número está misturado no nome da rua. Na fonte aparece como Na Budíně 854, Nádražní 1211/25a, Bezručova 12/3.

Decomposição em três componentes — nome da rua, número predial, número orientador (com possível letra):

-- MySQL 8: REGEXP_SUBSTR / REGEXP_REPLACE
UPDATE PARTY_ADDRESS SET
  ADDR_NUM1_STD   = REGEXP_SUBSTR(ADDR_STREET, '[0-9]+(?=(/[0-9]+[a-zA-Z]?)?[[:space:]]*$)'),
  ADDR_NUM2_STD   = REGEXP_SUBSTR(ADDR_STREET, '(?<=/)[0-9]+[a-zA-Z]?[[:space:]]*$'),
  ADDR_STREET_STD = TRIM(REGEXP_REPLACE(ADDR_STREET, '[0-9]+(/[0-9]+[a-zA-Z]?)?[[:space:]]*$', ''));

Equivalente em Python para pipeline pandas:

num  = raw.str.extract(r'(\d+)(?:/\d+\w?)?\s*$', expand=False)      # número predial
street = raw.str.replace(r'\d+(/\d+\w?)?\s*$', '', regex=True).str.strip()

Cuidado com a ordem dos números: na República Tcheca escreve-se predial/orientador, mas a convenção nem sempre é seguida. Se o parser encontrar apenas um número, armazene como predial e deixe o orientador NULL — não adivinhe.

Valor composto no campo de cidade

Praha 5 - Smíchov, České Budějovice - České Budějovice 5 — o operador escreveu toda a hierarquia de endereço (cidade + distrito + número do distrito) em um único campo. Impossibilita o pareamento com o registro.

-- primeiro segmento antes do traço = cidade, o restante = distrito
SELECT ADDR_CITY,
  TRIM(SUBSTRING_INDEX(ADDR_CITY, '-', 1))                      cidade,
  NULLIF(TRIM(SUBSTRING(ADDR_CITY, LOCATE('-',ADDR_CITY)+1)),'') distrito
FROM PARTY_ADDRESS WHERE ADDR_CITY LIKE '%-%';

Detecção de candidatos antes de começar a analisar:

SELECT ADDR_CITY, COUNT(*) cnt FROM PARTY_ADDRESS
WHERE ADDR_CITY REGEXP '[0-9]' OR ADDR_CITY LIKE '%-%' OR LENGTH(ADDR_CITY) > 50
GROUP BY 1 ORDER BY cnt DESC LIMIT 20;

Cuidado: o traço no nome da cidade às vezes é legítimo (Frýdek-Místek, Brandýs nad Labem-Stará Boleslav). Por isso, a decomposição é verificada contra o registro de cidades, não cegamente — e se após a decomposição a cidade não estiver no registro, retorne o valor original.

Caixa Postal e outras formas fora do escopo

Caixa Postal, apartado postal, "no endereço de entrega da empresa" — endereços formalmente válidos que o registro de imóveis não conhece. Não os analise à força; marque com um flag próprio e exclua da métrica de match rate (senão você reportará como erro algo que não é erro).

Nome

Espaços intercalados

D A V I D, Ž A N N A — cada letra separada por espaço. Pode ser colado deterministicamente se todas as tokens forem de uma letra, ou se a maioria for de uma letra (o restante normalmente é traço ou inicial):

toks = s.split()
if len(toks) > 1 and all(len(t) == 1 for t in toks):
    s = "".join(toks)
elif len(toks) > 1 and sum(len(t) == 1 for t in toks) >= max(2, len(toks) - 1):
    s = "".join(toks)

Nunca cole tokens genericamente — Anna Marie são dois nomes, não AnnaMarie.

Nome composto

Primeiro nome + nome do meio em um campo. Divida pelo espaço e armazene o segundo token separado; descarte tokens de uma letra (iniciais), senão você criará um nome do meio sem sentido:

UPDATE PART_PARTY
SET PARTY_FNAME_STD = <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)),
    PARTY_MNAME_STD = NULLIF(<proper>(SUBSTRING_INDEX(SUBSTRING_INDEX(PARTY_FNAME,' ',2),' ',-1)),
                             <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)))
WHERE PARTY_TYPE='P' AND LENGTH(SUBSTRING_INDEX(PARTY_FNAME,' ',2)) > 1;

Título registrado no campo de nome

Ing. Jan Novák em PARTY_NAME. Detecte pela correspondência do primeiro token com o dicionário de títulos, não pelo ponto — J. Novák é inicial, não título.

O dicionário é a tabela de títulos (REF_*_TITBEF para títulos antes do nome, TITAFT depois) e a correspondência deve ser fuzzy, porque nos dados coexistem MuDr, MUDr. e Mudr. O parser apenas separa o título; a conversão para forma canônica é trabalho do padronizador.

Erros de codificação

Duas coisas diferentes, duas soluções diferentes:

ErroExemploSolução
substituição sistemática de caractereĄUDOVÍT (deveria ser ĽUDOVÍT)substituição determinística, se o padrão for inequívoco — por exemplo, Ą no início de palavra é sempre transcrição errada, porque em polonês nunca aparece ali
letra perdidaBE0ŠOVÁ (deveria ser BENEŠOVÁ)não corrija no parser. Marque com flag _CORRUPT e deixe a recuperação para o padronizador (registro de nomes) ou deduplicador (gêmeo limpo no cluster)
s = re.sub(r"(^|(?<=\s))Ą", "Ľ", s)          # inequívoco, determinístico
corrupt = bool(re.search(r"[0-9]", s))       # dígitos no nome = corrompido, tratado em outra etapa

A divisão é intencional: o parser faz apenas o que é certo. A reconstrução de letra faltante é uma estimativa e pertence a uma etapa onde pode ser verificada contra algo.

Data armazenada como texto

Antes de analisar, verifique formato e dia/mês:

SELECT SUBSTRING(<col>,1,2) primeiro, SUBSTRING(<col>,4,2) segundo, COUNT(*)
FROM <t> GROUP BY 1,2 ORDER BY 3 DESC LIMIT 20;

Se a primeira posição nunca ultrapassa 12 e a segunda sim, a ordem é MM.DD; se ambas ultrapassam 12, o formato é misto e é necessário decidir por linha com base em outra fonte. O cego STR_TO_DATE não detecta troca silenciosa e a espalha por toda a tabela.

Saída do parser

  • Novas colunas _STD com valores atômicos, originais intactos.
  • Taxa de parsing para cada componente (% de linhas com resultado não vazio).
  • Tabela de erros com linhas que o parser não decompôs, com o motivo de cada uma — trilha de auditoria sobre por que o registro não passou. Entrada para revisão manual ou para decisão "isto está fora do escopo".

Habilidades Relacionadas