Pular para o conteúdo

Qualidade de dados

As regras que impedem o relatório errado — e por que elas rodam junto do pipeline, não depois.

Um pipeline que termina sem erro não prova que os dados estão certos. Ele prova que nada estourou — e a diferença entre as duas coisas é onde vive o relatório errado que ninguém contesta.

As seis perguntas#

DimensãoA perguntaComo se mede
completudefalta alguma coisa?quantos vazios por coluna
unicidadehá repetido?contagem de chaves distintas × total
validadeo valor faz sentido?faixa, formato, lista de valores aceitos
consistênciaas partes concordam?o total bate com a soma das partes?
pontualidadeo dado é de hoje?a data mais recente × agora
volumeveio a quantidade esperada?linhas de hoje × a média dos últimos dias

Escrever uma regra#

dataforge
record Regra:
    nome: String
    grave: Boolean := yes

action conferir(dados, regra, teste):
    falhas := [l cycle l in dados given not teste(l)]
    yield {"regra": regra.nome,
           "grave": regra.grave,
           "falhas": len(falhas),
           "total": len(dados),
           "exemplos": falhas[0:3]}
dataforge
resultados := [
    conferir(vendas, Regra("valor positivo"), lambda l: l["valor"] > 0.0),
    conferir(vendas, Regra("regiao conhecida"),
             lambda l: l["regiao"] in ["sul", "norte"]),
    conferir(vendas, Regra("qtd inteira", no), lambda l: l["qtd"] >= 1)
]

cycle r in resultados:
    marca := "x" given r["falhas"] > 0 otherwise " "
    out $"[{marca}] {r['regra']}: {r['falhas']} de {r['total']}"

Grave interrompe; aviso não#

Toda regra precisa de uma resposta declarada para "e se falhar?". Sem isso, ou o pipeline para por qualquer coisa, ou nunca para por nada:

dataforge
action decidir(resultados):
    graves := [r cycle r in resultados
               given r["grave"] and r["falhas"] > 0]
    given len(graves) > 0:
        trigger $"{len(graves)} regra(s) grave(s) falharam; o carregamento nao aconteceu"

    avisos := [r cycle r in resultados given r["falhas"] > 0]
    cycle a in avisos:
        out $"aviso: {a['regra']}{a['falhas']} linha(s)"
    yield yes
GravidadeO que fazer
gravenão carregar. Um dado errado publicado é pior que um relatório atrasado
avisocarregar, registrar, e olhar amanhã
informativosó o número, para acompanhar a tendência

Onde as regras rodam#

Junto do pipeline, entre transformar e carregar — não num relatório separado que alguém abre na sexta.

dataforge
action rodar(caminho, db):
    brutas := extrair(caminho)
    limpas := transformar(brutas)

    resultados := conferir_tudo(limpas)
    decidir(resultados)               // para aqui se houver grave

    yield carregar(db, limpas)

A razão é simples: a única hora em que alguém consegue agir sobre um dado ruim é antes de ele virar a fonte de um painel.

Guardar o resultado das regras#

Gravar a saída das verificações a cada execução transforma "os números pareciam estranhos" em uma série temporal:

dataforge
IO.write_json($"qualidade/{data}.json", {
    "data": data,
    "linhas": len(limpas),
    "regras": resultados
})

Com um histórico, a regra de volume deixa de precisar de número mágico: ela compara com a média dos últimos dias.

Testar as regras#

Uma regra de qualidade é código, e código sem teste dá falso negativo em silêncio — a regra que nunca acusa nada parece estar tudo bem:

dataforge
adopt Arcane.Crucible as C

crucible "regras":
    trial "valor negativo e pego":
        r := conferir([{"valor": -1.0}], Regra("positivo"),
                      lambda l: l["valor"] > 0.0)
        expect r["falhas"] is 1

    trial "valor positivo passa":
        r := conferir([{"valor": 5.0}], Regra("positivo"),
                      lambda l: l["valor"] > 0.0)
        expect r["falhas"] is 0

C.run()

Por onde seguir#