dataforge-07 0.1.0__py3-none-any.whl
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.
- dataforge/__init__.py +204 -0
- dataforge/__main__.py +5 -0
- dataforge/agent/__init__.py +16 -0
- dataforge/agent/providers.py +259 -0
- dataforge/agent/scratchpad.py +183 -0
- dataforge/agent/tool_actions.py +343 -0
- dataforge/bench/__init__.py +31 -0
- dataforge/bench/core.py +426 -0
- dataforge/bench/groq_client.py +386 -0
- dataforge/bench/methods.py +443 -0
- dataforge/bench/report.py +309 -0
- dataforge/bench/runner.py +247 -0
- dataforge/causal/__init__.py +21 -0
- dataforge/causal/dag.py +174 -0
- dataforge/causal/pc.py +232 -0
- dataforge/causal/root_cause.py +193 -0
- dataforge/cli/__init__.py +50 -0
- dataforge/cli/audit.py +70 -0
- dataforge/cli/bench.py +154 -0
- dataforge/cli/common.py +267 -0
- dataforge/cli/constraints.py +407 -0
- dataforge/cli/profile.py +147 -0
- dataforge/cli/release.py +166 -0
- dataforge/cli/repair.py +407 -0
- dataforge/cli/revert.py +139 -0
- dataforge/cli/watch.py +144 -0
- dataforge/datasets/__init__.py +25 -0
- dataforge/datasets/embedded/hospital/clean.csv +11 -0
- dataforge/datasets/embedded/hospital/dirty.csv +11 -0
- dataforge/datasets/real_world.py +290 -0
- dataforge/datasets/registry.py +103 -0
- dataforge/detectors/__init__.py +80 -0
- dataforge/detectors/base.py +145 -0
- dataforge/detectors/decimal_shift.py +166 -0
- dataforge/detectors/fd_violation.py +157 -0
- dataforge/detectors/type_mismatch.py +173 -0
- dataforge/engine/__init__.py +39 -0
- dataforge/engine/repair.py +905 -0
- dataforge/env/__init__.py +22 -0
- dataforge/env/environment.py +883 -0
- dataforge/env/observation.py +61 -0
- dataforge/env/openenv_core.py +161 -0
- dataforge/env/reward.py +128 -0
- dataforge/env/server.py +176 -0
- dataforge/evaluation_contract.py +76 -0
- dataforge/fixtures/hospital_10rows.csv +11 -0
- dataforge/fixtures/hospital_schema.yaml +17 -0
- dataforge/http/__init__.py +1 -0
- dataforge/http/problem.py +103 -0
- dataforge/integrations/__init__.py +1 -0
- dataforge/integrations/dbt.py +164 -0
- dataforge/observability.py +76 -0
- dataforge/py.typed +1 -0
- dataforge/release/__init__.py +1 -0
- dataforge/release/doctor.py +367 -0
- dataforge/release/full_vision.py +702 -0
- dataforge/release/gate.py +861 -0
- dataforge/release/playground_check.py +411 -0
- dataforge/repair_contract.py +468 -0
- dataforge/repairers/__init__.py +88 -0
- dataforge/repairers/base.py +77 -0
- dataforge/repairers/decimal_shift.py +43 -0
- dataforge/repairers/fd_violation.py +225 -0
- dataforge/repairers/type_mismatch.py +73 -0
- dataforge/safety/__init__.py +5 -0
- dataforge/safety/adversarial/attack_01_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_02_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_03_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_04_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_05_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_06_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_07_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_08_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_09_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_10_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_11_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_12_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_13_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_14_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_15_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_16_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_17_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_18_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_19_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_20_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_21_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_22_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_23_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_24_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_25_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_26_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_27_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_28_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_29_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_30_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_31_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_32_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_33_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_34_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_35_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_36_row_delete.yaml +11 -0
- dataforge/safety/adversarial/attack_37_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_38_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_39_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_40_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_41_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_42_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_43_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_44_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_45_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_46_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_47_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_48_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_49_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_50_row_delete.yaml +7 -0
- dataforge/safety/constitution.py +307 -0
- dataforge/safety/constitutions/default.yaml +40 -0
- dataforge/safety/filter.py +134 -0
- dataforge/schema_inference.py +620 -0
- dataforge/stores/__init__.py +46 -0
- dataforge/stores/base.py +73 -0
- dataforge/stores/cloud.py +78 -0
- dataforge/stores/csv.py +94 -0
- dataforge/stores/duckdb.py +313 -0
- dataforge/stores/patch_plan.py +178 -0
- dataforge/stores/registry.py +82 -0
- dataforge/stores/repair.py +121 -0
- dataforge/stores/revert.py +22 -0
- dataforge/stores/sql.py +27 -0
- dataforge/table.py +228 -0
- dataforge/transactions/__init__.py +34 -0
- dataforge/transactions/files.py +96 -0
- dataforge/transactions/log.py +613 -0
- dataforge/transactions/revert.py +102 -0
- dataforge/transactions/txn.py +104 -0
- dataforge/ui/__init__.py +1 -0
- dataforge/ui/profile_view.py +136 -0
- dataforge/ui/repair_diff.py +91 -0
- dataforge/verifier/__init__.py +55 -0
- dataforge/verifier/constraint_ir.py +155 -0
- dataforge/verifier/explain.py +47 -0
- dataforge/verifier/gate.py +5 -0
- dataforge/verifier/schema.py +111 -0
- dataforge/verifier/smt.py +433 -0
- dataforge_07-0.1.0.dist-info/METADATA +436 -0
- dataforge_07-0.1.0.dist-info/RECORD +150 -0
- dataforge_07-0.1.0.dist-info/WHEEL +5 -0
- dataforge_07-0.1.0.dist-info/entry_points.txt +3 -0
- dataforge_07-0.1.0.dist-info/licenses/LICENSE +176 -0
- dataforge_07-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Detector for type-mismatch anomalies in tabular data.
|
|
2
|
+
|
|
3
|
+
Identifies values whose inferred type conflicts with the dominant type of
|
|
4
|
+
their column. Examples: a pure-numeric string ``"12345"`` in a column of
|
|
5
|
+
person names, or ``"N/A"`` in a column where 90% of values parse as float.
|
|
6
|
+
|
|
7
|
+
The detector is **pure**: no LLM calls, no I/O, no side effects.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
from dataforge.detectors.base import Issue, Schema, Severity
|
|
15
|
+
from dataforge.table import TableLike, column_names, column_values
|
|
16
|
+
|
|
17
|
+
# Compiled regexes for type inference.
|
|
18
|
+
_NUMERIC_RE = re.compile(r"^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$")
|
|
19
|
+
_DATE_RE = re.compile(
|
|
20
|
+
r"^\d{4}[-/]\d{2}[-/]\d{2}" # YYYY-MM-DD or YYYY/MM/DD prefix
|
|
21
|
+
r"([ T]\d{2}:\d{2}(:\d{2})?)?$" # optional time component
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Minimum ratio of the dominant type for a column to be considered typed.
|
|
25
|
+
# Below this, the column is genuinely mixed and we don't flag individuals.
|
|
26
|
+
_DOMINANCE_THRESHOLD = 0.65
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _classify_value(value: str) -> str:
|
|
30
|
+
"""Classify a single string value into a type category.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
value: The string value to classify.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
One of ``"numeric"``, ``"date"``, or ``"string"``.
|
|
37
|
+
"""
|
|
38
|
+
stripped = value.strip()
|
|
39
|
+
if _NUMERIC_RE.match(stripped):
|
|
40
|
+
return "numeric"
|
|
41
|
+
if _DATE_RE.match(stripped):
|
|
42
|
+
return "date"
|
|
43
|
+
return "string"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TypeMismatchDetector:
|
|
47
|
+
"""Detects values whose type conflicts with the column's dominant type.
|
|
48
|
+
|
|
49
|
+
For each column, the detector infers the dominant type by classifying
|
|
50
|
+
every non-null value as numeric, date, or string. Values belonging to
|
|
51
|
+
the minority type are flagged as ``type_mismatch`` issues.
|
|
52
|
+
|
|
53
|
+
A column must have a clear dominant type (>= 65% of values) for any
|
|
54
|
+
flags to be raised. Ambiguous 50/50 splits are not flagged.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> import pandas as pd
|
|
58
|
+
>>> detector = TypeMismatchDetector()
|
|
59
|
+
>>> df = pd.DataFrame({"age": ["25", "30", "N/A", "40"]})
|
|
60
|
+
>>> issues = detector.detect(df)
|
|
61
|
+
>>> len(issues)
|
|
62
|
+
1
|
|
63
|
+
>>> issues[0].actual
|
|
64
|
+
'N/A'
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def detect(self, df: TableLike, schema: Schema | None = None) -> list[Issue]:
|
|
68
|
+
"""Detect type-mismatch issues in the DataFrame.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
df: The input DataFrame to analyze. Values are expected to be
|
|
72
|
+
strings (loaded with ``dtype=str``).
|
|
73
|
+
schema: Optional declared schema (not used by this detector yet;
|
|
74
|
+
reserved for future type-declaration awareness).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A list of Issue objects for each cell whose type conflicts with
|
|
78
|
+
the column's dominant type.
|
|
79
|
+
"""
|
|
80
|
+
issues: list[Issue] = []
|
|
81
|
+
|
|
82
|
+
for col_name in column_names(df):
|
|
83
|
+
col_issues = self._check_column(df, str(col_name))
|
|
84
|
+
issues.extend(col_issues)
|
|
85
|
+
|
|
86
|
+
return issues
|
|
87
|
+
|
|
88
|
+
def _check_column(self, df: TableLike, col_name: str) -> list[Issue]:
|
|
89
|
+
"""Check a single column for type mismatches.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
df: The DataFrame containing the column.
|
|
93
|
+
col_name: Name of the column to check.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Issues found in this column.
|
|
97
|
+
"""
|
|
98
|
+
# Collect (index, value, type) for non-null entries.
|
|
99
|
+
classified: list[tuple[int, str, str]] = []
|
|
100
|
+
for row_idx, val in enumerate(column_values(df, col_name)):
|
|
101
|
+
if val is None:
|
|
102
|
+
continue
|
|
103
|
+
str_val = str(val).strip()
|
|
104
|
+
if not str_val:
|
|
105
|
+
continue
|
|
106
|
+
classified.append((row_idx, str_val, _classify_value(str_val)))
|
|
107
|
+
|
|
108
|
+
if len(classified) < 2:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
# Count type frequencies.
|
|
112
|
+
type_counts: dict[str, int] = {}
|
|
113
|
+
for _, _, vtype in classified:
|
|
114
|
+
type_counts[vtype] = type_counts.get(vtype, 0) + 1
|
|
115
|
+
|
|
116
|
+
total = len(classified)
|
|
117
|
+
dominant_type = max(type_counts, key=lambda t: type_counts[t])
|
|
118
|
+
dominant_count = type_counts[dominant_type]
|
|
119
|
+
dominance_ratio = dominant_count / total
|
|
120
|
+
|
|
121
|
+
if dominance_ratio < _DOMINANCE_THRESHOLD:
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
# Flag minority-type values.
|
|
125
|
+
issues: list[Issue] = []
|
|
126
|
+
for row_idx, str_val, vtype in classified:
|
|
127
|
+
if vtype == dominant_type:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Scale confidence: dominance of 0.65 -> 0.70, 0.75 -> 0.85, 1.0 -> 0.95.
|
|
131
|
+
# A single minority value in a strongly typed column is a strong signal.
|
|
132
|
+
minority_count = total - dominant_count
|
|
133
|
+
scarcity_boost = max(0.0, 0.10 * (1.0 - minority_count / total))
|
|
134
|
+
confidence = min(0.95, dominance_ratio + scarcity_boost)
|
|
135
|
+
reason = self._build_reason(str_val, vtype, dominant_type, col_name)
|
|
136
|
+
|
|
137
|
+
issues.append(
|
|
138
|
+
Issue(
|
|
139
|
+
row=row_idx,
|
|
140
|
+
column=col_name,
|
|
141
|
+
issue_type="type_mismatch",
|
|
142
|
+
severity=Severity.REVIEW,
|
|
143
|
+
confidence=round(confidence, 2),
|
|
144
|
+
actual=str_val,
|
|
145
|
+
reason=reason,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return issues
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _build_reason(value: str, value_type: str, dominant_type: str, col_name: str) -> str:
|
|
153
|
+
"""Build a human-readable reason string.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
value: The actual cell value.
|
|
157
|
+
value_type: Inferred type of the value.
|
|
158
|
+
dominant_type: Dominant type of the column.
|
|
159
|
+
col_name: Column name for context.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A descriptive reason string.
|
|
163
|
+
"""
|
|
164
|
+
if dominant_type == "numeric" and value_type == "string":
|
|
165
|
+
return f"Value '{value}' is non-numeric in predominantly numeric column '{col_name}'"
|
|
166
|
+
if dominant_type == "string" and value_type == "numeric":
|
|
167
|
+
return f"Value '{value}' looks numeric in predominantly string column '{col_name}'"
|
|
168
|
+
if dominant_type == "string" and value_type == "date":
|
|
169
|
+
return f"Value '{value}' looks like a date in predominantly string column '{col_name}'"
|
|
170
|
+
return (
|
|
171
|
+
f"Value '{value}' (type: {value_type}) conflicts with "
|
|
172
|
+
f"dominant type '{dominant_type}' in column '{col_name}'"
|
|
173
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Public backend engine APIs for DataForge."""
|
|
2
|
+
|
|
3
|
+
from dataforge.engine.repair import (
|
|
4
|
+
CandidateFix,
|
|
5
|
+
CandidateRepair,
|
|
6
|
+
ProofObligation,
|
|
7
|
+
RepairFailure,
|
|
8
|
+
RepairMode,
|
|
9
|
+
RepairPipelineRequest,
|
|
10
|
+
RepairPipelineResult,
|
|
11
|
+
RepairReceipt,
|
|
12
|
+
RootCause,
|
|
13
|
+
VerifiedFix,
|
|
14
|
+
apply_fixes_to_csv,
|
|
15
|
+
apply_transaction,
|
|
16
|
+
create_repair_transaction,
|
|
17
|
+
propose_repairs,
|
|
18
|
+
run_repair_pipeline,
|
|
19
|
+
source_path_lock,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"CandidateFix",
|
|
24
|
+
"CandidateRepair",
|
|
25
|
+
"ProofObligation",
|
|
26
|
+
"RepairFailure",
|
|
27
|
+
"RepairMode",
|
|
28
|
+
"RepairPipelineRequest",
|
|
29
|
+
"RepairPipelineResult",
|
|
30
|
+
"RepairReceipt",
|
|
31
|
+
"RootCause",
|
|
32
|
+
"VerifiedFix",
|
|
33
|
+
"apply_fixes_to_csv",
|
|
34
|
+
"apply_transaction",
|
|
35
|
+
"create_repair_transaction",
|
|
36
|
+
"propose_repairs",
|
|
37
|
+
"run_repair_pipeline",
|
|
38
|
+
"source_path_lock",
|
|
39
|
+
]
|