BHAVIKBANKER/BERTopic_AGENTIC_AI__GROUP_1
0
1"""app.py — Gradio UI entry point (<200 lines, §11)."""2import os, json, tempfile, time3import pandas as pd, numpy as np4import gradio as gr5import plotly.express as px6import plotly.graph_objects as go7from agent import run_pipeline8 9# ── CSV preview on upload ────────────────────────────────────────────────────10def _preview(file):11 if not file: return "Upload a Scopus CSV to begin."12 df = pd.read_csv(file.name)13 df.columns = df.columns.str.lower()14 has_t = "title" in df.columns15 has_a = "abstract" in df.columns16 n = len(df)17 blanks_t = int(df["title"].isna().sum()) if has_t else n18 blanks_a = int(df["abstract"].isna().sum()) if has_a else n19 ok = "✅" if has_t and has_a and blanks_t < n and blanks_a < n else "❌"20 return (f"## {ok} CSV loaded — {n} entries\n\n"21 f"| Column | Present | Blank rows |\n|---|---|---|\n"22 f"| title | {'✅' if has_t else '❌'} | {blanks_t} |\n"23 f"| abstract | {'✅' if has_a else '❌'} | {blanks_a} |\n\n"24 f"**Usable papers:** {n - max(blanks_t,blanks_a)} / {n}")25 26# ── Pipeline runner ──────────────────────────────────────────────────────────27def _run(file, gk, mk, gek, n_trials, progress=gr.Progress(track_tqdm=True)):28 if not file: raise gr.Error("Upload a CSV first.")29 gk = gk.strip() or os.getenv("GROQ_API_KEY","")30 mk = mk.strip() or os.getenv("MISTRAL_API_KEY","")31 gek = gek.strip() or os.getenv("GEMINI_API_KEY","")32 if not all([gk,mk,gek]): raise gr.Error("All 3 API keys required.")33 progress(0.05, desc="📥 Loading CSV…")34 progress(0.1, desc="🔬 Embedding with SPECTER-2 (this takes a few minutes)…")35 r = run_pipeline(file.name, gk, mk, gek, int(n_trials))36 if r.get("error"): raise gr.Error(r["error"])37 progress(0.95, desc="📊 Building outputs…")38 td, interps = r["topic_data"], r.get("interpretations",{})39 disc, met = td["discipline"], td["metrics"]40 ar = r.get("agreement_rates",{})41 # ── Summary metrics (styled like reference) ──42 def _s(ok): return "✅ PASS" if ok else "❌ FAIL"43 summary = (f"## Pipeline Complete — {disc['n_clusters']} clusters discovered\n\n"44 f"| Criterion | Value | Status |\n|---|---|---|\n"45 f"| Max cluster mass | {round(disc['max_mass_pct']*100,1)}% | {_s(disc['max_mass_ok'])} |\n"46 f"| Min cluster size | {disc['min_size']} | {_s(disc['min_size_ok'])} |\n"47 f"| Persistence (mean) | {round(met['persistence'],4)} | — |\n"48 f"| DBCV | {round(met['dbcv'],4)} | — |\n"49 f"| Stability ({3} seeds) | {round(met['stability'],4)} | — |\n\n"50 f"**Trials:** {td['n_trials_run']} (best #{td['best_trial']}) · "51 f"**Agreement:** Triple {ar.get('triple',0)}% · Two+ {ar.get('two_or_more',0)}%")52 # ── UMAP scatter ──53 u2d = np.array(td["umap_2d"])54 sdf = pd.DataFrame({"UMAP-1":u2d[:,0],"UMAP-2":u2d[:,1],55 "Cluster":[str(l) for l in td["labels"]],56 "Doc":[d[:60] for d in td["documents"]]})57 fig = px.scatter(sdf, x="UMAP-1", y="UMAP-2", color="Cluster",58 hover_data=["Doc"], opacity=0.75,59 title=f"2-D UMAP visualisation of SPECTER-2 embeddings")60 fig.update_layout(template="plotly_dark", height=500,61 paper_bgcolor="#0d1117", plot_bgcolor="#161b22",62 font=dict(size=11))63 # ── Trial log ──64 tl = pd.DataFrame(td["trial_log"])65 tl_cols = [c for c in ["trial","discipline_pass","n_clusters","persistence",66 "dbcv","max_mass_pct","min_size","n_noise"] if c in tl.columns]67 tl_show = tl[tl_cols] if not tl.empty else pd.DataFrame()68 # ── Pareto front ──69 pfig = go.Figure()70 if not tl.empty:71 for passed, color, name in [(True,"#3dba7a","PASS"),(False,"#e04d4d","FAIL")]:72 sub = tl[tl["discipline_pass"]==passed]73 if not sub.empty:74 pfig.add_trace(go.Scatter(x=sub["max_mass_pct"],y=sub["persistence"],75 mode="markers",marker=dict(size=8,color=color),name=name,76 text=sub["trial"],hovertemplate="Trial %{text}<br>Mass: %{x:.0%}<br>Pers: %{y:.3f}"))77 pfig.add_vline(x=0.25, line_dash="dash", line_color="#5a6480",78 annotation_text="25% rule")79 pfig.update_layout(template="plotly_dark", height=400,80 paper_bgcolor="#0d1117", plot_bgcolor="#161b22",81 title="Pareto front — Persistence vs Max cluster mass",82 xaxis_title="Max cluster mass (lower is better)",83 yaxis_title="Persistence (higher is better)", font=dict(size=11))84 # ── Cluster table ──85 rows = []86 for cid in sorted(interps.keys()):87 v = interps[cid]88 rows.append({"Cluster":cid,"Label":v["label"],"Agreement":v["agreement"],89 "Strong":v["strong"],"Weak":v["weak"],90 "Persistence":round(v.get("persistence",0),4),91 "Keyphrases":", ".join(v.get("keyphrases",[]))})92 cdf = pd.DataFrame(rows)93 # ── 4 separate sheets ──94 sheets = r.get("sheets",{})95 s1 = pd.DataFrame(sheets.get(1,[])); s2 = pd.DataFrame(sheets.get(2,[]))96 s3 = pd.DataFrame(sheets.get(3,[])); s4 = pd.DataFrame(sheets.get(4,[]))97 sp = r.get("sheet_paths",{})98 mdf = pd.DataFrame(r.get("mismatch_table",[]))99 progress(1.0, desc="✅ Done!")100 dl_files = [f for f in101 [sp.get(1), sp.get(2), sp.get(3), sp.get(4), r.get("json_path")]102 if f is not None]103 return (summary, fig, pfig, tl_show, cdf, s1, s2, s3, s4,104 dl_files if dl_files else None, mdf)105 106# ── UI ───────────────────────────────────────────────────────────────────────107css = ".gradio-container{background:#0d1117!important;color:#c9d1d9!important}" \108 "footer{display:none!important}"109with gr.Blocks(theme=gr.themes.Base(primary_hue="blue",neutral_hue="slate"),110 css=css, title="SPECTER-2 Topic Analyzer") as demo:111 gr.Markdown("# 📐 SPECTER-2 Topic Analyzer")112 with gr.Row():113 with gr.Column(scale=1):114 file_in = gr.File(label="Upload Scopus CSV", file_types=[".csv"])115 preview_out = gr.Markdown("Upload a CSV to see stats.")116 groq_in = gr.Textbox(label="Groq API Key", type="password",117 placeholder="or set GROQ_API_KEY env var")118 mistral_in = gr.Textbox(label="Mistral API Key", type="password",119 placeholder="or set MISTRAL_API_KEY env var")120 gemini_in = gr.Textbox(label="Gemini API Key", type="password",121 placeholder="or set GEMINI_API_KEY env var")122 trials_in = gr.Slider(10,100,50,step=5,label="Optuna Trials")123 run_btn = gr.Button("▶ Run Full Pipeline", variant="primary", size="lg")124 with gr.Column(scale=3):125 with gr.Tabs():126 with gr.Tab("Summary"): summary_out = gr.Markdown()127 with gr.Tab("2-D UMAP"): scatter_out = gr.Plot()128 with gr.Tab("Pareto Front"): pareto_out = gr.Plot()129 with gr.Tab("Trial Log"): trial_out = gr.Dataframe()130 with gr.Tab("Clusters"): cluster_out = gr.Dataframe()131 with gr.Tab("Sheet 1 — Groq"): s1_out = gr.Dataframe()132 with gr.Tab("Sheet 2 — Mistral"): s2_out = gr.Dataframe()133 with gr.Tab("Sheet 3 — Gemini"): s3_out = gr.Dataframe()134 with gr.Tab("Sheet 4 — Consolidated"): s4_out = gr.Dataframe()135 with gr.Tab("RQ Mismatch"): mismatch_out = gr.Dataframe()136 with gr.Tab("Downloads"):137 dl_out = gr.File(label="All sheet CSVs + topics.json",138 file_count="multiple")139 file_in.change(_preview, inputs=[file_in], outputs=[preview_out])140 run_btn.click(_run,141 inputs=[file_in, groq_in, mistral_in, gemini_in, trials_in],142 outputs=[summary_out, scatter_out, pareto_out, trial_out, cluster_out,143 s1_out, s2_out, s3_out, s4_out, dl_out, mismatch_out])144 145if __name__ == "__main__":146 demo.launch(server_name="0.0.0.0", server_port=7860)147 