Download src/t2s/eval/error_analysis.py from Ashoksainibadu/text2sql-assistant: direct link, hf CLI and curl.
- Browser
- Download file 5.01 kB
-
https://huggingface.co/spaces/Ashoksainibadu/text2sql-assistant/resolve/main/src/t2s/eval/error_analysis.py
- Command line
-
hf download hf://spaces/Ashoksainibadu/text2sql-assistant/src/t2s/eval/error_analysis.py
-
curl -L -o error_analysis.py https://huggingface.co/spaces/Ashoksainibadu/text2sql-assistant/resolve/main/src/t2s/eval/error_analysis.py
5.01 kB
| """Error analysis and generalization slicing for Text-to-SQL evaluation.""" | |
| import re | |
| from typing import Any, Dict, List | |
| def categorize_error( | |
| pred_sql: str, | |
| gold_sql: str, | |
| exec_success: bool, | |
| is_valid_sql: bool, | |
| ) -> str: | |
| """Categorize the type of failure for a predicted SQL query. | |
| Categories: | |
| - correct | |
| - syntax_error | |
| - missing_group_by | |
| - wrong_aggregation | |
| - wrong_join | |
| - wrong_column | |
| - value_mismatch | |
| - other_semantic_error | |
| """ | |
| if exec_success: | |
| return "correct" | |
| if not is_valid_sql: | |
| return "syntax_error" | |
| pred_upper = pred_sql.upper() | |
| gold_upper = gold_sql.upper() | |
| # Check for GROUP BY mismatch | |
| if ("GROUP BY" in gold_upper) and ("GROUP BY" not in pred_upper): | |
| return "missing_group_by" | |
| # Check for aggregation functions (COUNT, SUM, AVG, MIN, MAX) | |
| agg_funcs = ["COUNT(", "SUM(", "AVG(", "MIN(", "MAX("] | |
| gold_aggs = [f for f in agg_funcs if f in gold_upper] | |
| pred_aggs = [f for f in agg_funcs if f in pred_upper] | |
| if gold_aggs != pred_aggs: | |
| return "wrong_aggregation" | |
| # Check for JOIN mismatch | |
| if ("JOIN" in gold_upper) and ("JOIN" not in pred_upper): | |
| return "wrong_join" | |
| # Check if WHERE clause values differ | |
| where_gold = re.findall(r"'[^']+'|\b\d+\b", gold_sql) | |
| where_pred = re.findall(r"'[^']+'|\b\d+\b", pred_sql) | |
| if where_gold != where_pred: | |
| return "value_mismatch" | |
| # Check column names mismatch heuristics | |
| return "other_semantic_error" | |
| def compare_systems_diff( | |
| eval_a_results: List[Dict[str, Any]], | |
| eval_c_results: List[Dict[str, Any]], | |
| ) -> Dict[str, Any]: | |
| """Identify questions fixed or broken by System C compared to System A.""" | |
| fixed = [] | |
| broken = [] | |
| both_correct = [] | |
| both_wrong = [] | |
| for idx, (res_a, res_c) in enumerate(zip(eval_a_results, eval_c_results)): | |
| a_match = res_a.get("exec_match", False) | |
| c_match = res_c.get("exec_match", False) | |
| q = res_c.get("question", f"Question #{idx}") | |
| db = res_c.get("db_id", "") | |
| item = { | |
| "index": idx, | |
| "db_id": db, | |
| "question": q, | |
| "gold": res_c.get("gold", ""), | |
| "pred_a": res_a.get("pred", ""), | |
| "pred_c": res_c.get("pred", ""), | |
| } | |
| if not a_match and c_match: | |
| fixed.append(item) | |
| elif a_match and not c_match: | |
| broken.append(item) | |
| elif a_match and c_match: | |
| both_correct.append(item) | |
| else: | |
| both_wrong.append(item) | |
| return { | |
| "fixed_count": len(fixed), | |
| "broken_count": len(broken), | |
| "both_correct_count": len(both_correct), | |
| "both_wrong_count": len(both_wrong), | |
| "fixed_questions": fixed[:20], # sample up to 20 | |
| "broken_questions": broken[:20], | |
| } | |
| def compute_generalization_slices(detailed_results: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| """Compute accuracy sliced by database, SQL features (JOIN, GROUP BY, etc.).""" | |
| by_db: Dict[str, Dict[str, int]] = {} | |
| by_feature: Dict[str, Dict[str, int]] = { | |
| "has_join": {"total": 0, "correct": 0}, | |
| "has_group_by": {"total": 0, "correct": 0}, | |
| "has_order_by": {"total": 0, "correct": 0}, | |
| "has_subquery": {"total": 0, "correct": 0}, | |
| } | |
| for res in detailed_results: | |
| db = res.get("db_id", "unknown") | |
| correct = res.get("exec_match", False) | |
| if db not in by_db: | |
| by_db[db] = {"total": 0, "correct": 0} | |
| by_db[db]["total"] += 1 | |
| if correct: | |
| by_db[db]["correct"] += 1 | |
| gold = res.get("gold", "").upper() | |
| if "JOIN" in gold: | |
| by_feature["has_join"]["total"] += 1 | |
| if correct: | |
| by_feature["has_join"]["correct"] += 1 | |
| if "GROUP BY" in gold: | |
| by_feature["has_group_by"]["total"] += 1 | |
| if correct: | |
| by_feature["has_group_by"]["correct"] += 1 | |
| if "ORDER BY" in gold: | |
| by_feature["has_order_by"]["total"] += 1 | |
| if correct: | |
| by_feature["has_order_by"]["correct"] += 1 | |
| if "SELECT" in gold and gold.count("SELECT") > 1: | |
| by_feature["has_subquery"]["total"] += 1 | |
| if correct: | |
| by_feature["has_subquery"]["correct"] += 1 | |
| db_accuracy = {} | |
| for db, stats in by_db.items(): | |
| tot = stats["total"] | |
| db_accuracy[db] = { | |
| "total": tot, | |
| "correct": stats["correct"], | |
| "accuracy": stats["correct"] / tot if tot > 0 else 0.0, | |
| } | |
| feature_accuracy = {} | |
| for feat, stats in by_feature.items(): | |
| tot = stats["total"] | |
| feature_accuracy[feat] = { | |
| "total": tot, | |
| "correct": stats["correct"], | |
| "accuracy": stats["correct"] / tot if tot > 0 else 0.0, | |
| } | |
| return { | |
| "by_database": db_accuracy, | |
| "by_feature": feature_accuracy, | |
| } | |