dq-engine 0.3.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dq-engine might be problematic. Click here for more details.
- dq_engine-0.3.14/PKG-INFO +307 -0
- dq_engine-0.3.14/README.md +295 -0
- dq_engine-0.3.14/pyproject.toml +35 -0
- dq_engine-0.3.14/setup.cfg +4 -0
- dq_engine-0.3.14/src/dq_engine/__init__.py +6 -0
- dq_engine-0.3.14/src/dq_engine/config/__init__.py +6 -0
- dq_engine-0.3.14/src/dq_engine/config/conventions.py +164 -0
- dq_engine-0.3.14/src/dq_engine/execution/__init__.py +25 -0
- dq_engine-0.3.14/src/dq_engine/execution/engine.py +782 -0
- dq_engine-0.3.14/src/dq_engine/execution/exceptions.py +23 -0
- dq_engine-0.3.14/src/dq_engine/execution/outputs.py +166 -0
- dq_engine-0.3.14/src/dq_engine/execution/result.py +148 -0
- dq_engine-0.3.14/src/dq_engine/execution/rule_compiler.py +245 -0
- dq_engine-0.3.14/src/dq_engine/execution/treatment_plan.py +509 -0
- dq_engine-0.3.14/src/dq_engine/execution/validator.py +28 -0
- dq_engine-0.3.14/src/dq_engine/planning/__init__.py +15 -0
- dq_engine-0.3.14/src/dq_engine/planning/models.py +52 -0
- dq_engine-0.3.14/src/dq_engine/planning/resolver.py +72 -0
- dq_engine-0.3.14/src/dq_engine/registry/__init__.py +6 -0
- dq_engine-0.3.14/src/dq_engine/registry/registry.py +82 -0
- dq_engine-0.3.14/src/dq_engine/utils/__init__.py +3 -0
- dq_engine-0.3.14/src/dq_engine/utils/logging.py +37 -0
- dq_engine-0.3.14/src/dq_engine.egg-info/PKG-INFO +307 -0
- dq_engine-0.3.14/src/dq_engine.egg-info/SOURCES.txt +25 -0
- dq_engine-0.3.14/src/dq_engine.egg-info/dependency_links.txt +1 -0
- dq_engine-0.3.14/src/dq_engine.egg-info/requires.txt +5 -0
- dq_engine-0.3.14/src/dq_engine.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dq-engine
|
|
3
|
+
Version: 0.3.14
|
|
4
|
+
Summary: Framework de validação de qualidade de dados com PySpark
|
|
5
|
+
Author-email: Bruna Cataldo <bruna.cataldo@autoglass.com.br>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pyspark>=3.4.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# dq-engine
|
|
14
|
+
|
|
15
|
+
Framework de validação de qualidade de dados com PySpark.
|
|
16
|
+
|
|
17
|
+
Valida tabelas Spark com base em convenções de nomenclatura de colunas, regras declarativas por coluna, padrões de nulos e valores inválidos — gerando relatórios detalhados de inconsistências e sugestões de tratamento automático.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Sumário
|
|
22
|
+
|
|
23
|
+
- [Objetivo](#objetivo)
|
|
24
|
+
- [Estrutura do projeto](#estrutura-do-projeto)
|
|
25
|
+
- [Arquitetura](#arquitetura)
|
|
26
|
+
- [Instalação](#instalação)
|
|
27
|
+
- [Como usar](#como-usar)
|
|
28
|
+
- [Interpretando o output](#interpretando-o-output)
|
|
29
|
+
- [Plano de tratamento](#plano-de-tratamento)
|
|
30
|
+
- [Como adicionar novas regras](#como-adicionar-novas-regras)
|
|
31
|
+
- [Como adicionar novos prefixos](#como-adicionar-novos-prefixos)
|
|
32
|
+
- [Testes](#testes)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Objetivo
|
|
37
|
+
|
|
38
|
+
O `dq-engine` valida tabelas Spark garantindo que os dados estejam de acordo com:
|
|
39
|
+
|
|
40
|
+
- **Convenções de nomenclatura de colunas** — prefixos como `COD_`, `DAT_`, `NOM_` determinam automaticamente o conjunto de regras aplicável a cada coluna
|
|
41
|
+
- **Tipos de regras por coluna** — trimming, upper case, padrões de nulo, regex, domínios enumerados
|
|
42
|
+
- **Convenções de valores nulos** — representações inválidas de nulo são detectadas e sinalizadas por tipo de coluna
|
|
43
|
+
- **Valores de teste** — detecta `TESTE`, `FAKE`, `***` e similares em ambiente de produção
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Estrutura do projeto
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
data-quality-local/
|
|
51
|
+
├── pyproject.toml
|
|
52
|
+
├── README.md
|
|
53
|
+
│
|
|
54
|
+
├── docs/
|
|
55
|
+
│ ├── architecture.md # Arquitetura, fluxo e decisões de design
|
|
56
|
+
│ └── rules.md # Regras de validação e rule sets
|
|
57
|
+
│
|
|
58
|
+
├── src/
|
|
59
|
+
│ └── dq_engine/
|
|
60
|
+
│ ├── __init__.py # Exportações públicas do pacote
|
|
61
|
+
│ │
|
|
62
|
+
│ ├── config/
|
|
63
|
+
│ │ ├── __init__.py
|
|
64
|
+
│ │ └── conventions.py # Fonte única de verdade: nulos, regras, prefixos, domínios
|
|
65
|
+
│ │
|
|
66
|
+
│ ├── planning/
|
|
67
|
+
│ │ ├── __init__.py
|
|
68
|
+
│ │ ├── models.py # Dataclasses: ValidationTarget, CompiledRule, ColumnTreatmentSpec
|
|
69
|
+
│ │ └── resolver.py # Resolução coluna → rule_set via prefix_mapping
|
|
70
|
+
│ │
|
|
71
|
+
│ ├── registry/
|
|
72
|
+
│ │ ├── __init__.py
|
|
73
|
+
│ │ └── registry.py # RuleRegistry: registro e lookup de builders de regras
|
|
74
|
+
│ │
|
|
75
|
+
│ ├── execution/
|
|
76
|
+
│ │ ├── __init__.py
|
|
77
|
+
│ │ ├── engine.py # DataQualityEngine: orquestração completa
|
|
78
|
+
│ │ ├── rule_compiler.py # SparkRuleCompiler: compila regras em Column expressions
|
|
79
|
+
│ │ ├── treatment_plan.py# Geração de código Spark de tratamento sugerido
|
|
80
|
+
│ │ ├── outputs.py # Builders do DataFrame de resultado
|
|
81
|
+
│ │ ├── result.py # DataQualityResult: contrato de saída
|
|
82
|
+
│ │ ├── validator.py # GenericValidator: validação pontual por coluna
|
|
83
|
+
│ │ └── exceptions.py # Hierarquia de exceções do framework
|
|
84
|
+
│ │
|
|
85
|
+
│ └── utils/
|
|
86
|
+
│ ├── __init__.py
|
|
87
|
+
│ └── logging.py # Logging estruturado
|
|
88
|
+
│
|
|
89
|
+
└── tests/
|
|
90
|
+
├── __init__.py
|
|
91
|
+
├── test_resolver.py # Testes do módulo planning/resolver
|
|
92
|
+
├── test_rule_compiler.py # Testes de cada builder de regra
|
|
93
|
+
├── test_engine.py # Testes de integração do engine
|
|
94
|
+
└── test_treatment_plan.py # Testes do gerador de código de tratamento
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Arquitetura
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
DataFrame Spark
|
|
103
|
+
│
|
|
104
|
+
▼
|
|
105
|
+
planning/resolver.py → identifica colunas elegíveis por prefixo
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
execution/rule_compiler.py → compila regras declarativas em Column expressions
|
|
109
|
+
│
|
|
110
|
+
▼
|
|
111
|
+
execution/engine.py → projeta flags de validação (1 select), agrega contagens (1 agg)
|
|
112
|
+
│
|
|
113
|
+
▼
|
|
114
|
+
execution/outputs.py → gera DataFrame de resultado por coluna
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
DataQualityResult.output_df → 1 linha por coluna validada
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Consulte [`docs/architecture.md`](docs/architecture.md) para detalhes de cada componente e decisões de design.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Instalação
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Dependências:
|
|
131
|
+
|
|
132
|
+
- Python >= 3.10
|
|
133
|
+
- PySpark >= 3.4.0
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Como usar
|
|
138
|
+
|
|
139
|
+
### Validação de uma tabela
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from pyspark.sql import SparkSession
|
|
143
|
+
from dq_engine import DataQualityEngine, CONVENTIONS
|
|
144
|
+
|
|
145
|
+
spark = SparkSession.builder.getOrCreate()
|
|
146
|
+
|
|
147
|
+
df = spark.table("meu_lakehouse.tabela_clientes")
|
|
148
|
+
|
|
149
|
+
engine = DataQualityEngine(spark=spark, conventions=CONVENTIONS)
|
|
150
|
+
|
|
151
|
+
result = engine.validate_table(
|
|
152
|
+
df=df,
|
|
153
|
+
table_name="tabela_clientes",
|
|
154
|
+
run_id="2024-01-15",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
result.output_df.show(truncate=False)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Exemplo de output
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
164
|
+
| table_name | column_name | invalid_value |total_rows|valid_rows|invalid_rows|quality_score| status |
|
|
165
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
166
|
+
| tabela_clientes | NOM_CLIENTE | null | TESTE | 1000 | 985 | 15 | 0.985 | FAILED |
|
|
167
|
+
| tabela_clientes | COD_PRODUTO | | 1000 | 1000 | 0 | 1.0 | PASSED |
|
|
168
|
+
| tabela_clientes | DAT_NASCIMENTO | 0000-00-00 | 1000 | 998 | 2 | 0.998 | FAILED |
|
|
169
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Interpretando o output
|
|
175
|
+
|
|
176
|
+
| Coluna | Tipo | Descrição |
|
|
177
|
+
|--------|------|-----------|
|
|
178
|
+
| `table_name` | string | Nome da tabela validada |
|
|
179
|
+
| `column_name` | string | Coluna com inconsistência |
|
|
180
|
+
| `invalid_value` | string | Amostra dos valores inválidos encontrados (até 50 distintos, separados por `\|`) |
|
|
181
|
+
| `total_rows` | long | Total de linhas da tabela |
|
|
182
|
+
| `valid_rows` | long | Linhas que passaram em todas as regras da coluna |
|
|
183
|
+
| `invalid_rows` | long | Linhas que falharam em pelo menos uma regra (deduplificado) |
|
|
184
|
+
| `quality_score` | double | `valid_rows / total_rows` — entre 0.0 e 1.0 |
|
|
185
|
+
| `status` | string | `PASSED` se `invalid_rows == 0`, caso contrário `FAILED` |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Plano de tratamento
|
|
190
|
+
|
|
191
|
+
Além da validação, o engine gera automaticamente o **código Spark de tratamento** sugerido para corrigir as inconsistências encontradas.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Opção 1: calcular contexto e gerar código em uma chamada
|
|
195
|
+
treatment_code = engine.render_treatment_code_from_df(
|
|
196
|
+
df=df,
|
|
197
|
+
df_name="df_clientes",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Opção 2: calcular contexto separadamente (evita reprocessamento)
|
|
201
|
+
contexts = engine.get_column_treatment_contexts(df)
|
|
202
|
+
treatment_code = engine.render_treatment_code(df, contexts, df_name="df_clientes")
|
|
203
|
+
|
|
204
|
+
print(treatment_code)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Saída de exemplo:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
df_clientes = df_clientes.select(
|
|
211
|
+
when(col("NOM_CLIENTE").isNull() | upper(trim(col("NOM_CLIENTE").cast("string"))).isin("NULL", "TESTE"),
|
|
212
|
+
lit("N/D")).otherwise(upper(trim(col("NOM_CLIENTE").cast("string")))).alias("NOM_CLIENTE"),
|
|
213
|
+
coalesce(col("COD_PRODUTO").cast("string"), lit("0")).alias("COD_PRODUTO"),
|
|
214
|
+
when(upper(col("DAT_NASCIMENTO").cast("string")).isin("0000-00-00"),
|
|
215
|
+
lit("1800-01-01 00:00:00").cast("timestamp")).otherwise(col("DAT_NASCIMENTO").cast("timestamp")).alias("DAT_NASCIMENTO"),
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
O código gerado pode ser copiado diretamente para o notebook de tratamento.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Como adicionar novas regras
|
|
224
|
+
|
|
225
|
+
### 1. Criar o builder em `src/dq_engine/execution/rule_compiler.py`
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
def _build_minha_regra(column_name: str, rule: dict, conventions: dict) -> CompiledRule:
|
|
229
|
+
condition = col(column_name).isNull() # sua condição de falha
|
|
230
|
+
|
|
231
|
+
return CompiledRule(
|
|
232
|
+
rule_type="minha_regra",
|
|
233
|
+
error_code="MINHA_REGRA_INVALIDA",
|
|
234
|
+
error_message="Descrição do erro",
|
|
235
|
+
condition=condition,
|
|
236
|
+
suggested_treatment_category="MANUAL_REVIEW",
|
|
237
|
+
suggested_treatment_expression=None,
|
|
238
|
+
manual_review_required=True,
|
|
239
|
+
treatment_note="Orientação de correção.",
|
|
240
|
+
)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2. Registrar no `SparkRuleCompiler._register_default_rules()`
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
def _register_default_rules(self) -> None:
|
|
247
|
+
builders = {
|
|
248
|
+
# ... builders existentes ...
|
|
249
|
+
"minha_regra": _build_minha_regra,
|
|
250
|
+
}
|
|
251
|
+
for rule_type, builder in builders.items():
|
|
252
|
+
self.registry.register(rule_type, builder)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 3. Referenciar em um rule_set em `src/dq_engine/config/conventions.py`
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
"rule_sets": {
|
|
259
|
+
"meu_rule_set": [
|
|
260
|
+
{"type": "string_null_pattern"},
|
|
261
|
+
{"type": "minha_regra"},
|
|
262
|
+
],
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 4. Documentar em `docs/rules.md`
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Como adicionar novos prefixos
|
|
271
|
+
|
|
272
|
+
Em `src/dq_engine/config/conventions.py`, adicione uma entrada em `prefix_mapping`:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
"prefix_mapping": {
|
|
276
|
+
# ... prefixos existentes ...
|
|
277
|
+
"EMAIL": "email_field",
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
E defina o rule_set correspondente em `rule_sets`:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
"email_field": [
|
|
285
|
+
{"type": "string_null_pattern"},
|
|
286
|
+
{"type": "regex", "pattern_ref": "email"},
|
|
287
|
+
],
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Prefixos mais longos têm precedência automaticamente — nenhuma configuração extra necessária.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Testes
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
pytest tests/ -v
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Os testes cobrem:
|
|
301
|
+
|
|
302
|
+
| Arquivo | Cobertura |
|
|
303
|
+
|---------|-----------|
|
|
304
|
+
| `tests/test_resolver.py` | Resolução de prefixos, precedência, colunas sem match |
|
|
305
|
+
| `tests/test_rule_compiler.py` | Cada builder de regra individualmente |
|
|
306
|
+
| `tests/test_engine.py` | Fluxo completo de validação, quality_score, status |
|
|
307
|
+
| `tests/test_treatment_plan.py` | Geração de expressões e código de tratamento |
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# dq-engine
|
|
2
|
+
|
|
3
|
+
Framework de validação de qualidade de dados com PySpark.
|
|
4
|
+
|
|
5
|
+
Valida tabelas Spark com base em convenções de nomenclatura de colunas, regras declarativas por coluna, padrões de nulos e valores inválidos — gerando relatórios detalhados de inconsistências e sugestões de tratamento automático.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Sumário
|
|
10
|
+
|
|
11
|
+
- [Objetivo](#objetivo)
|
|
12
|
+
- [Estrutura do projeto](#estrutura-do-projeto)
|
|
13
|
+
- [Arquitetura](#arquitetura)
|
|
14
|
+
- [Instalação](#instalação)
|
|
15
|
+
- [Como usar](#como-usar)
|
|
16
|
+
- [Interpretando o output](#interpretando-o-output)
|
|
17
|
+
- [Plano de tratamento](#plano-de-tratamento)
|
|
18
|
+
- [Como adicionar novas regras](#como-adicionar-novas-regras)
|
|
19
|
+
- [Como adicionar novos prefixos](#como-adicionar-novos-prefixos)
|
|
20
|
+
- [Testes](#testes)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Objetivo
|
|
25
|
+
|
|
26
|
+
O `dq-engine` valida tabelas Spark garantindo que os dados estejam de acordo com:
|
|
27
|
+
|
|
28
|
+
- **Convenções de nomenclatura de colunas** — prefixos como `COD_`, `DAT_`, `NOM_` determinam automaticamente o conjunto de regras aplicável a cada coluna
|
|
29
|
+
- **Tipos de regras por coluna** — trimming, upper case, padrões de nulo, regex, domínios enumerados
|
|
30
|
+
- **Convenções de valores nulos** — representações inválidas de nulo são detectadas e sinalizadas por tipo de coluna
|
|
31
|
+
- **Valores de teste** — detecta `TESTE`, `FAKE`, `***` e similares em ambiente de produção
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Estrutura do projeto
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
data-quality-local/
|
|
39
|
+
├── pyproject.toml
|
|
40
|
+
├── README.md
|
|
41
|
+
│
|
|
42
|
+
├── docs/
|
|
43
|
+
│ ├── architecture.md # Arquitetura, fluxo e decisões de design
|
|
44
|
+
│ └── rules.md # Regras de validação e rule sets
|
|
45
|
+
│
|
|
46
|
+
├── src/
|
|
47
|
+
│ └── dq_engine/
|
|
48
|
+
│ ├── __init__.py # Exportações públicas do pacote
|
|
49
|
+
│ │
|
|
50
|
+
│ ├── config/
|
|
51
|
+
│ │ ├── __init__.py
|
|
52
|
+
│ │ └── conventions.py # Fonte única de verdade: nulos, regras, prefixos, domínios
|
|
53
|
+
│ │
|
|
54
|
+
│ ├── planning/
|
|
55
|
+
│ │ ├── __init__.py
|
|
56
|
+
│ │ ├── models.py # Dataclasses: ValidationTarget, CompiledRule, ColumnTreatmentSpec
|
|
57
|
+
│ │ └── resolver.py # Resolução coluna → rule_set via prefix_mapping
|
|
58
|
+
│ │
|
|
59
|
+
│ ├── registry/
|
|
60
|
+
│ │ ├── __init__.py
|
|
61
|
+
│ │ └── registry.py # RuleRegistry: registro e lookup de builders de regras
|
|
62
|
+
│ │
|
|
63
|
+
│ ├── execution/
|
|
64
|
+
│ │ ├── __init__.py
|
|
65
|
+
│ │ ├── engine.py # DataQualityEngine: orquestração completa
|
|
66
|
+
│ │ ├── rule_compiler.py # SparkRuleCompiler: compila regras em Column expressions
|
|
67
|
+
│ │ ├── treatment_plan.py# Geração de código Spark de tratamento sugerido
|
|
68
|
+
│ │ ├── outputs.py # Builders do DataFrame de resultado
|
|
69
|
+
│ │ ├── result.py # DataQualityResult: contrato de saída
|
|
70
|
+
│ │ ├── validator.py # GenericValidator: validação pontual por coluna
|
|
71
|
+
│ │ └── exceptions.py # Hierarquia de exceções do framework
|
|
72
|
+
│ │
|
|
73
|
+
│ └── utils/
|
|
74
|
+
│ ├── __init__.py
|
|
75
|
+
│ └── logging.py # Logging estruturado
|
|
76
|
+
│
|
|
77
|
+
└── tests/
|
|
78
|
+
├── __init__.py
|
|
79
|
+
├── test_resolver.py # Testes do módulo planning/resolver
|
|
80
|
+
├── test_rule_compiler.py # Testes de cada builder de regra
|
|
81
|
+
├── test_engine.py # Testes de integração do engine
|
|
82
|
+
└── test_treatment_plan.py # Testes do gerador de código de tratamento
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Arquitetura
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
DataFrame Spark
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
planning/resolver.py → identifica colunas elegíveis por prefixo
|
|
94
|
+
│
|
|
95
|
+
▼
|
|
96
|
+
execution/rule_compiler.py → compila regras declarativas em Column expressions
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
execution/engine.py → projeta flags de validação (1 select), agrega contagens (1 agg)
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
execution/outputs.py → gera DataFrame de resultado por coluna
|
|
103
|
+
│
|
|
104
|
+
▼
|
|
105
|
+
DataQualityResult.output_df → 1 linha por coluna validada
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Consulte [`docs/architecture.md`](docs/architecture.md) para detalhes de cada componente e decisões de design.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Instalação
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install -e .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Dependências:
|
|
119
|
+
|
|
120
|
+
- Python >= 3.10
|
|
121
|
+
- PySpark >= 3.4.0
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Como usar
|
|
126
|
+
|
|
127
|
+
### Validação de uma tabela
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from pyspark.sql import SparkSession
|
|
131
|
+
from dq_engine import DataQualityEngine, CONVENTIONS
|
|
132
|
+
|
|
133
|
+
spark = SparkSession.builder.getOrCreate()
|
|
134
|
+
|
|
135
|
+
df = spark.table("meu_lakehouse.tabela_clientes")
|
|
136
|
+
|
|
137
|
+
engine = DataQualityEngine(spark=spark, conventions=CONVENTIONS)
|
|
138
|
+
|
|
139
|
+
result = engine.validate_table(
|
|
140
|
+
df=df,
|
|
141
|
+
table_name="tabela_clientes",
|
|
142
|
+
run_id="2024-01-15",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
result.output_df.show(truncate=False)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Exemplo de output
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
152
|
+
| table_name | column_name | invalid_value |total_rows|valid_rows|invalid_rows|quality_score| status |
|
|
153
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
154
|
+
| tabela_clientes | NOM_CLIENTE | null | TESTE | 1000 | 985 | 15 | 0.985 | FAILED |
|
|
155
|
+
| tabela_clientes | COD_PRODUTO | | 1000 | 1000 | 0 | 1.0 | PASSED |
|
|
156
|
+
| tabela_clientes | DAT_NASCIMENTO | 0000-00-00 | 1000 | 998 | 2 | 0.998 | FAILED |
|
|
157
|
+
+------------------+------------------+-------------------+----------+----------+------------+-------------+--------+
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Interpretando o output
|
|
163
|
+
|
|
164
|
+
| Coluna | Tipo | Descrição |
|
|
165
|
+
|--------|------|-----------|
|
|
166
|
+
| `table_name` | string | Nome da tabela validada |
|
|
167
|
+
| `column_name` | string | Coluna com inconsistência |
|
|
168
|
+
| `invalid_value` | string | Amostra dos valores inválidos encontrados (até 50 distintos, separados por `\|`) |
|
|
169
|
+
| `total_rows` | long | Total de linhas da tabela |
|
|
170
|
+
| `valid_rows` | long | Linhas que passaram em todas as regras da coluna |
|
|
171
|
+
| `invalid_rows` | long | Linhas que falharam em pelo menos uma regra (deduplificado) |
|
|
172
|
+
| `quality_score` | double | `valid_rows / total_rows` — entre 0.0 e 1.0 |
|
|
173
|
+
| `status` | string | `PASSED` se `invalid_rows == 0`, caso contrário `FAILED` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Plano de tratamento
|
|
178
|
+
|
|
179
|
+
Além da validação, o engine gera automaticamente o **código Spark de tratamento** sugerido para corrigir as inconsistências encontradas.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# Opção 1: calcular contexto e gerar código em uma chamada
|
|
183
|
+
treatment_code = engine.render_treatment_code_from_df(
|
|
184
|
+
df=df,
|
|
185
|
+
df_name="df_clientes",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Opção 2: calcular contexto separadamente (evita reprocessamento)
|
|
189
|
+
contexts = engine.get_column_treatment_contexts(df)
|
|
190
|
+
treatment_code = engine.render_treatment_code(df, contexts, df_name="df_clientes")
|
|
191
|
+
|
|
192
|
+
print(treatment_code)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Saída de exemplo:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
df_clientes = df_clientes.select(
|
|
199
|
+
when(col("NOM_CLIENTE").isNull() | upper(trim(col("NOM_CLIENTE").cast("string"))).isin("NULL", "TESTE"),
|
|
200
|
+
lit("N/D")).otherwise(upper(trim(col("NOM_CLIENTE").cast("string")))).alias("NOM_CLIENTE"),
|
|
201
|
+
coalesce(col("COD_PRODUTO").cast("string"), lit("0")).alias("COD_PRODUTO"),
|
|
202
|
+
when(upper(col("DAT_NASCIMENTO").cast("string")).isin("0000-00-00"),
|
|
203
|
+
lit("1800-01-01 00:00:00").cast("timestamp")).otherwise(col("DAT_NASCIMENTO").cast("timestamp")).alias("DAT_NASCIMENTO"),
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
O código gerado pode ser copiado diretamente para o notebook de tratamento.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Como adicionar novas regras
|
|
212
|
+
|
|
213
|
+
### 1. Criar o builder em `src/dq_engine/execution/rule_compiler.py`
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
def _build_minha_regra(column_name: str, rule: dict, conventions: dict) -> CompiledRule:
|
|
217
|
+
condition = col(column_name).isNull() # sua condição de falha
|
|
218
|
+
|
|
219
|
+
return CompiledRule(
|
|
220
|
+
rule_type="minha_regra",
|
|
221
|
+
error_code="MINHA_REGRA_INVALIDA",
|
|
222
|
+
error_message="Descrição do erro",
|
|
223
|
+
condition=condition,
|
|
224
|
+
suggested_treatment_category="MANUAL_REVIEW",
|
|
225
|
+
suggested_treatment_expression=None,
|
|
226
|
+
manual_review_required=True,
|
|
227
|
+
treatment_note="Orientação de correção.",
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 2. Registrar no `SparkRuleCompiler._register_default_rules()`
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
def _register_default_rules(self) -> None:
|
|
235
|
+
builders = {
|
|
236
|
+
# ... builders existentes ...
|
|
237
|
+
"minha_regra": _build_minha_regra,
|
|
238
|
+
}
|
|
239
|
+
for rule_type, builder in builders.items():
|
|
240
|
+
self.registry.register(rule_type, builder)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 3. Referenciar em um rule_set em `src/dq_engine/config/conventions.py`
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
"rule_sets": {
|
|
247
|
+
"meu_rule_set": [
|
|
248
|
+
{"type": "string_null_pattern"},
|
|
249
|
+
{"type": "minha_regra"},
|
|
250
|
+
],
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 4. Documentar em `docs/rules.md`
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Como adicionar novos prefixos
|
|
259
|
+
|
|
260
|
+
Em `src/dq_engine/config/conventions.py`, adicione uma entrada em `prefix_mapping`:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
"prefix_mapping": {
|
|
264
|
+
# ... prefixos existentes ...
|
|
265
|
+
"EMAIL": "email_field",
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
E defina o rule_set correspondente em `rule_sets`:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
"email_field": [
|
|
273
|
+
{"type": "string_null_pattern"},
|
|
274
|
+
{"type": "regex", "pattern_ref": "email"},
|
|
275
|
+
],
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Prefixos mais longos têm precedência automaticamente — nenhuma configuração extra necessária.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Testes
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
pytest tests/ -v
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Os testes cobrem:
|
|
289
|
+
|
|
290
|
+
| Arquivo | Cobertura |
|
|
291
|
+
|---------|-----------|
|
|
292
|
+
| `tests/test_resolver.py` | Resolução de prefixos, precedência, colunas sem match |
|
|
293
|
+
| `tests/test_rule_compiler.py` | Cada builder de regra individualmente |
|
|
294
|
+
| `tests/test_engine.py` | Fluxo completo de validação, quality_score, status |
|
|
295
|
+
| `tests/test_treatment_plan.py` | Geração de expressões e código de tratamento |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Arquivo : pyproject.toml
|
|
2
|
+
# Caminho : data-quality-local/pyproject.toml
|
|
3
|
+
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["setuptools>=68", "wheel"]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "dq-engine"
|
|
10
|
+
version = "0.3.14"
|
|
11
|
+
description = "Framework de validação de qualidade de dados com PySpark"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Bruna Cataldo", email = "bruna.cataldo@autoglass.com.br" }
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"pyspark>=3.4.0"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.0.0",
|
|
24
|
+
"pytest-mock>=3.12.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = {"" = "src"}
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
pythonpath = ["src"]
|
|
35
|
+
testpaths = ["tests"]
|