Spaces:
Sleeping
Sleeping
Upload 15 files
Browse files- server/environment.py +3 -0
- server/graders.py +8 -1
- server/main.py +8 -1
- static/index.html +7 -2
server/environment.py
CHANGED
|
@@ -623,6 +623,9 @@ def analyze_uploaded_database(conn: sqlite3.Connection, schema_info: dict[str, d
|
|
| 623 |
ratio = profile["date_ratio"]
|
| 624 |
if not family:
|
| 625 |
continue
|
|
|
|
|
|
|
|
|
|
| 626 |
score = ratio
|
| 627 |
if family in {"numeric", "score_scale"}:
|
| 628 |
score += 0.15
|
|
|
|
| 623 |
ratio = profile["date_ratio"]
|
| 624 |
if not family:
|
| 625 |
continue
|
| 626 |
+
if family == "date" and ratio >= 0.98:
|
| 627 |
+
# Fully parseable date-like text does not need repair unless another anomaly exists.
|
| 628 |
+
continue
|
| 629 |
score = ratio
|
| 630 |
if family in {"numeric", "score_scale"}:
|
| 631 |
score += 0.15
|
server/graders.py
CHANGED
|
@@ -246,13 +246,20 @@ def _evaluate_fix_outcome(
|
|
| 246 |
original_rows = ground_truth.get("original_row_count", total_after)
|
| 247 |
preservation = min(1.0, total_after / max(1, original_rows))
|
| 248 |
improvement = max(0.0, parse_ratio - baseline_ratio)
|
|
|
|
| 249 |
desired_types = {
|
| 250 |
"boolean": {"BOOLEAN", "INTEGER", "NUMERIC"},
|
| 251 |
"date": {"DATE", "DATETIME", "TIMESTAMP", "TEXT"},
|
| 252 |
"numeric": {"REAL", "INTEGER", "NUMERIC", "FLOAT", "DOUBLE"},
|
| 253 |
}
|
| 254 |
type_fixed = current_declared in desired_types.get(issue_type, {"TEXT"}) and current_declared != original_declared
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
final_preservation = min(1.0, (preservation * 0.55) + (min(1.0, parse_ratio) * 0.45))
|
| 257 |
return issue_resolved, round(final_preservation, 4), True
|
| 258 |
try:
|
|
|
|
| 246 |
original_rows = ground_truth.get("original_row_count", total_after)
|
| 247 |
preservation = min(1.0, total_after / max(1, original_rows))
|
| 248 |
improvement = max(0.0, parse_ratio - baseline_ratio)
|
| 249 |
+
baseline_gap = max(0.0, 1.0 - baseline_ratio)
|
| 250 |
desired_types = {
|
| 251 |
"boolean": {"BOOLEAN", "INTEGER", "NUMERIC"},
|
| 252 |
"date": {"DATE", "DATETIME", "TIMESTAMP", "TEXT"},
|
| 253 |
"numeric": {"REAL", "INTEGER", "NUMERIC", "FLOAT", "DOUBLE"},
|
| 254 |
}
|
| 255 |
type_fixed = current_declared in desired_types.get(issue_type, {"TEXT"}) and current_declared != original_declared
|
| 256 |
+
near_clean = parse_ratio >= 0.98
|
| 257 |
+
meaningful_gain = improvement >= max(0.02, baseline_gap * 0.5)
|
| 258 |
+
issue_resolved = (
|
| 259 |
+
(parse_ratio >= 0.95 and type_fixed)
|
| 260 |
+
or meaningful_gain
|
| 261 |
+
or (near_clean and improvement > 0.005)
|
| 262 |
+
)
|
| 263 |
final_preservation = min(1.0, (preservation * 0.55) + (min(1.0, parse_ratio) * 0.45))
|
| 264 |
return issue_resolved, round(final_preservation, 4), True
|
| 265 |
try:
|
server/main.py
CHANGED
|
@@ -739,6 +739,13 @@ def is_aggregate_like_column_name(column_name: str) -> bool:
|
|
| 739 |
return bool(tokens and any(token in aggregate_tokens for token in tokens))
|
| 740 |
|
| 741 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
def get_db_schema(conn: sqlite3.Connection) -> list[dict]:
|
| 743 |
"""Extract schema information from a SQLite connection."""
|
| 744 |
schema = []
|
|
@@ -1700,7 +1707,7 @@ def _generate_local_insights(sql: str, rows: list, columns: list, row_count: int
|
|
| 1700 |
|
| 1701 |
# Duplicate detection
|
| 1702 |
for col in columns:
|
| 1703 |
-
if is_aggregate_like_column_name(col):
|
| 1704 |
continue
|
| 1705 |
values = [str(r.get(col, '')) for r in rows if r.get(col) is not None]
|
| 1706 |
unique = set(values)
|
|
|
|
| 739 |
return bool(tokens and any(token in aggregate_tokens for token in tokens))
|
| 740 |
|
| 741 |
|
| 742 |
+
def is_key_like_column_name(column_name: str) -> bool:
|
| 743 |
+
"""Heuristic for key-like result columns where duplicate values are suspicious."""
|
| 744 |
+
tokens = [token for token in re.split(r"[^a-z0-9]+", (column_name or "").lower()) if token]
|
| 745 |
+
key_tokens = {"id", "email", "phone", "ssn", "uuid", "employee", "patient"}
|
| 746 |
+
return bool(tokens and any(token in key_tokens for token in tokens))
|
| 747 |
+
|
| 748 |
+
|
| 749 |
def get_db_schema(conn: sqlite3.Connection) -> list[dict]:
|
| 750 |
"""Extract schema information from a SQLite connection."""
|
| 751 |
schema = []
|
|
|
|
| 1707 |
|
| 1708 |
# Duplicate detection
|
| 1709 |
for col in columns:
|
| 1710 |
+
if is_aggregate_like_column_name(col) or not is_key_like_column_name(col):
|
| 1711 |
continue
|
| 1712 |
values = [str(r.get(col, '')) for r in rows if r.get(col) is not None]
|
| 1713 |
unique = set(values)
|
static/index.html
CHANGED
|
@@ -680,6 +680,11 @@
|
|
| 680 |
const aggregateTokens = new Set(['avg', 'sum', 'count', 'total', 'min', 'max', 'rank', 'highest', 'lowest']);
|
| 681 |
return tokens.some(token => aggregateTokens.has(token));
|
| 682 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
|
| 684 |
// ββ Toast notification system ββ
|
| 685 |
let toastTimer = null;
|
|
@@ -1084,7 +1089,7 @@
|
|
| 1084 |
|
| 1085 |
// Duplicate detection
|
| 1086 |
for (const col of columns) {
|
| 1087 |
-
if (isAggregateLikeColumn(col)) continue;
|
| 1088 |
const values = rows.map(r => r[col]).filter(v => v !== null);
|
| 1089 |
const unique = new Set(values.map(String));
|
| 1090 |
if (unique.size < values.length * 0.8 && values.length > 5) {
|
|
@@ -1133,7 +1138,7 @@
|
|
| 1133 |
}
|
| 1134 |
|
| 1135 |
// Check for duplicates
|
| 1136 |
-
const suggestionCols = columns.filter(col => !isAggregateLikeColumn(col));
|
| 1137 |
const allValues = rows.flatMap(r => suggestionCols.map(col => r[col])).filter(v => v !== null);
|
| 1138 |
const duplicates = allValues.filter((v, i, a) => a.indexOf(v) !== i);
|
| 1139 |
if (suggestionCols.length > 0 && duplicates.length > rows.length * 0.1) {
|
|
|
|
| 680 |
const aggregateTokens = new Set(['avg', 'sum', 'count', 'total', 'min', 'max', 'rank', 'highest', 'lowest']);
|
| 681 |
return tokens.some(token => aggregateTokens.has(token));
|
| 682 |
}
|
| 683 |
+
function isKeyLikeColumn(col) {
|
| 684 |
+
const tokens = String(col || '').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
| 685 |
+
const keyTokens = new Set(['id', 'email', 'phone', 'ssn', 'uuid', 'employee', 'patient']);
|
| 686 |
+
return tokens.some(token => keyTokens.has(token));
|
| 687 |
+
}
|
| 688 |
|
| 689 |
// ββ Toast notification system ββ
|
| 690 |
let toastTimer = null;
|
|
|
|
| 1089 |
|
| 1090 |
// Duplicate detection
|
| 1091 |
for (const col of columns) {
|
| 1092 |
+
if (isAggregateLikeColumn(col) || !isKeyLikeColumn(col)) continue;
|
| 1093 |
const values = rows.map(r => r[col]).filter(v => v !== null);
|
| 1094 |
const unique = new Set(values.map(String));
|
| 1095 |
if (unique.size < values.length * 0.8 && values.length > 5) {
|
|
|
|
| 1138 |
}
|
| 1139 |
|
| 1140 |
// Check for duplicates
|
| 1141 |
+
const suggestionCols = columns.filter(col => !isAggregateLikeColumn(col) && isKeyLikeColumn(col));
|
| 1142 |
const allValues = rows.flatMap(r => suggestionCols.map(col => r[col])).filter(v => v !== null);
|
| 1143 |
const duplicates = allValues.filter((v, i, a) => a.indexOf(v) !== i);
|
| 1144 |
if (suggestionCols.length > 0 && duplicates.length > rows.length * 0.1) {
|