"""Drupal AI Eval Dashboard — POC. Reads EvalEval instance-level records from a HuggingFace dataset and renders a filterable comparison dashboard across models and skills. """ import json import shutil import tempfile from pathlib import Path import gradio as gr import pandas as pd from huggingface_hub import snapshot_download DATASET_REPO = "webchick/eval-results-poc" CSS = """ .dashboard-header { background: linear-gradient(135deg, #004F8C 0%, #0678BE 100%); padding: 24px 32px; border-radius: 12px; color: white; margin-bottom: 16px; } .dashboard-header h1 { margin: 0; font-size: 1.8em; font-weight: 700; color: white; } .dashboard-header p { margin: 6px 0 0; opacity: 0.9; font-size: 0.95em; color: white; } .dashboard-header a { color: white !important; text-decoration: underline; } .kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; } .kpi-card { background: white; border: 1px solid #e2e8f0; border-radius: 10px; padding: 16px; text-align: center; box-shadow: 0 1px 4px rgba(0,0,0,0.07); } .kpi-value { font-size: 2em; font-weight: 700; color: #0678BE; line-height: 1; } .kpi-label { font-size: 0.72em; color: #64748b; text-transform: uppercase; letter-spacing: 0.07em; margin-top: 6px; } """ HEADER_HTML = """

Drupal AI Eval Dashboard

How well do AI models handle Drupal-specific coding tasks? Results from ai_best_practices, stored in EvalEval envelope format.

""" def load_results() -> pd.DataFrame: """Download the dataset fresh and flatten instance records into a DataFrame.""" tmp = tempfile.mkdtemp() try: local_dir = snapshot_download( repo_id=DATASET_REPO, repo_type="dataset", local_dir=tmp, ) rows = [] for jsonl_file in sorted(Path(local_dir).rglob("*_samples.jsonl")): with open(jsonl_file) as f: for line in f: line = line.strip() if not line: continue try: rec = json.loads(line) except json.JSONDecodeError: continue if not rec.get("schema_version", "").startswith("instance_level_eval"): continue meta = rec.get("metadata") or {} perf = rec.get("performance") or {} usage = rec.get("token_usage") or {} ev = rec.get("evaluation") or {} inp = rec.get("input") or {} reference = inp.get("reference") or [] task = reference[0] if reference and reference[0] else inp.get("raw", "")[:100] rows.append({ "Namespace": meta.get("eval_namespace", "?"), "Skill": meta.get("skill_name", "?"), "Case": meta.get("case_id", "?"), "Model": rec.get("model_id", "?"), "Passed": bool(ev.get("is_correct")), "Task": task, "Latency (ms)": round(perf.get("latency_ms") or 0, 0), "Input tokens": usage.get("input_tokens", 0), "Output tokens": usage.get("output_tokens", 0), "Cost (USD)": round(float(meta.get("cost_usd") or 0), 6), "Run timestamp": meta.get("timestamp_run", ""), }) df = pd.DataFrame(rows) if not df.empty: df["Passed"] = df["Passed"].astype(bool) return df finally: shutil.rmtree(tmp, ignore_errors=True) def make_kpi_html(df: pd.DataFrame) -> str: if df.empty: return "" total = len(df) models = df["Model"].nunique() skills = df["Skill"].nunique() pass_rate = df["Passed"].mean() * 100 def card(value, label): return ( f'
' f'
{value}
' f'
{label}
' f'
' ) return ( f'
' f'{card(total, "Total runs")}' f'{card(models, "Models")}' f'{card(skills, "Skills")}' f'{card(f"{pass_rate:.0f}%", "Pass rate")}' f'
' ) def make_agg(df: pd.DataFrame) -> pd.DataFrame: if df.empty: return pd.DataFrame(columns=["Model", "Pass rate (%)", "Cases run"]) agg = ( df.assign(_p=df["Passed"].astype(int)) .groupby("Model")["_p"] .agg(["mean", "count"]) .reset_index() .rename(columns={"mean": "Pass rate (%)", "count": "Cases run"}) ) agg["Pass rate (%)"] = (agg["Pass rate (%)"] * 100).round(1) return agg # Numeric columns where lower = better (green) and higher = worse (red) _LOWER_IS_BETTER = ["Latency (ms)", "Input tokens", "Output tokens", "Cost (USD)"] _TABLE_CSS = """ """ def _heat_rgb(ratio: float) -> str: """Map 0→green, 1→red as pastel background colours.""" r = int(198 + (255 - 198) * ratio) g = int(239 - (239 - 199) * ratio) b = int(206 + (206 - 206) * ratio) return f"rgb({r},{g},{b})" def df_to_html_table(df: pd.DataFrame) -> str: """Render DataFrame as an HTML table with emoji pass/fail and heat-map colours.""" if df.empty: return "

No results found.

" # Pre-compute min/max for numeric gradient columns ranges: dict[str, tuple[float, float]] = {} for col in _LOWER_IS_BETTER: if col in df.columns: mn, mx = float(df[col].min()), float(df[col].max()) if mx > mn: ranges[col] = (mn, mx) cols = list(df.columns) # Header row ths = "".join(f"{c}" for c in cols) # Data rows rows_html = "" for _, row in df.iterrows(): cells = "" for col in cols: val = row[col] if col == "Passed": icon = "✅" if val else "❌" cells += f'{icon}' elif col in ranges: mn, mx = ranges[col] ratio = (float(val) - mn) / (mx - mn) bg = _heat_rgb(ratio) cells += ( f'' f'{val}' ) else: safe = str(val).replace("<", "<").replace(">", ">") cells += f"{safe}" rows_html += f"{cells}" return ( _TABLE_CSS + f'
' + f"{ths}" + f"{rows_html}" + "
" ) def apply_filters(df: pd.DataFrame, ns: str, skill: str, result: str) -> pd.DataFrame: filtered = df.copy() if ns != "(all)": filtered = filtered[filtered["Namespace"] == ns] if skill != "(all)": filtered = filtered[filtered["Skill"] == skill] if result == "Passed only": filtered = filtered[filtered["Passed"]] elif result == "Failed only": filtered = filtered[~filtered["Passed"]] return filtered def build_ui(): with gr.Blocks( title="Drupal AI Eval Dashboard", css=CSS, theme=gr.themes.Soft(), ) as demo: gr.HTML(HEADER_HTML) kpi_html = gr.HTML() with gr.Row(): namespace_filter = gr.Dropdown( choices=["(all)"], value="(all)", label="Namespace", scale=2, ) skill_filter = gr.Dropdown( choices=["(all)"], value="(all)", label="Skill", scale=2, ) result_filter = gr.Radio( choices=["All", "Passed only", "Failed only"], value="All", label="Result", scale=1, ) results_table = gr.HTML() gr.Markdown("### Pass rate by model") pass_rate_chart = gr.BarPlot( x="Model", y="Pass rate (%)", tooltip=["Model", "Pass rate (%)", "Cases run"], y_lim=[0, 100], color="Model", ) data_store = gr.State(pd.DataFrame()) def on_load(): df = load_results() if df.empty: return ( gr.Dropdown(choices=["(all)"], value="(all)"), gr.Dropdown(choices=["(all)"], value="(all)"), df, "", df, pd.DataFrame(), ) namespaces = sorted(df["Namespace"].unique().tolist()) skills = sorted(df["Skill"].unique().tolist()) return ( gr.Dropdown(choices=["(all)"] + namespaces, value="(all)"), gr.Dropdown(choices=["(all)"] + skills, value="(all)"), df, make_kpi_html(df), df_to_html_table(df), make_agg(df), ) def filter_data(ns, skill, result, df): filtered = apply_filters(df, ns, skill, result) if filtered.empty: return "", "

No results found.

", pd.DataFrame() return make_kpi_html(filtered), df_to_html_table(filtered), make_agg(filtered) demo.load( fn=on_load, outputs=[ namespace_filter, skill_filter, data_store, kpi_html, results_table, pass_rate_chart, ], ) for widget in [namespace_filter, skill_filter, result_filter]: widget.change( fn=filter_data, inputs=[namespace_filter, skill_filter, result_filter, data_store], outputs=[kpi_html, results_table, pass_rate_chart], ) return demo if __name__ == "__main__": build_ui().launch()