""" Pre-execution validator. Checks that a resolved intent is compatible with the dataframe schema before any data gets touched. This is the safety net that stops "increase the name column" from ever reaching Polars. """ from __future__ import annotations from typing import Optional import polars as pl from config import DATA_DIR from core.column_registry import column_registry # Operations that REQUIRE numeric dtype on the target column NUMERIC_ONLY_OPS = {"increase", "decrease", "sum", "average", "min", "max"} def get_schema(session_id: str) -> Optional[pl.Schema]: """Read just the Parquet metadata to get column dtypes — no data loaded.""" import os from services.session_manager import session_manager pq_path = session_manager.get_filepath(session_id) if not os.path.exists(pq_path): return None try: schema = pl.scan_parquet(pq_path).collect_schema() return schema except Exception: return None def validate_intent(session_id: str, intent: dict) -> Optional[str]: """Return an error string if the intent is invalid, else ``None``.""" schema = get_schema(session_id) if schema is None: return "Session ka data nahi mila — dobara upload karo" col = intent.get("column") op = intent.get("operation", "") # ── Column existence (skip for operations that don't target a column) ── no_column_ops = {"remove_duplicates"} if op not in no_column_ops and col: if col not in schema: return f"'{col}' column nahi hai file mein" # ── Numeric dtype check ──────────────────────────────────────────── if op in NUMERIC_ONLY_OPS and col: dtype = schema[col] if dtype not in (pl.Float64, pl.Int64, pl.Int32, pl.Float32, pl.UInt64, pl.UInt32): return f"'{col}' numeric column nahi hai ({dtype}), ye operation apply nahi ho sakta" # ── Filter: need condition + value ────────────────────────────────── if op == "filter": if intent.get("condition") is None or intent.get("filter_value") is None: return "Filter ke liye condition aur value dono chahiye, jaise: 'salary > 50000 dikhao'" # ── Find & Replace: need old + new ────────────────────────────────── if op == "find_replace": if not intent.get("old_value") or not intent.get("new_value"): return "Find & Replace ke liye purana aur naya value dono chahiye, jaise: 'Active ko Inactive se badlo'" # ── Rename: need new_name ─────────────────────────────────────────── if op == "rename_column": if not intent.get("new_name"): return "Rename ke liye naya naam chahiye, jaise: 'name ko full_name rename karo'" return None # All good