dq-engine
Framework PySpark para validacao e tratamento de qualidade de dados.
O que faz
- Resolve rule_set por nome de coluna
- Compila regras declarativas em expressoes Spark
- Gera score e status por coluna
- Aplica tratamento em lote (inclui colapso de espacos internos em texto DSC_)
- Executa pipeline antes/depois com comparativo
- Detecta avisos (informativos, nao alteram o score):
- cardinalidade IDT_ fora do padrao;
- similaridade categorica DSC_/TPO_ — mesma categoria com grafia diferente, por pontuacao/espacos (ex.: "S.A" vs "S/A");
- nulos padronizados (
N/D/0/1800-01-01) por coluna, marcando[DROP?]quando sao maioria; - sugestoes de modelagem: DSC_ com poucas categorias (renomear para TPO_), COD_ que so origina o IDT_ de mesmo nome, coluna constante (um unico valor) e valor dominante (uma categoria com 95%+ das linhas — possivel default na origem).
- Perfil das colunas no
.txt(nulos, nulos padrao e distintos aproximados), calculado na mesma agregacao do relatorio de nulos. - Registra em
dq_historico.xlsx(abas Historico com% Colunas Aprovadas, Relatorio comCausa/Acao Sugerida, e Avisos) - Suporta modo producao (valida o DataFrame ja tratado e marca situacao CORRIGIDO)
Requisitos
- Python 3.10+
- PySpark 3.4+
Instalacao
pip install -e .
Uso rapido
from pyspark.sql import SparkSession
from dq_engine import DataQualityEngine
from dq_engine.config.conventions import CONVENTIONS
spark = SparkSession.builder.getOrCreate()
engine = DataQualityEngine(spark=spark, conventions=CONVENTIONS)
df = spark.table("lakehouse.clientes")
result = engine.validate_table(df=df, table_name="CLIENTES", run_id="2026-07-02")
result.output_df.orderBy("column_name").show(truncate=False)
Dois modos de uso
| Modo | Quando usar | O que faz |
|---|---|---|
| Simulacao (padrao) | Antes de levar o tratamento para o ETL | Valida o dado bruto, aplica o tratamento, valida de novo e compara ANTES x DEPOIS. Imprime o trecho de ETL (treat_*) para copiar e colar. |
Producao (producao=True) |
Depois que o ETL real ja aplicou o data clean | Valida uma unica vez o dado ja tratado (nao re-trata) e marca a situacao como CORRIGIDO, ou PENDENTE_FONTE se sobrar falha. |
Simulacao
df_tratado, result_before, result_after = engine.run_dq_pipeline(
df=df,
table_name="CLIENTES",
output_dir="/mnt/dq_output", # opcional: sem ele nada e gravado em arquivo
)
Producao
df_validado, result, _ = engine.run_dq_pipeline(
df=df_ja_tratado,
table_name="CLIENTES",
output_dir="/mnt/dq_output",
producao=True,
ignore_columns_from_score=[], # lista de colunas a tirar do score (veja abaixo)
)
Ignorar colunas no score (ignore_columns_from_score)
Para uma particularidade ja conhecida e nao corrigivel agora (ex.: IDT_X que na verdade
deveria ser TPO_X), informe o nome exato da coluna:
df_validado, result, _ = engine.run_dq_pipeline(
df=df_ja_tratado,
table_name="CLIENTES",
output_dir="/mnt/dq_output",
producao=True,
ignore_columns_from_score=["IDT_SITUACAO", "IDT_SUBCATEGORIA"],
)
- Vale igual em simulacao e producao: a coluna continua sendo validada, mas fica fora do
quality_score/indicador da tabela (abaHistorico) e do diagnostico de falha residual (abaRelatorio). Nao e manipular score, e priorizar. - Em simulacao, a coluna de texto recebe so o tratamento padrao
treat_string(col, basico=True)(trim, maiusculas, sem acento, nulo disfarcado → N/D), no trecho de ETL e na simulacao; colunas que nao sao texto ficam como vieram ("fora do plano (lista de excecao)"). - O estado real da coluna e mostrado no console/
.txt(subsecao "Colunas monitoradas") e gravado na abaAvisosdo Excel (Tipo Aviso = COLUNA_MONITORADA) para ajuste posterior. No console, a coluna monitorada e uma entrada so (os avisos de regra, IDT_ e grafia dela entram resumidos:avisos: idt_cardinality (5 valores)); no Excel, cada aviso continua num registro proprio, para o painel contar por regra e categoria. [](ou omitir) = todas as colunas entram no score.
Via scripts/main.py: python scripts/main.py --table ... --producao --ignore-columns-producao IDT_X
(flag repetivel), ou no .env: DQ_PRODUCAO=true e DQ_IGNORE_COLUMNS_PRODUCAO=IDT_X,IDT_Y.
Como ler o output
O bloco AVISOS abre com um resumo (uma linha por coluna) em tres grupos:
PENDENTES — seguem no dado tratado, corrigir na fonte — 2 coluna(s)
NUM_CHASSI formato invalido (1); DV de chassi divergente (1)
DSC_OUTRA FALHA RESIDUAL score 0.0000 (upper)
CORRIGIDOS NO TRATAMENTO — só registro da origem, sem ação — 1 coluna(s)
DSC_PLACA nulo fora do padrao (1)
SUGESTÕES — modelagem e distribuição, não é erro no dado — 1 coluna(s)
TPO_CANAL valor dominante, conferir default na origem — linhas (980)
O grupo de cada aviso vem do catalogo: categorias com "sugestao": True em
warning_categories (NOMENCLATURA, ARMAZENAMENTO, DISTRIBUICAO) vao para SUGESTÕES.
Com output_dir, o console mostra so o resumo; o detalhe por regra (exemplos e acao)
fica no .txt salvo. Sem output_dir, o console mostra o resumo e o detalhe. O detalhe tem
dois blocos:
- PONTO DE ATENCAO — nao reduz o score. Um bloco unico, em subsecoes:
IDT_fora do padrao de dominio (mais de 2 valores distintos alem deN/D: provavel erro de prefixo, deveria serTPO_/DSC_);- regras WARNING (ex.: ano suspeito em datas, campos estritos como CPF/CNPJ/placa/chassi, encoding corrompido), sempre com exemplos dos valores que dispararam a regra;
- categorias duplicadas por grafia (
S.AxS/A); - colunas monitoradas (fora do score).
- FALHA RESIDUAL — reduz o score. Colunas que seguem com falha. Cada regra aparece
separada, com o que significa, quantas linhas, exemplos daquela regra e a acao:
- "o tratamento corrige" (nulo fora do padrao, placeholder, maiusculas...): se aparece em producao, a tabela nao foi reprocessada com o data_clean atual;
- "o tratamento NAO corrige" (texto so com numero, encoding, dominio, formato): corrigir na fonte ou rever o prefixo da coluna.
Tudo isso vai tambem para o Excel (aba Avisos), com coluna, regra, categoria, quantidade e
mensagem (incluindo exemplos).
Textos das regras (rule_labels)
Rotulo do resumo, mensagem do aviso, significado/acao da falha residual e o Tipo Aviso do
Excel vem de CONVENTIONS["rule_labels"] — uma regra nova precisa so de uma entrada la (e da
categoria em warning_categories), sem mudar engine, result ou saver. Regras que ja tratam
nulo por conta propria declaram "null_guard": False no rule_set.
Regras de tratamento de texto
Todo tratamento de string converte para MAIUSCULAS, remove acentos e trata nulos
(N/D), exceto:
COD_— mantem o texto como veio (sotrim): sem upper, preserva pontuacao, acentos e minusculas — codigos/chaves nao mudam. O nulo deCOD_e sempre"0"(nuncaN/D), inclusive emCOD_com regra propria pelo nome (COD_CHASSI,COD_UF,COD_CPF...), que mantem a validacao/avisos do campo mas devolvem"0"no nulo;- colunas com
PATH,LINKouANEXOno nome (qualquer prefixo) eDSC_comBUCKET/CAMINHO(treat_dsc_path) — nao passam para maiusculas: preservam o caminho/link e usam minusculas.
Datas (DAT_/DTH_) mantem o tipo: coluna date continua date e coluna timestamp
continua timestamp (verificacao pelo schema). So coluna texto (ou inteiro, ex.: 20240101) e
classificada pelas amostras: com hora diferente de 00:00:00 e timestamp, senao date. O
trecho de ETL sugere e explica a escolha:
"DAT_NASCIMENTO": treat_date("DAT_NASCIMENTO", tipo="date"),
"DAT_ABERTURA": treat_datetime("DAT_ABERTURA"),
# datas: schema date/timestamp mantém o tipo; texto pelas amostras (com hora = timestamp)
# DAT_ABERTURA: timestamp (schema)
# DAT_NASCIMENTO: date (schema)
Chamada direta sem tipo: treat_date e treat_datetime devolvem timestamp (compativel com
os ETLs em producao). Formatos aceitos: ISO, dd/MM/yyyy, dd/MM/yyyy HH:mm[:ss] e yyyyMMdd;
o parse so e tentado no formato que casa com o texto, entao funciona tambem com
spark.sql.ansi.enabled=true. Nulo/invalido vira 1800-01-01 00:00:00, assim como datas com ano abaixo de
suspicious_year_min (default 1900, ex.: 0001-01-01). Datas futuras sao mantidas e so geram
aviso (ano suspeito). Tabelas REP_/REF_ (colunas date) nao tem tratamento especifico no
dq-engine.
treat_string (NOM_, TPO_, END_...): trim, maiusculas, sem acento, espacos colapsados e
nulo disfarcado (VAZIO, N/A, TESTE, 0, vazio, texto sem letra/numero) -> N/D. Hifen
entre letras vira espaco (ANA-MARIA -> ANA MARIA). Ponto e os demais caracteres especiais
ficam (SIMONE.SILVA, M&M). Para tirar, use
treat_string("X", limpar_especiais=True): simbolo vira espaco (SIMONE SILVA, M M).
999, 9999 e -9999 nunca viram N/D.
DSC_ (texto livre ou categorica, de baixa cardinalidade) e STR_ usam a mesma funcao,
treat_dsc. DSC_ capturadas por nome (ex.: DSC_PLACA) mantem o tratamento especifico mesmo
com baixa cardinalidade.
- Simbolos em qualquer posicao:
treat_dscmantem letras, numeros, espaco e a pontuacao. , ; : - / ( ) [ ] ' " ? ! % & + = @ _; o resto (* # | ~ ^ $ < >, emojis...) e removido tambem no meio do texto ("EVENTO** RELATO"->EVENTO RELATO). - Ruido nas bordas: simbolos soltos no inicio/fim saem (
"SUZANO *"->SUZANO,"SUZANO ."->SUZANO), mas(/[no inicio e)/]/%no fim ficam ("... (GASOLINA / FLEX)"mantem o)final). - Placeholders:
X,TESTE, sequencia de zeros (0,000000) e o mesmo digito repetido 3+ vezes (111,11111111111111111111111) viramN/D.1,22,208continuam valor. - Ponto final:
.colado em letra/numero fica (abreviacao:16V AUT.); ponto solto (SUZANO .) ou repetido (AUT..) sai. - Localidade (
CIDADE,MUNICIPIO,ESTADO,BAIRRO,PAIS,LOCALIDADE,DISTRITO,REGIAOcomo palavra inteira do nome —DSC_ESTADOsim,DSC_PRESTADORnao — viatreat_dscoutreat_string): limpeza estrita, so letras, numeros, espaco, hifen e apostrofo (UBERABA.->UBERABA,Embu-Guaçu->EMBU-GUACU,Santa Bárbara D'Oeste->SANTA BARBARA D'OESTE). UF usatreat_uf(so sigla). Cidade com UF junto (VILA VELHA, ES,UBERABA - MG,CAMPINAS (SP)) gera avisocidade_com_ufsugerindo separar em duas colunas (cidade e UF).VAL_/QTD_/NUM_/COD_comCIDADEno nome (ex.:VAL_CUSTO_CIDADE_MENSAL) seguem a regra do prefixo. - Texto so com numero: em DSC aberta (alta cardinalidade) e so aviso, nao reduz o score; em
DSC categorica (baixa cardinalidade) continua erro, porque numero no meio das categorias
costuma ser codigo no lugar da descricao. Colunas com
MODELOno nome nao usam a regra (208,3008,500sao modelos reais).
Funcoes treat_* do trecho de ETL sao sempre as mesmas desde a 0.7.2 — ajustes novos mudam o
comportamento dessas funcoes, sem criar nomes novos. Para os ajustes valerem nos ETLs que ja
estao em producao (que chamam a funcao gravada no trecho da epoca), cada treat_* aplica a
regra da familia pelo nome da coluna:
| Chamada no ETL | Coluna | Regra aplicada |
|---|---|---|
treat_string / treat_dsc / treat_dsc_path |
COD_* (ex.: COD_APOLICE, COD_DOCUMENTO, COD_LINK) |
treat_cod (texto mantido, nulo "0") |
treat_string / treat_dsc |
nome com PATH/LINK |
treat_dsc_path |
treat_string |
DSC_* / STR_* |
treat_dsc |
treat_float |
NUM_* / QTD_* |
treat_int |
COD_ com campo estrito (COD_CHASSI, COD_UF, COD_CPF...) continua com a validacao do campo,
mas o nulo e "0".
Tipos numericos
| Prefixo | Tipo | Tratamento |
|---|---|---|
VAL_, PERC_ |
double | treat_float (treat_string em VAL_/PERC_ tambem vira double) — double (64 bits) mantem as casas decimais: 1338.46 continua 1338.46 (em float de 32 bits virava 1338.4599609375) |
NUM_, QTD_ |
int | treat_int (decimais sao truncados; coluna com casas decimais deve ser VAL_) |
Excecoes pelo nome continuam com a regra propria: NUM_APOLICE/NUM_DOCUMENTO (texto),
NUM_TELEFONE, NUM_CHASSI, NUM_CPF.../NUM_CNPJ.... Atencao: int vai ate
2.147.483.647 — colunas NUM_ com mais digitos (protocolos, notas fiscais) estouram.
QTD_, VAL_ e PERC_ seguem sempre o prefixo, mesmo com UF/CPF/PLACA no nome
(QTD_ORDEM_UF_MES e int, nao UF). A sigla UF so casa como palavra inteira do nome (COD_UF,
UF_ORIGEM).
Sentinelas numericos (9999, -999, -9999 — numeric_sentinel_values — e 999 so em
VAL_), comuns em fill_na de ETLs de score (VAL_SCORE_* = 999, VAL_FRANQUIA = -9999.0),
geram aviso numeric_sentinel (categoria NULO, nao afeta o score). O valor nao e trocado
no tratamento; o ajuste e no fill_na do ETL (nulo numerico padrao 0).
999 e 998 sao valores reais, nunca nulo: COD_SEGURADORA 999 = loja propria / TODAS
CIAS, 998 = RLP/RPS, NUM_SEQUENCIA 999 = loja propria. treat_cod/treat_int mantem o
valor e a validacao nao acusa nulo fora do padrao.
Campos estritos: so aviso, valor invalido mantido
CPF, CNPJ, CEP, UF, telefone, placa e chassi (resolvidos pelo nome da coluna) nao afetam o
score — todas as regras sao WARNING e aparecem na aba Avisos. No tratamento:
- nulo disfarcado (
VAZIO,0,TESTE,000.000.000-00, vazio) ->N/D; - valor valido -> forma padronizada (sem mascara; telefone como
(XX) XXXXX-XXXX); - valor invalido -> mantido como veio, com aviso "valor mantido; corrigir na origem" (ex.: CNPJ incompleto continua incompleto).
Avisos especificos, alem de formato/nulo:
| Aviso | Quando |
|---|---|
cpf_check_digit / cnpj_check_digit |
digito verificador invalido ou digitos repetidos |
chassi_check_digit |
chassi com formato valido mas digito verificador (ISO 3779) divergente |
telefone_ddd |
DDD inexistente na Anatel |
placa_padrao_antigo |
placa ABC1234 — nao e convertida para Mercosul |
domain (UF) |
nome por extenso ou codigo IBGE — nao e convertido para sigla |
No modo simulacao, problemas que o tratamento corrigiu (ex.: nulo disfarcado -> N/D) tambem
continuam na aba Avisos, com a indicacao "trocado(s) por N/D no tratamento".
Colunas _NORMATIZADO (PKs: chassi, placa, CPF, CNPJ)
Para colunas de chassi, placa, CPF e CNPJ (normatizado_rule_sets), o trecho de ETL ganha um
bloco extra que cria <COLUNA>_NORMATIZADO. Nessa coluna, valor valido vira a forma padronizada
e valor invalido ou nulo vira N/D ("0" em COD_). A coluna original segue o tratamento
normal (invalido mantido + aviso):
df = df.withColumns({
"NUM_CHASSI_NORMATIZADO": treat_chassi("NUM_CHASSI", normatizado=True),
"NUM_CPF_NORMATIZADO": treat_cpf("NUM_CPF", normatizado=True),
})
Valor em NUM_CPF |
NUM_CPF (tratado) |
NUM_CPF_NORMATIZADO |
|---|---|---|
529.982.247-25 |
52998224725 |
52998224725 |
123.456.789-00 (DV errado) |
123.456.789-00 |
N/D |
000.000.000-00 |
N/D |
N/D |
normatizado=True e um parametro das mesmas treat_* (sem nome novo). NUM_APOLICE nao tem
validacao de formato (e texto) e por isso nao ganha coluna _NORMATIZADO.
Onde os arquivos sao salvos
Quando output_dir e informado, o DqOutputSaver grava:
{output_dir}/
├── table_output/
│ └── {TABELA}_{AAAAMMDD_HHMMSS}.txt # relatorio detalhado de cada execucao
└── output_consolidado/
└── dq_historico_v2.xlsx # historico acumulado (nome via historico_filename)
O Excel e gravado num arquivo temporario e trocado de uma vez (queda no meio nao corrompe o
historico), com a trava dq_historico_v2.xlsx.lock: execucoes em paralelo esperam a vez (ate
2 min; trava com mais de 10 min e considerada abandonada). Rodando localmente, use
--output-dir outputs para gravar em outputs/ (fora do git).
Abas do Excel: Historico (indicador/score por execucao), Relatorio (diagnostico por coluna)
e Avisos (todos os avisos acima).
No scripts/main.py a configuracao e:
| Item | Flag / .env |
Default |
|---|---|---|
| Pasta de saida | --output-dir / DQ_OUTPUT_DIR |
\KIRIBATI.autoglass.com.br\Departamento$\Célula de Dados e Implantação\Engenharia\Outputs_Data_Quality |
| Nome do Excel | --historico-filename / DQ_HISTORICO_FILENAME |
dq_historico_v2.xlsx |
| Tabelas ja processadas | --processed-file / DQ_PROCESSED_FILE |
config/state/processed_tables.yaml |
| Tabelas com falha | --failed-file / DQ_FAILED_FILE |
config/state/failed_tables.yaml |
Se o Excel estiver aberto, o save avisa e nao grava o registro: feche e rode de novo.
Leitura via Snowflake (opcional)
O engine sempre recebe um DataFrame Spark comum — a origem dos dados e desacoplada. Para descobrir e ler tabelas do Snowflake por schema, instale o extra snowflake:
pip install -e ".[snowflake]"
from dq_engine.sources.snowflake_source import SnowflakeReader, discover_tables
reader = SnowflakeReader() # credenciais via variaveis de ambiente (.env)
table_names = discover_tables(reader, schema_like="%NOME_DO_SCHEMA%")
df = reader.read(spark, table_names[0])
result = engine.validate_table(df=df, table_name=table_names[0], run_id="2026-07-14")
O script scripts/main.py automatiza o fluxo (descobre tabelas do schema -> roda dq-engine -> salva consolidado/historico) e pode ser agendado no Sonata:
python scripts/main.py --schema-like "%NOME_DO_SCHEMA%"
O padrao LIKE tambem pode vir do .env (variavel TABLE_SCHEMA), dispensando --schema-like em toda chamada.
Tambem e possivel rodar uma lista fixa de tabelas em vez de (ou combinada com) o schema, via --table (repetivel) ou pela variavel TABLE_LIST no .env (nomes completos DATABASE.SCHEMA.TABELA separados por virgula):
python scripts/main.py --table TRUSTED.MAX_APOLICE.TABELA_1 --table TRUSTED.MAX_APOLICE.TABELA_2
Tambem e possivel apontar um YAML com lista simples de tabelas (como config/state/to_process.yaml) usando --tables-yaml ou a variavel TABLE_LIST_FILE no .env (um caminho antigo state/... continua funcionando — e procurado tambem em config/):
python scripts/main.py --tables-yaml config/state/to_process.yaml
Sem --schema-like/TABLE_SCHEMA, o scripts/main.py roda so as tabelas informadas (--table, TABLE_LIST ou --tables-yaml), sem descoberta via INFORMATION_SCHEMA; com os dois informados, a lista filtra o resultado da descoberta.
Tabelas processadas com sucesso ficam registradas em processed_tables.yaml (arquivo temporario, desacoplado do dq-engine) e sao puladas nas proximas execucoes; use --rerun-processed para forcar tudo de novo.
API principal
- DataQualityEngine.validate_table
- DataQualityEngine.apply_treatment
- DataQualityEngine.run_dq_pipeline
- DataQualityEngine.get_column_treatment_contexts
- DataQualityResult.table_summary
- DataQualityResult.failed_columns
Configuracao
Arquivo central de convencoes:
- src/dq_engine/config/conventions.py
Documentacao
- docs/architecture.md
- docs/rules.md
Metadata
Release files for dq-engine 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dq_engine-0.9.0.tar.gz | 105.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dq_engine-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 209.4 kB
Release files / dq_engine-0.9.0.tar.gz
| Download URL | dq_engine-0.9.0.tar.gz |
|---|---|
| Size | 105.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df940a3c2e34d7300e6974b59f917f5ac455ccb1bf29aeceb05084b175583b35
|
|
BLAKE2b-256 checksum How to use checksums |
d195b7347b1a3ed06fd30c39740967ed13bb2a3f0987c7dfde98b4019b38094b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Release files / dq_engine-0.9.0-py3-none-any.whl
| Download URL | dq_engine-0.9.0-py3-none-any.whl |
|---|---|
| Size | 103.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c9273ea66a303d5e0879c23c436a463bfd1bb1126b157c9bbd2949c515adfc52
|
|
BLAKE2b-256 checksum How to use checksums |
216fcd9f9120e7d9f56042c08853e2c4dfd643a21dab06a3b1b4e21875d0cc78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Security for dq-engine 0.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.