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.
Files changed (150) hide show
  1. dataforge/__init__.py +204 -0
  2. dataforge/__main__.py +5 -0
  3. dataforge/agent/__init__.py +16 -0
  4. dataforge/agent/providers.py +259 -0
  5. dataforge/agent/scratchpad.py +183 -0
  6. dataforge/agent/tool_actions.py +343 -0
  7. dataforge/bench/__init__.py +31 -0
  8. dataforge/bench/core.py +426 -0
  9. dataforge/bench/groq_client.py +386 -0
  10. dataforge/bench/methods.py +443 -0
  11. dataforge/bench/report.py +309 -0
  12. dataforge/bench/runner.py +247 -0
  13. dataforge/causal/__init__.py +21 -0
  14. dataforge/causal/dag.py +174 -0
  15. dataforge/causal/pc.py +232 -0
  16. dataforge/causal/root_cause.py +193 -0
  17. dataforge/cli/__init__.py +50 -0
  18. dataforge/cli/audit.py +70 -0
  19. dataforge/cli/bench.py +154 -0
  20. dataforge/cli/common.py +267 -0
  21. dataforge/cli/constraints.py +407 -0
  22. dataforge/cli/profile.py +147 -0
  23. dataforge/cli/release.py +166 -0
  24. dataforge/cli/repair.py +407 -0
  25. dataforge/cli/revert.py +139 -0
  26. dataforge/cli/watch.py +144 -0
  27. dataforge/datasets/__init__.py +25 -0
  28. dataforge/datasets/embedded/hospital/clean.csv +11 -0
  29. dataforge/datasets/embedded/hospital/dirty.csv +11 -0
  30. dataforge/datasets/real_world.py +290 -0
  31. dataforge/datasets/registry.py +103 -0
  32. dataforge/detectors/__init__.py +80 -0
  33. dataforge/detectors/base.py +145 -0
  34. dataforge/detectors/decimal_shift.py +166 -0
  35. dataforge/detectors/fd_violation.py +157 -0
  36. dataforge/detectors/type_mismatch.py +173 -0
  37. dataforge/engine/__init__.py +39 -0
  38. dataforge/engine/repair.py +905 -0
  39. dataforge/env/__init__.py +22 -0
  40. dataforge/env/environment.py +883 -0
  41. dataforge/env/observation.py +61 -0
  42. dataforge/env/openenv_core.py +161 -0
  43. dataforge/env/reward.py +128 -0
  44. dataforge/env/server.py +176 -0
  45. dataforge/evaluation_contract.py +76 -0
  46. dataforge/fixtures/hospital_10rows.csv +11 -0
  47. dataforge/fixtures/hospital_schema.yaml +17 -0
  48. dataforge/http/__init__.py +1 -0
  49. dataforge/http/problem.py +103 -0
  50. dataforge/integrations/__init__.py +1 -0
  51. dataforge/integrations/dbt.py +164 -0
  52. dataforge/observability.py +76 -0
  53. dataforge/py.typed +1 -0
  54. dataforge/release/__init__.py +1 -0
  55. dataforge/release/doctor.py +367 -0
  56. dataforge/release/full_vision.py +702 -0
  57. dataforge/release/gate.py +861 -0
  58. dataforge/release/playground_check.py +411 -0
  59. dataforge/repair_contract.py +468 -0
  60. dataforge/repairers/__init__.py +88 -0
  61. dataforge/repairers/base.py +77 -0
  62. dataforge/repairers/decimal_shift.py +43 -0
  63. dataforge/repairers/fd_violation.py +225 -0
  64. dataforge/repairers/type_mismatch.py +73 -0
  65. dataforge/safety/__init__.py +5 -0
  66. dataforge/safety/adversarial/attack_01_phone_pii.yaml +8 -0
  67. dataforge/safety/adversarial/attack_02_phone_pii.yaml +8 -0
  68. dataforge/safety/adversarial/attack_03_phone_pii.yaml +8 -0
  69. dataforge/safety/adversarial/attack_04_phone_pii.yaml +8 -0
  70. dataforge/safety/adversarial/attack_05_phone_pii.yaml +8 -0
  71. dataforge/safety/adversarial/attack_06_phone_pii.yaml +8 -0
  72. dataforge/safety/adversarial/attack_07_phone_pii.yaml +8 -0
  73. dataforge/safety/adversarial/attack_08_phone_pii.yaml +8 -0
  74. dataforge/safety/adversarial/attack_09_phone_pii.yaml +8 -0
  75. dataforge/safety/adversarial/attack_10_phone_pii.yaml +8 -0
  76. dataforge/safety/adversarial/attack_11_ssn_pii.yaml +8 -0
  77. dataforge/safety/adversarial/attack_12_ssn_pii.yaml +8 -0
  78. dataforge/safety/adversarial/attack_13_ssn_pii.yaml +8 -0
  79. dataforge/safety/adversarial/attack_14_ssn_pii.yaml +8 -0
  80. dataforge/safety/adversarial/attack_15_ssn_pii.yaml +8 -0
  81. dataforge/safety/adversarial/attack_16_ssn_pii.yaml +8 -0
  82. dataforge/safety/adversarial/attack_17_ssn_pii.yaml +8 -0
  83. dataforge/safety/adversarial/attack_18_ssn_pii.yaml +8 -0
  84. dataforge/safety/adversarial/attack_19_ssn_pii.yaml +8 -0
  85. dataforge/safety/adversarial/attack_20_ssn_pii.yaml +8 -0
  86. dataforge/safety/adversarial/attack_21_email_pii.yaml +8 -0
  87. dataforge/safety/adversarial/attack_22_email_pii.yaml +8 -0
  88. dataforge/safety/adversarial/attack_23_email_pii.yaml +8 -0
  89. dataforge/safety/adversarial/attack_24_email_pii.yaml +8 -0
  90. dataforge/safety/adversarial/attack_25_email_pii.yaml +8 -0
  91. dataforge/safety/adversarial/attack_26_email_pii.yaml +8 -0
  92. dataforge/safety/adversarial/attack_27_email_pii.yaml +8 -0
  93. dataforge/safety/adversarial/attack_28_email_pii.yaml +8 -0
  94. dataforge/safety/adversarial/attack_29_email_pii.yaml +8 -0
  95. dataforge/safety/adversarial/attack_30_email_pii.yaml +8 -0
  96. dataforge/safety/adversarial/attack_31_row_delete.yaml +7 -0
  97. dataforge/safety/adversarial/attack_32_row_delete.yaml +8 -0
  98. dataforge/safety/adversarial/attack_33_row_delete.yaml +7 -0
  99. dataforge/safety/adversarial/attack_34_row_delete.yaml +7 -0
  100. dataforge/safety/adversarial/attack_35_row_delete.yaml +7 -0
  101. dataforge/safety/adversarial/attack_36_row_delete.yaml +11 -0
  102. dataforge/safety/adversarial/attack_37_row_delete.yaml +7 -0
  103. dataforge/safety/adversarial/attack_38_row_delete.yaml +7 -0
  104. dataforge/safety/adversarial/attack_39_row_delete.yaml +8 -0
  105. dataforge/safety/adversarial/attack_40_row_delete.yaml +7 -0
  106. dataforge/safety/adversarial/attack_41_row_delete.yaml +7 -0
  107. dataforge/safety/adversarial/attack_42_row_delete.yaml +7 -0
  108. dataforge/safety/adversarial/attack_43_row_delete.yaml +7 -0
  109. dataforge/safety/adversarial/attack_44_row_delete.yaml +7 -0
  110. dataforge/safety/adversarial/attack_45_row_delete.yaml +8 -0
  111. dataforge/safety/adversarial/attack_46_row_delete.yaml +8 -0
  112. dataforge/safety/adversarial/attack_47_row_delete.yaml +7 -0
  113. dataforge/safety/adversarial/attack_48_row_delete.yaml +7 -0
  114. dataforge/safety/adversarial/attack_49_row_delete.yaml +8 -0
  115. dataforge/safety/adversarial/attack_50_row_delete.yaml +7 -0
  116. dataforge/safety/constitution.py +307 -0
  117. dataforge/safety/constitutions/default.yaml +40 -0
  118. dataforge/safety/filter.py +134 -0
  119. dataforge/schema_inference.py +620 -0
  120. dataforge/stores/__init__.py +46 -0
  121. dataforge/stores/base.py +73 -0
  122. dataforge/stores/cloud.py +78 -0
  123. dataforge/stores/csv.py +94 -0
  124. dataforge/stores/duckdb.py +313 -0
  125. dataforge/stores/patch_plan.py +178 -0
  126. dataforge/stores/registry.py +82 -0
  127. dataforge/stores/repair.py +121 -0
  128. dataforge/stores/revert.py +22 -0
  129. dataforge/stores/sql.py +27 -0
  130. dataforge/table.py +228 -0
  131. dataforge/transactions/__init__.py +34 -0
  132. dataforge/transactions/files.py +96 -0
  133. dataforge/transactions/log.py +613 -0
  134. dataforge/transactions/revert.py +102 -0
  135. dataforge/transactions/txn.py +104 -0
  136. dataforge/ui/__init__.py +1 -0
  137. dataforge/ui/profile_view.py +136 -0
  138. dataforge/ui/repair_diff.py +91 -0
  139. dataforge/verifier/__init__.py +55 -0
  140. dataforge/verifier/constraint_ir.py +155 -0
  141. dataforge/verifier/explain.py +47 -0
  142. dataforge/verifier/gate.py +5 -0
  143. dataforge/verifier/schema.py +111 -0
  144. dataforge/verifier/smt.py +433 -0
  145. dataforge_07-0.1.0.dist-info/METADATA +436 -0
  146. dataforge_07-0.1.0.dist-info/RECORD +150 -0
  147. dataforge_07-0.1.0.dist-info/WHEEL +5 -0
  148. dataforge_07-0.1.0.dist-info/entry_points.txt +3 -0
  149. dataforge_07-0.1.0.dist-info/licenses/LICENSE +176 -0
  150. 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
+ ]