PlainSQL / backend /app /ai_features /insights.py
LalitChaudhari3's picture
feat: synchronize text-to-sql-bot codebase with Hugging Face Space repository, including Docker build configurations
6086e71
Raw
History Blame Contribute Delete
5.16 kB
"""
Auto Insights Generator β€” Statistical analysis and pattern detection on query results.
Generates human-readable insights without LLM (pure statistical analysis).
"""
import structlog
from collections import Counter
logger = structlog.get_logger()
class InsightsGenerator:
"""Generates statistical insights from query result data."""
def generate(self, results: list[dict], query: str = "") -> list[str]:
"""
Analyze query results and generate human-readable insights.
Uses statistical methods β€” no LLM needed.
"""
if not results:
return ["No data available for analysis."]
insights = []
# ── Basic stats ──────────────────────────────────
row_count = len(results)
col_count = len(results[0]) if results else 0
insights.append(f"πŸ“Š Dataset: **{row_count}** records across **{col_count}** columns")
# ── Classify columns ─────────────────────────────
numeric_cols = []
text_cols = []
date_cols = []
columns = list(results[0].keys()) if results else []
for col in columns:
sample = results[0].get(col)
col_lower = col.lower()
if any(d in col_lower for d in ["date", "time", "created", "updated"]):
date_cols.append(col)
elif isinstance(sample, (int, float)):
numeric_cols.append(col)
else:
try:
if sample is not None:
float(sample)
numeric_cols.append(col)
else:
text_cols.append(col)
except (ValueError, TypeError):
text_cols.append(col)
# ── Numeric column analysis ──────────────────────
for col in numeric_cols[:4]:
values = self._extract_numeric_values(results, col)
if not values:
continue
avg = sum(values) / len(values)
min_val = min(values)
max_val = max(values)
total = sum(values)
label = col.replace("_", " ").title()
insights.append(
f"**{label}**: Total {total:,.2f} | "
f"Avg {avg:,.2f} | Range [{min_val:,.2f} β€” {max_val:,.2f}]"
)
# Outlier detection (IQR method)
outliers = self._detect_outliers(values)
if outliers:
insights.append(
f"⚠️ **{len(outliers)} outliers** detected in {label} "
f"(values: {', '.join(f'{v:,.2f}' for v in outliers[:3])})"
)
# Concentration analysis
if len(values) > 1:
top_val = max(values)
top_pct = (top_val / total * 100) if total > 0 else 0
if top_pct > 30:
insights.append(f"🎯 Top value in **{label}** accounts for {top_pct:.1f}% of total")
# ── Text column analysis ─────────────────────────
for col in text_cols[:2]:
str_values = [str(row.get(col, "")) for row in results if row.get(col)]
if not str_values:
continue
counter = Counter(str_values)
unique_count = len(counter)
label = col.replace("_", " ").title()
if unique_count == row_count:
insights.append(f"πŸ”‘ **{label}** has all unique values")
elif unique_count <= 10:
top_items = counter.most_common(3)
distribution = ", ".join(f"'{k}' ({v})" for k, v in top_items)
insights.append(f"🏷️ **{label}** distribution: {distribution}")
# ── Trend detection ──────────────────────────────
if date_cols and numeric_cols and len(results) >= 3:
insights.append("πŸ“ˆ Time-series data detected β€” trend analysis available")
return insights
def _extract_numeric_values(self, results: list[dict], col: str) -> list[float]:
"""Extract numeric values from a column, skipping nulls."""
values = []
for row in results:
v = row.get(col)
if v is not None:
try:
values.append(float(v))
except (ValueError, TypeError):
continue
return values
def _detect_outliers(self, values: list[float]) -> list[float]:
"""Detect outliers using the IQR method."""
if len(values) < 4:
return []
sorted_vals = sorted(values)
n = len(sorted_vals)
q1 = sorted_vals[int(n * 0.25)]
q3 = sorted_vals[int(n * 0.75)]
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
return [v for v in values if v < lower or v > upper]