datascope-dq 2.1.0__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.
- datascope_dq-2.1.0/LICENSE +21 -0
- datascope_dq-2.1.0/PKG-INFO +185 -0
- datascope_dq-2.1.0/README.md +155 -0
- datascope_dq-2.1.0/datascope/__init__.py +5 -0
- datascope_dq-2.1.0/datascope/__main__.py +6 -0
- datascope_dq-2.1.0/datascope/analyzers/__init__.py +16 -0
- datascope_dq-2.1.0/datascope/analyzers/base.py +19 -0
- datascope_dq-2.1.0/datascope/analyzers/cardinality.py +112 -0
- datascope_dq-2.1.0/datascope/analyzers/format_check.py +232 -0
- datascope_dq-2.1.0/datascope/analyzers/sentinel.py +157 -0
- datascope_dq-2.1.0/datascope/analyzers/type_consistency.py +134 -0
- datascope_dq-2.1.0/datascope/cli.py +276 -0
- datascope_dq-2.1.0/datascope/findings/__init__.py +21 -0
- datascope_dq-2.1.0/datascope/findings/composer.py +57 -0
- datascope_dq-2.1.0/datascope/findings/pipeline.py +39 -0
- datascope_dq-2.1.0/datascope/findings/severity.py +47 -0
- datascope_dq-2.1.0/datascope/findings/templates.py +355 -0
- datascope_dq-2.1.0/datascope/loaders/__init__.py +9 -0
- datascope_dq-2.1.0/datascope/loaders/base.py +58 -0
- datascope_dq-2.1.0/datascope/loaders/csv_loader.py +179 -0
- datascope_dq-2.1.0/datascope/loaders/excel.py +98 -0
- datascope_dq-2.1.0/datascope/models.py +93 -0
- datascope_dq-2.1.0/datascope/reports/__init__.py +9 -0
- datascope_dq-2.1.0/datascope/reports/pdf.py +626 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/PKG-INFO +185 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/SOURCES.txt +41 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/dependency_links.txt +1 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/entry_points.txt +2 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/requires.txt +8 -0
- datascope_dq-2.1.0/datascope_dq.egg-info/top_level.txt +1 -0
- datascope_dq-2.1.0/pyproject.toml +60 -0
- datascope_dq-2.1.0/setup.cfg +4 -0
- datascope_dq-2.1.0/tests/test_cardinality.py +266 -0
- datascope_dq-2.1.0/tests/test_cli.py +235 -0
- datascope_dq-2.1.0/tests/test_composer.py +602 -0
- datascope_dq-2.1.0/tests/test_format_check.py +322 -0
- datascope_dq-2.1.0/tests/test_integration.py +223 -0
- datascope_dq-2.1.0/tests/test_loaders.py +349 -0
- datascope_dq-2.1.0/tests/test_models.py +155 -0
- datascope_dq-2.1.0/tests/test_report_pdf.py +371 -0
- datascope_dq-2.1.0/tests/test_sentinel.py +405 -0
- datascope_dq-2.1.0/tests/test_severity.py +276 -0
- datascope_dq-2.1.0/tests/test_type_consistency.py +259 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shawn (Lailara LLC)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datascope-dq
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Data quality diagnostics for tabular datasets — surfaces hidden problems in plain English
|
|
5
|
+
Author-email: Shawn <msshawnp@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MsShawnP/datascope
|
|
8
|
+
Project-URL: Repository, https://github.com/MsShawnP/datascope
|
|
9
|
+
Project-URL: Issues, https://github.com/MsShawnP/datascope/issues
|
|
10
|
+
Keywords: data-quality,diagnostics,csv,excel,type-detection,data-validation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pandas>=2.0.0
|
|
23
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
24
|
+
Requires-Dist: reportlab>=4.0.0
|
|
25
|
+
Requires-Dist: defusedxml>=0.7.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# datascope
|
|
32
|
+
|
|
33
|
+
Data created upstream — by manufacturing teams entering UPCs, inventory staff assigning product codes, offshore developers choosing column types — silently breaks systems downstream. A product code with letters where EDI expects numbers. Fifteen "N/A" strings buried in 500 numeric rows that pandas silently drops, skewing every calculation by 3%.
|
|
34
|
+
|
|
35
|
+
datascope finds these problems, explains what's wrong in plain English, and tells you what to fix. It reads each cell's actual type (not what pandas infers), detects hidden quality issues, classifies their severity by downstream impact, and generates a professional diagnostic report.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What It Finds
|
|
40
|
+
|
|
41
|
+
| Detection | Example | Severity |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| **Mixed types** | 485 numbers + 15 strings in a "numeric" column | Critical |
|
|
44
|
+
| **Sentinel values** | "N/A", "TBD", "pending" hiding in numeric data | Critical |
|
|
45
|
+
| **Leading-zero inconsistency** | "00123" alongside "456" — keys that won't match | Warning |
|
|
46
|
+
| **Mixed date formats** | "01/15/2026" and "2026-01-15" in the same column | Warning |
|
|
47
|
+
| **Suspected duplicate IDs** | 98% unique in an ID column — the other 2% will fan out joins | Warning |
|
|
48
|
+
| **Near-constant columns** | 1 distinct value across 10,000 rows | Info |
|
|
49
|
+
|
|
50
|
+
Each finding is expressed as **assumption vs. reality**: what the data *appears* to be vs. what it *actually contains*. Every finding includes a downstream impact explanation, a fix recommendation, and a prevention rule.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install datascope-dq
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or install from source:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/MsShawnP/datascope.git
|
|
64
|
+
cd datascope
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Analyze an Excel file
|
|
74
|
+
datascope data.xlsx
|
|
75
|
+
|
|
76
|
+
# Analyze a CSV
|
|
77
|
+
datascope sales_export.csv
|
|
78
|
+
|
|
79
|
+
# Specify a sheet and output directory
|
|
80
|
+
datascope data.xlsx --sheet Revenue --output-dir ./client_reports
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The tool produces a PDF diagnostic report and prints a summary to stdout:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
datascope: Analyzing sample_mixed_types.xlsx...
|
|
87
|
+
200 rows x 6 columns
|
|
88
|
+
|
|
89
|
+
Found 4 findings:
|
|
90
|
+
2 Critical ########
|
|
91
|
+
1 Warning ####
|
|
92
|
+
1 Info ####
|
|
93
|
+
|
|
94
|
+
Top critical findings:
|
|
95
|
+
* revenue_mixed: 15 non-numeric values hiding in an otherwise numeric column
|
|
96
|
+
* status: Sentinel values 'N/A' and 'TBD' in numeric data
|
|
97
|
+
|
|
98
|
+
Report saved: reports/sample_mixed_types_diagnostic.pdf
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## The Report
|
|
104
|
+
|
|
105
|
+
The PDF report is structured for non-technical readers — no jargon, no composite scores, no unexplained metrics.
|
|
106
|
+
|
|
107
|
+
**Executive Summary** — overall health assessment, finding counts by severity, top critical issues highlighted.
|
|
108
|
+
|
|
109
|
+
**Findings by Severity** — each finding presented as a card:
|
|
110
|
+
- **Assumption**: what the data appears to be
|
|
111
|
+
- **Reality**: what it actually contains
|
|
112
|
+
- **Impact**: what breaks downstream
|
|
113
|
+
- **Recommended Fix**: what to do now
|
|
114
|
+
- **Prevention Rule**: what right looks like going forward
|
|
115
|
+
|
|
116
|
+
**Field Inventory** — summary table of all columns with their detected issue types and severity.
|
|
117
|
+
|
|
118
|
+
Findings are color-coded (red/amber/blue) and grouped by severity so readers know what to fix first.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## How It Works
|
|
123
|
+
|
|
124
|
+
Most tools let pandas (or the SQL driver, or Excel) decide column types. A column with 485 numbers and 15 strings becomes `float64` — the strings become `NaN`, the type problem disappears, and every downstream calculation is quietly wrong.
|
|
125
|
+
|
|
126
|
+
datascope reads each cell's actual Python type via openpyxl (for Excel) or raw-string inference (for CSV). This cell-level type detection is always on — there's no flag to enable it because skipping it defeats the purpose.
|
|
127
|
+
|
|
128
|
+
The analysis pipeline:
|
|
129
|
+
|
|
130
|
+
1. **Load** — read with cell-level type preservation (no silent coercion)
|
|
131
|
+
2. **Detect** — five analyzers scan for type inconsistencies, sentinels, format issues, and cardinality anomalies
|
|
132
|
+
3. **Classify** — severity assigned by downstream impact (critical = silent data loss, warning = likely misinterpretation, info = worth noting)
|
|
133
|
+
4. **Compose** — plain-English narrative generated for each finding
|
|
134
|
+
5. **Report** — professional PDF rendered with reportlab
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Severity Model
|
|
139
|
+
|
|
140
|
+
| Level | Meaning | Examples |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| **Critical** | Silent data loss or incorrect calculations will occur | Mixed types in numeric columns; sentinel values pandas drops without warning |
|
|
143
|
+
| **Warning** | Key mismatches or misinterpretation likely | Leading-zero stripping; ambiguous date formats; duplicate IDs |
|
|
144
|
+
| **Info** | Worth noting, no direct downstream breakage | Near-constant columns; unusual cardinality |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Project Structure
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
datascope/
|
|
152
|
+
├── loaders/ # Excel and CSV with cell-level type tracking
|
|
153
|
+
│ ├── excel.py # openpyxl-based, preserves per-cell Python types
|
|
154
|
+
│ ├── csv_loader.py # Raw string inference (None → int → float → bool → datetime → str)
|
|
155
|
+
│ └── base.py # Extension-based dispatch
|
|
156
|
+
├── analyzers/ # Five detectors, each returns list[Finding]
|
|
157
|
+
│ ├── type_consistency.py
|
|
158
|
+
│ ├── sentinel.py
|
|
159
|
+
│ ├── format_check.py
|
|
160
|
+
│ └── cardinality.py
|
|
161
|
+
├── findings/ # Severity classifier + NL template engine
|
|
162
|
+
│ ├── severity.py # Impact-based classification rules
|
|
163
|
+
│ ├── templates.py # Plain-English templates per finding sub-type
|
|
164
|
+
│ ├── composer.py # Template dispatch
|
|
165
|
+
│ └── pipeline.py # classify → compose → sort
|
|
166
|
+
├── reports/
|
|
167
|
+
│ └── pdf.py # Professional PDF with reportlab
|
|
168
|
+
└── cli.py # argparse CLI, pipeline orchestration
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
- Python 3.10+
|
|
176
|
+
- pandas >= 2.0
|
|
177
|
+
- openpyxl >= 3.1
|
|
178
|
+
- reportlab >= 4.0
|
|
179
|
+
- defusedxml >= 0.7
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# datascope
|
|
2
|
+
|
|
3
|
+
Data created upstream — by manufacturing teams entering UPCs, inventory staff assigning product codes, offshore developers choosing column types — silently breaks systems downstream. A product code with letters where EDI expects numbers. Fifteen "N/A" strings buried in 500 numeric rows that pandas silently drops, skewing every calculation by 3%.
|
|
4
|
+
|
|
5
|
+
datascope finds these problems, explains what's wrong in plain English, and tells you what to fix. It reads each cell's actual type (not what pandas infers), detects hidden quality issues, classifies their severity by downstream impact, and generates a professional diagnostic report.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What It Finds
|
|
10
|
+
|
|
11
|
+
| Detection | Example | Severity |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Mixed types** | 485 numbers + 15 strings in a "numeric" column | Critical |
|
|
14
|
+
| **Sentinel values** | "N/A", "TBD", "pending" hiding in numeric data | Critical |
|
|
15
|
+
| **Leading-zero inconsistency** | "00123" alongside "456" — keys that won't match | Warning |
|
|
16
|
+
| **Mixed date formats** | "01/15/2026" and "2026-01-15" in the same column | Warning |
|
|
17
|
+
| **Suspected duplicate IDs** | 98% unique in an ID column — the other 2% will fan out joins | Warning |
|
|
18
|
+
| **Near-constant columns** | 1 distinct value across 10,000 rows | Info |
|
|
19
|
+
|
|
20
|
+
Each finding is expressed as **assumption vs. reality**: what the data *appears* to be vs. what it *actually contains*. Every finding includes a downstream impact explanation, a fix recommendation, and a prevention rule.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install datascope-dq
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or install from source:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/MsShawnP/datascope.git
|
|
34
|
+
cd datascope
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Analyze an Excel file
|
|
44
|
+
datascope data.xlsx
|
|
45
|
+
|
|
46
|
+
# Analyze a CSV
|
|
47
|
+
datascope sales_export.csv
|
|
48
|
+
|
|
49
|
+
# Specify a sheet and output directory
|
|
50
|
+
datascope data.xlsx --sheet Revenue --output-dir ./client_reports
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The tool produces a PDF diagnostic report and prints a summary to stdout:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
datascope: Analyzing sample_mixed_types.xlsx...
|
|
57
|
+
200 rows x 6 columns
|
|
58
|
+
|
|
59
|
+
Found 4 findings:
|
|
60
|
+
2 Critical ########
|
|
61
|
+
1 Warning ####
|
|
62
|
+
1 Info ####
|
|
63
|
+
|
|
64
|
+
Top critical findings:
|
|
65
|
+
* revenue_mixed: 15 non-numeric values hiding in an otherwise numeric column
|
|
66
|
+
* status: Sentinel values 'N/A' and 'TBD' in numeric data
|
|
67
|
+
|
|
68
|
+
Report saved: reports/sample_mixed_types_diagnostic.pdf
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## The Report
|
|
74
|
+
|
|
75
|
+
The PDF report is structured for non-technical readers — no jargon, no composite scores, no unexplained metrics.
|
|
76
|
+
|
|
77
|
+
**Executive Summary** — overall health assessment, finding counts by severity, top critical issues highlighted.
|
|
78
|
+
|
|
79
|
+
**Findings by Severity** — each finding presented as a card:
|
|
80
|
+
- **Assumption**: what the data appears to be
|
|
81
|
+
- **Reality**: what it actually contains
|
|
82
|
+
- **Impact**: what breaks downstream
|
|
83
|
+
- **Recommended Fix**: what to do now
|
|
84
|
+
- **Prevention Rule**: what right looks like going forward
|
|
85
|
+
|
|
86
|
+
**Field Inventory** — summary table of all columns with their detected issue types and severity.
|
|
87
|
+
|
|
88
|
+
Findings are color-coded (red/amber/blue) and grouped by severity so readers know what to fix first.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
Most tools let pandas (or the SQL driver, or Excel) decide column types. A column with 485 numbers and 15 strings becomes `float64` — the strings become `NaN`, the type problem disappears, and every downstream calculation is quietly wrong.
|
|
95
|
+
|
|
96
|
+
datascope reads each cell's actual Python type via openpyxl (for Excel) or raw-string inference (for CSV). This cell-level type detection is always on — there's no flag to enable it because skipping it defeats the purpose.
|
|
97
|
+
|
|
98
|
+
The analysis pipeline:
|
|
99
|
+
|
|
100
|
+
1. **Load** — read with cell-level type preservation (no silent coercion)
|
|
101
|
+
2. **Detect** — five analyzers scan for type inconsistencies, sentinels, format issues, and cardinality anomalies
|
|
102
|
+
3. **Classify** — severity assigned by downstream impact (critical = silent data loss, warning = likely misinterpretation, info = worth noting)
|
|
103
|
+
4. **Compose** — plain-English narrative generated for each finding
|
|
104
|
+
5. **Report** — professional PDF rendered with reportlab
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Severity Model
|
|
109
|
+
|
|
110
|
+
| Level | Meaning | Examples |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| **Critical** | Silent data loss or incorrect calculations will occur | Mixed types in numeric columns; sentinel values pandas drops without warning |
|
|
113
|
+
| **Warning** | Key mismatches or misinterpretation likely | Leading-zero stripping; ambiguous date formats; duplicate IDs |
|
|
114
|
+
| **Info** | Worth noting, no direct downstream breakage | Near-constant columns; unusual cardinality |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Project Structure
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
datascope/
|
|
122
|
+
├── loaders/ # Excel and CSV with cell-level type tracking
|
|
123
|
+
│ ├── excel.py # openpyxl-based, preserves per-cell Python types
|
|
124
|
+
│ ├── csv_loader.py # Raw string inference (None → int → float → bool → datetime → str)
|
|
125
|
+
│ └── base.py # Extension-based dispatch
|
|
126
|
+
├── analyzers/ # Five detectors, each returns list[Finding]
|
|
127
|
+
│ ├── type_consistency.py
|
|
128
|
+
│ ├── sentinel.py
|
|
129
|
+
│ ├── format_check.py
|
|
130
|
+
│ └── cardinality.py
|
|
131
|
+
├── findings/ # Severity classifier + NL template engine
|
|
132
|
+
│ ├── severity.py # Impact-based classification rules
|
|
133
|
+
│ ├── templates.py # Plain-English templates per finding sub-type
|
|
134
|
+
│ ├── composer.py # Template dispatch
|
|
135
|
+
│ └── pipeline.py # classify → compose → sort
|
|
136
|
+
├── reports/
|
|
137
|
+
│ └── pdf.py # Professional PDF with reportlab
|
|
138
|
+
└── cli.py # argparse CLI, pipeline orchestration
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Requirements
|
|
144
|
+
|
|
145
|
+
- Python 3.10+
|
|
146
|
+
- pandas >= 2.0
|
|
147
|
+
- openpyxl >= 3.1
|
|
148
|
+
- reportlab >= 4.0
|
|
149
|
+
- defusedxml >= 0.7
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""datascope.analyzers -- detectors that produce Finding objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datascope.analyzers.type_consistency import analyze_type_consistency
|
|
6
|
+
from datascope.analyzers.sentinel import analyze_sentinels
|
|
7
|
+
from datascope.analyzers.format_check import analyze_leading_zeros, analyze_mixed_dates
|
|
8
|
+
from datascope.analyzers.cardinality import analyze_cardinality
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"analyze_type_consistency",
|
|
12
|
+
"analyze_sentinels",
|
|
13
|
+
"analyze_leading_zeros",
|
|
14
|
+
"analyze_mixed_dates",
|
|
15
|
+
"analyze_cardinality",
|
|
16
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Analyzer contract and registry.
|
|
2
|
+
|
|
3
|
+
There is no abstract base class. An analyzer is any callable matching::
|
|
4
|
+
|
|
5
|
+
Callable[[LoaderResult], list[Finding]]
|
|
6
|
+
|
|
7
|
+
It receives a :class:`~datascope.models.LoaderResult` and returns zero or
|
|
8
|
+
more :class:`~datascope.models.Finding` instances. The pipeline runner
|
|
9
|
+
(U9) will call each registered analyzer and merge the results.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Callable
|
|
15
|
+
|
|
16
|
+
from datascope.models import Finding, LoaderResult
|
|
17
|
+
|
|
18
|
+
#: Type alias documenting the analyzer contract.
|
|
19
|
+
Analyzer = Callable[[LoaderResult], list[Finding]]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Cardinality-anomaly detector.
|
|
2
|
+
|
|
3
|
+
Flags columns that are near-constant (very low uniqueness) or that look
|
|
4
|
+
like ID columns with unexpected duplicates (very high but not 100%
|
|
5
|
+
uniqueness).
|
|
6
|
+
|
|
7
|
+
Produces :class:`~datascope.models.Finding` instances with
|
|
8
|
+
:attr:`~datascope.models.FindingType.NEAR_CONSTANT` or
|
|
9
|
+
:attr:`~datascope.models.FindingType.DUPLICATE_IDS`.
|
|
10
|
+
|
|
11
|
+
Severity is *not* assigned here -- that is the severity classifier's job (U7).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections import Counter
|
|
17
|
+
|
|
18
|
+
from datascope.models import Finding, FindingType, LoaderResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Thresholds
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
_MIN_ROWS = 10 # Skip columns with fewer non-null rows
|
|
26
|
+
_NEAR_CONSTANT_MAX = 0.01 # uniqueness_ratio < this => near-constant
|
|
27
|
+
_SUSPECTED_ID_MIN = 0.95 # uniqueness_ratio > this AND < 1.0 => suspected-ID dups
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Detector
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def analyze_cardinality(result: LoaderResult) -> list[Finding]:
|
|
35
|
+
"""Detect cardinality anomalies in each column.
|
|
36
|
+
|
|
37
|
+
Two patterns are flagged:
|
|
38
|
+
|
|
39
|
+
* **Near-constant**: fewer than 1% of values are unique.
|
|
40
|
+
* **Suspected duplicate IDs**: more than 95% unique but less than
|
|
41
|
+
100%, suggesting an ID column with unexpected duplicates.
|
|
42
|
+
|
|
43
|
+
Columns with fewer than 10 non-null rows are skipped because
|
|
44
|
+
cardinality ratios are unreliable for tiny samples.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
result:
|
|
49
|
+
A :class:`~datascope.models.LoaderResult`.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
list[Finding]
|
|
54
|
+
One finding per column that exhibits a cardinality anomaly.
|
|
55
|
+
"""
|
|
56
|
+
findings: list[Finding] = []
|
|
57
|
+
|
|
58
|
+
for col_name in result.dataframe.columns:
|
|
59
|
+
series = result.dataframe[col_name]
|
|
60
|
+
filled = series.dropna()
|
|
61
|
+
total_count = len(filled)
|
|
62
|
+
|
|
63
|
+
if total_count < _MIN_ROWS:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
unique_count = filled.nunique()
|
|
67
|
+
uniqueness_ratio = round(unique_count / total_count, 4)
|
|
68
|
+
|
|
69
|
+
if uniqueness_ratio < _NEAR_CONSTANT_MAX:
|
|
70
|
+
# Near-constant column
|
|
71
|
+
value_counts = Counter(filled)
|
|
72
|
+
top_values = [
|
|
73
|
+
{"value": str(val), "count": cnt}
|
|
74
|
+
for val, cnt in value_counts.most_common(5)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
evidence = {
|
|
78
|
+
"unique_count": unique_count,
|
|
79
|
+
"total_count": total_count,
|
|
80
|
+
"uniqueness_ratio": uniqueness_ratio,
|
|
81
|
+
"top_values": top_values,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
findings.append(Finding(
|
|
85
|
+
field_name=col_name,
|
|
86
|
+
finding_type=FindingType.NEAR_CONSTANT,
|
|
87
|
+
evidence=evidence,
|
|
88
|
+
))
|
|
89
|
+
|
|
90
|
+
elif _SUSPECTED_ID_MIN < uniqueness_ratio < 1.0:
|
|
91
|
+
# Suspected duplicate IDs
|
|
92
|
+
value_counts = Counter(filled)
|
|
93
|
+
duplicate_values = [
|
|
94
|
+
str(val)
|
|
95
|
+
for val, cnt in value_counts.most_common()
|
|
96
|
+
if cnt > 1
|
|
97
|
+
][:10]
|
|
98
|
+
|
|
99
|
+
evidence = {
|
|
100
|
+
"unique_count": unique_count,
|
|
101
|
+
"total_count": total_count,
|
|
102
|
+
"uniqueness_ratio": uniqueness_ratio,
|
|
103
|
+
"duplicate_values": duplicate_values,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
findings.append(Finding(
|
|
107
|
+
field_name=col_name,
|
|
108
|
+
finding_type=FindingType.DUPLICATE_IDS,
|
|
109
|
+
evidence=evidence,
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
return findings
|