databricks-dq-framework 1.0.2__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.
- databricks_dq_framework-1.0.2/PKG-INFO +525 -0
- databricks_dq_framework-1.0.2/README.md +505 -0
- databricks_dq_framework-1.0.2/databricks_dq_framework.egg-info/PKG-INFO +525 -0
- databricks_dq_framework-1.0.2/databricks_dq_framework.egg-info/SOURCES.txt +23 -0
- databricks_dq_framework-1.0.2/databricks_dq_framework.egg-info/dependency_links.txt +1 -0
- databricks_dq_framework-1.0.2/databricks_dq_framework.egg-info/requires.txt +10 -0
- databricks_dq_framework-1.0.2/databricks_dq_framework.egg-info/top_level.txt +1 -0
- databricks_dq_framework-1.0.2/dq_framework/__init__.py +23 -0
- databricks_dq_framework-1.0.2/dq_framework/agents/__init__.py +42 -0
- databricks_dq_framework-1.0.2/dq_framework/agents/context.py +224 -0
- databricks_dq_framework-1.0.2/dq_framework/agents/suggest.py +330 -0
- databricks_dq_framework-1.0.2/dq_framework/agents/tools.py +598 -0
- databricks_dq_framework-1.0.2/dq_framework/config.py +703 -0
- databricks_dq_framework-1.0.2/dq_framework/ddl_framework_tables.py +386 -0
- databricks_dq_framework-1.0.2/dq_framework/engine/__init__.py +12 -0
- databricks_dq_framework-1.0.2/dq_framework/engine/data_assessment_rules.py +726 -0
- databricks_dq_framework-1.0.2/dq_framework/engine/generate_rule_functions.py +570 -0
- databricks_dq_framework-1.0.2/dq_framework/engine/pattern_checks.py +769 -0
- databricks_dq_framework-1.0.2/dq_framework/engine/resolve_pattern_rules.py +250 -0
- databricks_dq_framework-1.0.2/dq_framework/framework.py +420 -0
- databricks_dq_framework-1.0.2/dq_framework/reporting/__init__.py +3 -0
- databricks_dq_framework-1.0.2/dq_framework/reporting/views.py +250 -0
- databricks_dq_framework-1.0.2/dq_framework/seed_master_data.py +552 -0
- databricks_dq_framework-1.0.2/pyproject.toml +34 -0
- databricks_dq_framework-1.0.2/setup.cfg +4 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: databricks-dq-framework
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Metadata-driven Data Quality Assessment Framework for Databricks / Delta Lake
|
|
5
|
+
Author: Nitin Mathew George
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: databricks,delta-lake,data-quality,pyspark,dq-framework
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: python-dateutil>=2.8
|
|
13
|
+
Requires-Dist: pandas>=2.0
|
|
14
|
+
Provides-Extra: standalone
|
|
15
|
+
Requires-Dist: pyspark>=3.3; extra == "standalone"
|
|
16
|
+
Requires-Dist: delta-spark>=2.0; extra == "standalone"
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# Data Quality Assessment Framework — Databricks / Delta Lake
|
|
22
|
+
|
|
23
|
+
**Open-source Python library** reimplementing the SQL Server DQ Assessment
|
|
24
|
+
Framework natively on Databricks with Delta tables, PySpark DataFrames, and
|
|
25
|
+
Python-based validation functions.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What this Library Provides
|
|
30
|
+
|
|
31
|
+
A **metadata-driven, PySpark port of a SQL Server Data Quality Assessment Framework**
|
|
32
|
+
for Databricks/Delta Lake. It validates data in curated Delta tables using configurable
|
|
33
|
+
rules — no code changes needed to add new fields or update logic.
|
|
34
|
+
|
|
35
|
+
| Feature | SQL Server Implementation | Databricks Implementation |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Storage | SQL Server tables | Delta Lake tables (Unity Catalog) |
|
|
38
|
+
| Pattern evaluation | T-SQL TVFs (generated dynamically) | Python closures (generated dynamically) |
|
|
39
|
+
| Row-level evaluation | `CROSS APPLY TVF` | PySpark UDF + `DeltaTable.merge()` |
|
|
40
|
+
| Custom rules | SQL expressions in `configCustomQuery` | Plain regex patterns or named validators |
|
|
41
|
+
| Email validation | Complex `LIKE` / `CHARINDEX` SQL | Native Python regex (enhanced) |
|
|
42
|
+
| Output | `SELECT` result sets | Spark DataFrames |
|
|
43
|
+
| Same field configuration | Script_02 SQL `INSERT` | `ConfigManager` Python API or raw SQL |
|
|
44
|
+
| Same pattern library | 118 patterns | 118 patterns (identical) |
|
|
45
|
+
| Same precedence logic | L0A `RANK()` CTE | Python ranking equivalent |
|
|
46
|
+
| Same validation levels | L01→L02→L03→L04→L99 | L01→L02→L03→L04→L99 (identical) |
|
|
47
|
+
| Same DQ columns | `DQEligible BIT`, `DQViolations`, `DQFields` | `DQEligible BOOLEAN`, `DQViolations STRING`, `DQFields STRING` |
|
|
48
|
+
| Same dual logging | `auditDQChecks` + `statDQChecks` | Same Delta tables |
|
|
49
|
+
| Same ExecutionID | `NEWID()` GUID | `uuid.uuid4()` |
|
|
50
|
+
| Same `1=1` scope filtering | `WHERE 1=1 AND (...)` | Python conditional filter |
|
|
51
|
+
| Same sticky DQ logic | `CASE WHEN DQEligible=0 THEN 0 ELSE Result END` | Spark `when()` equivalent |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Architecture at a Glance
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
masterField (config) ──► configFieldAllowedPattern ──► mapDQChecks
|
|
59
|
+
│ │
|
|
60
|
+
ConfigManager API resolve_pattern_rules.py (L0A precedence)
|
|
61
|
+
│ │
|
|
62
|
+
└──────────────────────────┘
|
|
63
|
+
│
|
|
64
|
+
generate_rule_functions.py
|
|
65
|
+
│
|
|
66
|
+
FunctionRegistry (UDFs)
|
|
67
|
+
│
|
|
68
|
+
data_assessment_rules.py (DQRunner)
|
|
69
|
+
│
|
|
70
|
+
Curated Table: DQEligible | DQViolations | DQFields
|
|
71
|
+
│
|
|
72
|
+
┌───────────────┴───────────────┐
|
|
73
|
+
auditDQChecks statDQChecks
|
|
74
|
+
└───────────────┬───────────────┘
|
|
75
|
+
Reporting Views
|
|
76
|
+
(v_auditDQChecks, v_statDQChecks)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Data flow:** Config tables → precedence resolution → Python closure generation →
|
|
80
|
+
UDF-based row assessment → DQ column updates + dual audit logging → reporting.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from dq_framework import DQFramework
|
|
88
|
+
|
|
89
|
+
# 1. Initialise
|
|
90
|
+
dq = DQFramework(spark, catalog="main", schema="dq")
|
|
91
|
+
dq.setup()
|
|
92
|
+
|
|
93
|
+
# 2. Configure a field (ConfigManager API — no SQL required)
|
|
94
|
+
cfg = dq.config
|
|
95
|
+
cfg.register_field(1, "Source.SRC_ContactPoint.EMAIL_ADDRESS", data_category_type_id=4)
|
|
96
|
+
cfg.set_field_values(1, "Source.SRC_ContactPoint.EMAIL_ADDRESS", min_data_length=6, max_data_length=255)
|
|
97
|
+
cfg.block_category(1, "Source.SRC_ContactPoint.EMAIL_ADDRESS", "DataEmptiness")
|
|
98
|
+
cfg.allow_pattern(2, "Source.SRC_ContactPoint.EMAIL_ADDRESS", "Has At Sign")
|
|
99
|
+
cfg.add_custom_query_regex(1, "Source.SRC_ContactPoint.EMAIL_ADDRESS",
|
|
100
|
+
r"^[^@\s]+@[^@\s]+\.[^@\s]+$", must_match=True)
|
|
101
|
+
cfg.add_mapping(1, "Source.SRC_ContactPoint.EMAIL_ADDRESS",
|
|
102
|
+
target_schema_name="Curated", target_table_name="Entity_Email_Denorm",
|
|
103
|
+
target_field_name="EMAIL_ADDRESS", target_catalog_name="main")
|
|
104
|
+
|
|
105
|
+
# 3. Validate config FK integrity before generating functions
|
|
106
|
+
cfg.verify_config()
|
|
107
|
+
|
|
108
|
+
# 4. Generate field-level checker functions from config
|
|
109
|
+
dq.generate_rule_functions()
|
|
110
|
+
|
|
111
|
+
# 5. Run the DQ assessment
|
|
112
|
+
exec_id = dq.run_assessment(schema_name="Curated")
|
|
113
|
+
|
|
114
|
+
# 6. Query results
|
|
115
|
+
dq.violations(exec_id).display()
|
|
116
|
+
dq.quality_scores(exec_id).display()
|
|
117
|
+
dq.fields_below_threshold(threshold=80).display()
|
|
118
|
+
dq.summary_by_table(exec_id).display()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
See [notebooks/00_quickstart.py](notebooks/00_quickstart.py) for a fully commented step-by-step walkthrough.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Prerequisite: DQ Columns on Curated Tables
|
|
126
|
+
|
|
127
|
+
Each curated Delta table being assessed must have these three columns **added
|
|
128
|
+
before running the assessment**:
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
ALTER TABLE main.Curated.YourTable
|
|
132
|
+
ADD COLUMNS (
|
|
133
|
+
DQEligible BOOLEAN COMMENT '1=all checks passed, 0=at least one failed, NULL=not assessed',
|
|
134
|
+
DQViolations STRING COMMENT '[field: ViolationType], [field: ViolationType]',
|
|
135
|
+
DQFields STRING COMMENT '[field1], [field2] (all fields assessed on this row)'
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Column | Value | Meaning |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `DQEligible` | `None` / `NULL` | Row not yet assessed |
|
|
142
|
+
| `DQEligible` | `True` | All configured checks passed |
|
|
143
|
+
| `DQEligible` | `False` | At least one check failed |
|
|
144
|
+
| `DQViolations` | e.g. `[email_address: Data Type]` | Violated rules (sticky — accumulates across fields) |
|
|
145
|
+
| `DQFields` | e.g. `[email_address], [phone]` | All fields assessed on this row |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Package Structure
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
dq_framework/
|
|
153
|
+
├── __init__.py # Exports: DQFramework, ConfigManager
|
|
154
|
+
├── framework.py # DQFramework facade — top-level orchestration
|
|
155
|
+
├── ddl_framework_tables.py # ≡ Script_00_DDL_Framework_Tables — Delta table DDL (9 tables)
|
|
156
|
+
├── seed_master_data.py # ≡ Script_01_Master_Reference_Data — 27 categories + 118 patterns
|
|
157
|
+
├── config.py # ConfigManager — Python API for all 5 config tables
|
|
158
|
+
├── engine/
|
|
159
|
+
│ ├── pattern_checks.py # All 118 pattern check implementations
|
|
160
|
+
│ ├── resolve_pattern_rules.py # Pattern precedence (L0A CTE equivalent)
|
|
161
|
+
│ ├── generate_rule_functions.py # ≡ p_DQ_GenerateRuleFunctions — FunctionRegistry + closures
|
|
162
|
+
│ └── data_assessment_rules.py # ≡ p_DQ_DataAssessmentRules — DQRunner
|
|
163
|
+
├── reporting/
|
|
164
|
+
│ └── views.py # v_auditDQChecks + v_statDQChecks equivalents
|
|
165
|
+
└── agents/
|
|
166
|
+
├── __init__.py # Exports SYSTEM_PROMPT, DQ_TOOLS, suggest_config
|
|
167
|
+
├── context.py # LLM system prompt + short context
|
|
168
|
+
├── tools.py # OpenAI-compatible tool definitions + execute_tool_call()
|
|
169
|
+
└── suggest.py # Schema-based config auto-suggester (no LLM required)
|
|
170
|
+
notebooks/
|
|
171
|
+
├── 00_quickstart.py # Step-by-step walkthrough: configure → assess → results
|
|
172
|
+
├── 01_agent_assistant.py # AI agent integration: auto-suggest, tool-calling, system prompt
|
|
173
|
+
├── 02_integration_test.py # Integration test suite (run on cluster before publishing)
|
|
174
|
+
└── 03_seed_config.py # Paste Excel-generated INSERT SQL here
|
|
175
|
+
tests/
|
|
176
|
+
├── conftest.py # Path setup + test split explanation
|
|
177
|
+
└── unit/
|
|
178
|
+
├── test_pattern_checks.py # All 118 pattern functions (no cluster needed)
|
|
179
|
+
├── test_resolve_pattern_rules.py # Pattern precedence logic
|
|
180
|
+
├── test_generate_rule_functions.py # Closure building + FunctionRegistry
|
|
181
|
+
├── test_seed_master_data.py # Seed data integrity (counts, IDs, FK)
|
|
182
|
+
└── test_suggest.py # Schema-based config suggester
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 9 Framework Tables
|
|
188
|
+
|
|
189
|
+
All tables live in the `dq` schema (configurable). Three categories:
|
|
190
|
+
|
|
191
|
+
| Category | Tables |
|
|
192
|
+
|---|---|
|
|
193
|
+
| **Framework-Seeded** (auto-populated by `setup()`) | `masterDataCategory`, `masterPattern` |
|
|
194
|
+
| **User-Managed** (populated via `ConfigManager` or SQL) | `masterField`, `configFieldValues`, `configFieldAllowedPattern`, `configCustomQuery`, `mapDQChecks` |
|
|
195
|
+
| **Results** (auto-populated by `run_assessment()`) | `auditDQChecks`, `statDQChecks` |
|
|
196
|
+
|
|
197
|
+
| Table | Purpose |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `masterDataCategory` | 27 field type classifications |
|
|
200
|
+
| `masterPattern` | 118 built-in validation patterns. Custom patterns use `_ID >= 1000` (IDs 1–999 are reserved for built-ins) |
|
|
201
|
+
| `masterField` | Source fields registered for assessment |
|
|
202
|
+
| `configFieldValues` | Data length (L01) and value range (L04) boundaries |
|
|
203
|
+
| `configFieldAllowedPattern` | Pattern allow/block rules per field |
|
|
204
|
+
| `configCustomQuery` | Custom validation expressions — plain regex, Spark SQL, or named validators (L02) |
|
|
205
|
+
| `mapDQChecks` | Maps source field → curated table column |
|
|
206
|
+
| `auditDQChecks` | Row-level violation audit log (failures only, append-only) |
|
|
207
|
+
| `statDQChecks` | Aggregated pass/fail statistics per execution (append-only) |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Validation Levels (in order, early-exit on first failure)
|
|
212
|
+
|
|
213
|
+
Each generated field checker implements the same 5-level hierarchy as the SQL TVF:
|
|
214
|
+
|
|
215
|
+
| Level | Check Type | Priority |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| **L01** | Data Length range (min/max character count) | Checked first |
|
|
218
|
+
| **L02** | Custom expression rules (regex, Spark SQL, or named validator) | Checked second |
|
|
219
|
+
| **L03** | 118-pattern checks (in PatternPriority order) | Checked third |
|
|
220
|
+
| **L04** | Data Value range (min/max value comparison) | Checked fourth |
|
|
221
|
+
| **L99** | Default PASS (if all prior checks pass) | Final |
|
|
222
|
+
|
|
223
|
+
**Early exit**: The checker returns immediately on first failure.
|
|
224
|
+
Re-run after fixing the first violation to surface the next.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 118 Patterns across 10 Categories
|
|
229
|
+
|
|
230
|
+
| Category | Count | Examples |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `DataType1` | 3 | Is Fully Numeric, Is Fully Decimal, Is Fully Text |
|
|
233
|
+
| `DataType2` | 1 | Is AlphaNumeric |
|
|
234
|
+
| `DataType3` | 4 | Is Date, Is Time, Is Timestamp, Is Boolean |
|
|
235
|
+
| `SpecialCharacter` | 32 | Has Comma, Has At Sign, Has Hyphen, Has Pipe Symbol, … |
|
|
236
|
+
| `SpaceFound` | 1 | Has Space |
|
|
237
|
+
| `DataEmptiness` | 34 | Is Empty or NULL, Is Virtually Empty with Spaces, … |
|
|
238
|
+
| `InvalidKeyword` | 40 | Has Keyword-n/a, Has Keyword-null, Has Keyword-missing, … |
|
|
239
|
+
| `FullyDuplicatedCharacter` | 1 | Has Fully Duplicated Character (e.g. "aaaa") |
|
|
240
|
+
| `UnicodeCharacters` | 1 | Has Unicode Characters (outside printable ASCII 0x20–0x7E) |
|
|
241
|
+
| `CasingCheck` | 2 | Has Lowercase Character, Has Uppercase Character |
|
|
242
|
+
|
|
243
|
+
**Total: 118 built-in patterns.** Pattern IDs 1–999 are reserved for framework use.
|
|
244
|
+
To add custom patterns (e.g. domain-specific invalid keywords), use IDs `>= 1000`.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Pattern Resolution (L0A — three levels of granularity)
|
|
249
|
+
|
|
250
|
+
| Level | Populate in `configFieldAllowedPattern` | Effect |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| Category-level | Set `PatternCategory` only | Applies to ALL patterns in that category |
|
|
253
|
+
| SubCategory-level | Set `PatternSubCategory` only | Applies to ALL patterns in that sub-category |
|
|
254
|
+
| Pattern-level | Set `PatternName` only | Applies to that specific named pattern |
|
|
255
|
+
|
|
256
|
+
**Precedence**: More specific rule (PatternName) overrides broader rule (Category).
|
|
257
|
+
**Within the same specificity level**, Allowed overrides Not Allowed — meaning a
|
|
258
|
+
specific "Allowed" at pattern level always wins over a broad "Not Allowed" at
|
|
259
|
+
category level.
|
|
260
|
+
|
|
261
|
+
Example: *"Block ALL special characters (category rule), EXCEPT allow Hyphen,
|
|
262
|
+
Full Stop, and At Sign (pattern-level overrides)"*
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
cfg.block_category(1, "Source.T.FIELD", "SpecialCharacter") # block all
|
|
266
|
+
cfg.allow_pattern(2, "Source.T.FIELD", "Has Hyphen") # except hyphen
|
|
267
|
+
cfg.allow_pattern(3, "Source.T.FIELD", "Has Full Stop") # except full stop
|
|
268
|
+
cfg.allow_pattern(4, "Source.T.FIELD", "Has At Sign") # except @ sign
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Custom Validation Rules (L02)
|
|
274
|
+
|
|
275
|
+
The `configCustomQuery` table stores a `CustomQueryExpression` for each field.
|
|
276
|
+
Three options are available.
|
|
277
|
+
|
|
278
|
+
### Option 1: Plain regex (recommended for non-technical users)
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
# Must match: value must satisfy the regex to PASS
|
|
282
|
+
cfg.add_custom_query_regex(1, "Source.T.EMAIL_ADDRESS",
|
|
283
|
+
r"^[^@\s]+@[^@\s]+\.[^@\s]+$", must_match=True,
|
|
284
|
+
description="Basic email format")
|
|
285
|
+
|
|
286
|
+
# Must NOT match: if the regex matches, the row FAILS
|
|
287
|
+
cfg.add_custom_query_regex(2, "Source.T.EMAIL_ADDRESS",
|
|
288
|
+
r"noemaildress", must_match=False,
|
|
289
|
+
description="Reject placeholder values")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The engine auto-detects regex patterns by the presence of metacharacters
|
|
293
|
+
(`^$+*?[({|\`) and applies `re.search()` automatically.
|
|
294
|
+
|
|
295
|
+
### Option 2: Spark SQL expression
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
# @InputValue is replaced with the actual column reference at assessment time
|
|
299
|
+
cfg.add_custom_query_sql(1, "Source.T.AMOUNT",
|
|
300
|
+
"CAST(@InputValue AS DOUBLE) > 0",
|
|
301
|
+
is_condition_allowed=True,
|
|
302
|
+
description="Amount must be positive")
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
SQL expressions are applied at the DataFrame level via `F.expr()` — not inside a
|
|
306
|
+
Python UDF — so they support any Spark SQL built-in function.
|
|
307
|
+
|
|
308
|
+
### Option 3: Named validator (for complex logic)
|
|
309
|
+
|
|
310
|
+
Register a Python function once, then reference it by name in any field config:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# Register (once, at cluster startup or in a setup cell)
|
|
314
|
+
dq.register_validator("au_mobile", lambda v: bool(__import__('re').match(r'^04[0-9]{8}$', v or '')))
|
|
315
|
+
|
|
316
|
+
# Reference by name in config
|
|
317
|
+
cfg.add_custom_query(1, "Source.T.MOBILE_NUMBER", "au_mobile", is_condition_allowed=True)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Built-in email validators are pre-registered:
|
|
321
|
+
|
|
322
|
+
| Validator Name | Description |
|
|
323
|
+
|---|---|
|
|
324
|
+
| `email_basic_format` | Regex: basic `x@y.z` format |
|
|
325
|
+
| `email_at_validation` | @ position, count, dot after @ |
|
|
326
|
+
| `email_domain_format` | 1–3 dots, no trailing dot, 2+ char labels |
|
|
327
|
+
| `email_local_part_dots` | Max 2 dots in local part |
|
|
328
|
+
| `email_no_placeholder` | Must not contain 'noemaildress' |
|
|
329
|
+
| `email_no_trailing_special` | Must not end with `.`, `-`, or `_` |
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Scope Filtering (`1=1` Pattern Preserved)
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
# All schemas, all tables, all fields
|
|
337
|
+
dq.run_assessment()
|
|
338
|
+
|
|
339
|
+
# Specific schema
|
|
340
|
+
dq.run_assessment(schema_name="Curated")
|
|
341
|
+
|
|
342
|
+
# Specific table
|
|
343
|
+
dq.run_assessment(schema_name="Curated", table_name="Individual_Denorm")
|
|
344
|
+
|
|
345
|
+
# Single field
|
|
346
|
+
dq.run_assessment(schema_name="Curated",
|
|
347
|
+
table_name="Individual_Denorm",
|
|
348
|
+
field_name="FIRST_NAME")
|
|
349
|
+
|
|
350
|
+
# Reset previously-failed rows before re-assessment
|
|
351
|
+
dq.run_assessment(schema_name="Curated", reset_eligible_flag=True)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Reporting
|
|
357
|
+
|
|
358
|
+
Four helper methods return Spark DataFrames ready for `.display()` or further transformation:
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
exec_id = dq.run_assessment(schema_name="Curated")
|
|
362
|
+
|
|
363
|
+
dq.violations(exec_id).display() # Row-level violations (field, violation type, value)
|
|
364
|
+
dq.quality_scores(exec_id).display() # Pass/fail counts + % per field
|
|
365
|
+
dq.fields_below_threshold(threshold=80).display() # Fields with quality < 80%
|
|
366
|
+
dq.summary_by_table(exec_id).display() # Aggregated quality score per table
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Two persistent reporting views are also created in the `dq` schema by `setup()`:
|
|
370
|
+
|
|
371
|
+
| View | Purpose |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `v_auditDQChecks` | Row-level violations joined with field metadata |
|
|
374
|
+
| `v_statDQChecks` | Aggregated pass/fail statistics with `PercentageQualified` |
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## AI Agent Support
|
|
379
|
+
|
|
380
|
+
The `dq_framework.agents` module provides three ways to use AI to configure the framework.
|
|
381
|
+
**No agent framework (Semantic Kernel, LangChain, AutoGen) is required.**
|
|
382
|
+
|
|
383
|
+
### Approach A — Auto-suggest from schema (no LLM needed)
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
from dq_framework.agents import suggest_config
|
|
387
|
+
|
|
388
|
+
schema = [
|
|
389
|
+
("EMAIL_ADDRESS", "string"),
|
|
390
|
+
("FIRST_NAME", "string"),
|
|
391
|
+
("POSTAL_CODE", "string"),
|
|
392
|
+
("CUSTOMER_ID", "int"),
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
# Returns ready-to-run ConfigManager code
|
|
396
|
+
code = suggest_config(schema, source_schema="Source", source_table="SRC_Customer",
|
|
397
|
+
curated_schema="Curated", curated_table="Customer_Denorm",
|
|
398
|
+
catalog="main")
|
|
399
|
+
print(code)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Or read schema directly from a live Delta table:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
schema = spark.table("main.Source.SRC_Customer").dtypes
|
|
406
|
+
code = suggest_config(schema, source_schema="Source", source_table="SRC_Customer", ...)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Approach B — Chat assistant with tool calling
|
|
410
|
+
|
|
411
|
+
The 14 framework operations are exposed as OpenAI-compatible JSON Schema tool definitions.
|
|
412
|
+
They work unchanged with OpenAI, Azure OpenAI, Anthropic Claude, Semantic Kernel, AutoGen,
|
|
413
|
+
and LangChain.
|
|
414
|
+
|
|
415
|
+
```python
|
|
416
|
+
from dq_framework.agents import SYSTEM_PROMPT, DQ_TOOLS
|
|
417
|
+
from dq_framework.agents.tools import execute_tool_call
|
|
418
|
+
from openai import AzureOpenAI
|
|
419
|
+
|
|
420
|
+
client = AzureOpenAI(azure_endpoint="...", api_key="...", api_version="2024-05-01-preview")
|
|
421
|
+
|
|
422
|
+
response = client.chat.completions.create(
|
|
423
|
+
model="gpt-4o",
|
|
424
|
+
messages=[
|
|
425
|
+
{"role": "system", "content": SYSTEM_PROMPT},
|
|
426
|
+
{"role": "user", "content": "Configure DQ rules for Source.SRC_Party.EMAIL_ADDRESS"},
|
|
427
|
+
],
|
|
428
|
+
tools=DQ_TOOLS,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
for call in response.choices[0].message.tool_calls or []:
|
|
432
|
+
result = execute_tool_call(call.function.name, call.function.arguments, dq, cfg)
|
|
433
|
+
print(f"[Tool] {call.function.name} → {result}")
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
See [notebooks/01_agent_assistant.py](notebooks/01_agent_assistant.py) for Azure OpenAI,
|
|
437
|
+
Anthropic Claude, and Semantic Kernel examples.
|
|
438
|
+
|
|
439
|
+
### Approach C — Paste system prompt into ChatGPT / Copilot
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from dq_framework.agents import SYSTEM_PROMPT
|
|
443
|
+
print(SYSTEM_PROMPT) # Copy and paste into any chat interface
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Testing
|
|
449
|
+
|
|
450
|
+
### Unit tests — no cluster needed
|
|
451
|
+
|
|
452
|
+
Run locally with Python and `pytest`. No Databricks, no Spark, no Delta Lake required.
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
pip install dq-framework pytest
|
|
456
|
+
pytest tests/unit/ -v
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
| Test file | What it covers |
|
|
460
|
+
|---|---|
|
|
461
|
+
| `test_pattern_checks.py` | All major pattern check functions (DataEmptiness, SpecialChar, InvalidKeyword, …) |
|
|
462
|
+
| `test_resolve_pattern_rules.py` | Pattern precedence — specific overrides broad, inactive excluded |
|
|
463
|
+
| `test_generate_rule_functions.py` | L01/L02/L04 closure building, `FunctionRegistry`, stale function removal |
|
|
464
|
+
| `test_seed_master_data.py` | Seed data integrity: 27 categories, 118 patterns, no duplicate IDs |
|
|
465
|
+
| `test_suggest.py` | Schema-based config suggester: column classification, code generation |
|
|
466
|
+
|
|
467
|
+
### Integration tests — requires a Databricks cluster
|
|
468
|
+
|
|
469
|
+
Open [notebooks/02_integration_test.py](notebooks/02_integration_test.py) on a cluster.
|
|
470
|
+
It creates throwaway test schemas, runs the full pipeline end-to-end, and drops everything on completion.
|
|
471
|
+
|
|
472
|
+
| Test | What it verifies |
|
|
473
|
+
|---|---|
|
|
474
|
+
| Test 1 | Framework setup — 9 tables created, 27 categories, 118 patterns seeded |
|
|
475
|
+
| Test 2 | `setup()` is idempotent — no duplicate data on second call |
|
|
476
|
+
| Test 3 | Synthetic curated table created with known good/bad email values |
|
|
477
|
+
| Test 4 | `ConfigManager` — all 5 user-managed tables populated |
|
|
478
|
+
| Test 5 | `generate_rule_functions()` — checker built and directly invocable |
|
|
479
|
+
| Test 6 | `run_assessment()` — DQ columns written correctly, specific rows verified |
|
|
480
|
+
| Test 7 | Reporting views — violations count, quality scores, threshold filter, summary |
|
|
481
|
+
| Test 8 | `reset_eligible_flag` — failed rows cleared, re-assessment gives same result |
|
|
482
|
+
| Test 9 | Custom keyword extensibility via `add_invalid_keyword()` |
|
|
483
|
+
| Test 10 | `cfg.verify_config()` passes with no FK issues |
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Platform Requirements
|
|
488
|
+
|
|
489
|
+
| Requirement | Detail |
|
|
490
|
+
|---|---|
|
|
491
|
+
| Databricks Runtime | 12.0+ (includes PySpark 3.3+) |
|
|
492
|
+
| Delta Lake | 2.0+ |
|
|
493
|
+
| Python | 3.9+ |
|
|
494
|
+
| Unity Catalog | Recommended (pass `catalog=""` for legacy Hive metastore) |
|
|
495
|
+
| python-dateutil | Included in Databricks Runtime; required for full date/time validation |
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Adding a New Source Field
|
|
500
|
+
|
|
501
|
+
1. Register the field: `cfg.register_field(id, "Schema.Table.Column", data_category_type_id=N)`
|
|
502
|
+
2. Set length bounds: `cfg.set_field_values(id, ffn, min_data_length=M, max_data_length=N)`
|
|
503
|
+
3. Add pattern rules: `cfg.block_category(...)`, `cfg.allow_pattern(...)`, `cfg.block_pattern(...)`
|
|
504
|
+
4. Add custom expressions: `cfg.add_custom_query_regex(id, ffn, r"your_regex", must_match=True)`
|
|
505
|
+
5. (Optional) Add custom invalid keywords: `dq.add_invalid_keyword(id, "your_keyword")`
|
|
506
|
+
6. Map to curated column: `cfg.add_mapping(id, ffn, target_schema_name=..., target_table_name=..., ...)`
|
|
507
|
+
7. Validate FK integrity: `cfg.verify_config()`
|
|
508
|
+
8. Re-run: `dq.generate_rule_functions()`
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Known Limitations
|
|
513
|
+
|
|
514
|
+
| # | Area | Description |
|
|
515
|
+
|---|---|---|
|
|
516
|
+
| 1 | **Bug — `add_custom_query()`** | `CustomQueryDescription` column is `NOT NULL` in the DDL but the method passes `null` when no `description` argument is provided. Always supply a `description` when calling `add_custom_query()`, `add_custom_query_regex()`, or `add_custom_query_sql()` to avoid an INSERT constraint violation. |
|
|
517
|
+
| 2 | **Silent pattern skip** | If a `PatternName` in `configFieldAllowedPattern` does not match any row in `masterPattern`, the rule is silently ignored (equivalent to SQL's `ELSE NULL`). A typo in a pattern name will go undetected unless `verify_config()` is run. |
|
|
518
|
+
| 3 | **No transaction guarantees** | The assessment writes DQ columns, audit rows, and stat rows in three separate Spark actions. A cluster failure between steps can leave partial results. Re-run the assessment with `reset_eligible_flag=True` to recover. |
|
|
519
|
+
| 4 | **Date validation outside Databricks** | If `python-dateutil` is not installed, date/time pattern checks fall back to three hard-coded regex formats. This is rarely an issue on Databricks Runtime but may affect local unit test runs that exercise the date validators. |
|
|
520
|
+
| 5 | **No parameterised SQL** | Config is written via f-string SQL with single-quote escaping. Field names and expressions provided by trusted developers are safe; do not pass untrusted user input directly into ConfigManager methods. |
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
*Data Quality Non-Functional Assessment Framework — Databricks Edition*
|
|
525
|
+
*Generalised for open-source publication — no client-specific data included*
|