kozynear / backend /scripts /build_showcase_notebook.py
DYmazeh's picture
deploy: 9b09a44656b8
c7e536f verified
Raw
History Blame Contribute Delete
8.37 kB
"""Bangun notebook showcase model (kode + teks + output nyata) lalu eksekusi
+ render ke HTML untuk ditampilkan di tab "Notebook" aplikasi.
Output:
- notebooks/05_model_showcase.ipynb (executed; bisa dibuka di HF Files tab)
- frontend/public/notebook.html (render Jupyter, di-embed React tab)
Notebook self-contained: tiap eksekusi nyatanya menyentuh index + eval CSV
yang sudah ada (cepat & deterministik). Jalankan ulang tiap update model:
cd backend && python -m scripts.build_showcase_notebook
"""
from __future__ import annotations
import sys
from pathlib import Path
import nbformat as nbf
from nbconvert import HTMLExporter
from nbconvert.preprocessors import ExecutePreprocessor
ROOT = Path(__file__).resolve().parents[2]
BACKEND = ROOT / "backend"
NB_OUT = ROOT / "notebooks" / "05_model_showcase.ipynb"
HTML_OUT = ROOT / "frontend" / "public" / "notebook.html"
def md(text: str):
return nbf.v4.new_markdown_cell(text)
def code(src: str):
return nbf.v4.new_code_cell(src)
SETUP = f'''
import sys, json, warnings
from pathlib import Path
warnings.filterwarnings("ignore")
ROOT = Path(r"{ROOT.as_posix()}")
sys.path.insert(0, str(ROOT / "backend"))
import pandas as pd
pd.set_option("display.max_colwidth", 46)
from app.indexing.loader import load_all_indexes
from app.indexing.hybrid import HybridIndex
from app.preprocessing import PreprocessingPipeline
from app.search.gazetteer import Gazetteer
from app.search.pipeline import smart_rank
from scripts.eval_smart import load_listings
idx = load_all_indexes(ROOT / "data" / "indexes", include_neural=True)
bm25, tfidf, neural = idx["bm25"], idx["tfidf"], idx["indobert"]
pipe = PreprocessingPipeline()
pre = lambda s: pipe.process(s).processed
hybrid = HybridIndex(bm25, neural, query_preprocessor=pre)
gz = Gazetteer.load()
listings = load_listings()
print(f"Corpus: {{len(listings)}} listing | vocab BM25: {{len(bm25.bm25.idf)}} term")
print(f"Index siap: {{', '.join(idx.keys())}} + smart + hybrid")
'''.strip()
PREP = '''
# Pipeline preprocessing 9-stage pada contoh teks (judul + deskripsi pemilik)
contoh = "Kost Putri AC KM Dlm dkt UNILA 800rb/bln, wifi kenceng"
res = pipe.process(contoh, trace=True)
print("INPUT :", res.raw)
for s in res.trace:
out = s["output"] if isinstance(s["output"], str) else " | ".join(map(str, s["output"]))
print(f" {s['stage']:<20} -> {out[:70]}")
print("HASIL :", res.processed)
print("Harga terdeteksi:", res.extracted_prices)
'''.strip()
QUERY = '''
# Bandingkan lima model pada satu query natural language
q = "kos putri dekat unila wifi murah"
def top(model, n=5):
if model == "bm25": ids = [(h.doc_id, h.score) for h in bm25.query(pre(q), top_k=n)]
elif model == "tfidf":ids = [(h.doc_id, h.score) for h in tfidf.query(pre(q), top_k=n)]
elif model == "neural":ids = [(h.doc_id, h.score) for h in neural.query(q, top_k=n)]
elif model == "hybrid":ids = [(h.doc_id, h.score) for h in hybrid.query(q, top_k=n)]
else: ids = smart_rank(q, bm25, listings, gz, top_k=n, preprocess=pre)[0]
return [listings[i].judul for i, _ in ids]
pd.DataFrame({m: top(m) for m in ["bm25","tfidf","neural","hybrid","smart"]})
'''.strip()
SMART = '''
# Smart pipeline: query understanding + geo + fusion (model live)
res, understood, relaxed = (lambda r: (r[0], r[1], r[2]))(
smart_rank(q, bm25, listings, gz, top_k=5, preprocess=pre))
print("Yang dipahami sistem dari query:")
for k, v in understood.items():
if v: print(f" {k:<10}: {v}")
print("\\nTop-5 smart:")
for did, score in res:
r = listings[did]
print(f" [{r.tipe:<6} Rp{r.harga_per_bulan:>8} {(r.kecamatan or '-'):<14}] {r.judul[:44]}")
'''.strip()
EVAL = '''
# Hasil evaluasi 30 query (dibaca dari CSV hasil eksperimen)
df = pd.read_csv(ROOT / "eval" / "results.csv")
agg = (df.groupby("model")[["p_at_5","p_at_10","ap","ndcg_at_10","rr"]]
.mean().round(3).sort_values("ap", ascending=False))
agg.columns = ["P@5","P@10","MAP","NDCG@10","MRR"]
agg
'''.strip()
CHART = '''
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(7, 3.4))
order = agg.sort_values("MAP")
colors = ["#2563eb" if m == "smart" else "#94a3b8" for m in order.index]
ax.barh(order.index, order["MAP"], color=colors)
ax.set_xlabel("MAP (standard top-K, n=30)"); ax.set_title("Perbandingan MAP per model β€” smart (biru) = model live")
for i, v in enumerate(order["MAP"]): ax.text(v + 0.004, i, f"{v:.3f}", va="center", fontsize=9)
plt.tight_layout(); plt.show()
'''.strip()
CONSTRAINT = '''
# Constraint Satisfaction @5: % top-5 yang penuhi SEMUA kebutuhan user
# (gender + budget + fasilitas + radius 3km kampus) β€” bebas pooling bias
cs = pd.read_csv(ROOT / "eval" / "results_constraints.csv")
cols = [c for c in cs.columns if c.startswith("cs_at_5_")]
cs_mean = cs[cols].mean().round(3).sort_values(ascending=False)
cs_mean.index = [c.replace("cs_at_5_","") for c in cs_mean.index]
cs_mean.to_frame("mean CS@5 (n=30)")
'''.strip()
EXPERIMENTS = '''
# Ringkasan eksperimen lain (dibaca dari artefak eval)
import json
abl = pd.read_csv(ROOT / "eval" / "preprocess_ablation.csv")[["config","map","delta_map"]]
pb = json.loads((ROOT / "eval" / "explore_pooling_bias.json").read_text())["per_model"]
print("Ablation preprocessing (delta MAP saat stage dimatikan):")
print(abl.to_string(index=False))
print("\\nPooling bias β€” MAP saat pool adil (5 model) vs BM25-only:")
for m, d in pb.items():
print(f" {m:<8} {d['map_bm25pool']:.3f} -> {d['map_unionpool']:.3f} (delta {d['delta']:+.3f})")
'''.strip()
def main() -> int:
nb = nbf.v4.new_notebook()
nb.cells = [
md("# KozyNear β€” Showcase Model IR\n\n"
"Notebook reproducible: lima model retrieval (TF-IDF, BM25, Neural "
"MiniLM, Hybrid, **Smart**) di corpus **227 listing kos REAL** "
"Bandar Lampung. Tiap sel dieksekusi sungguhan; output di bawah "
"adalah hasil nyata, bukan tangkapan layar.\n\n"
"Mata Kuliah Temu Kembali Informasi β€” Universitas Lampung."),
md("## 1. Setup: muat corpus + index + pipeline"),
code(SETUP),
md("## 2. Preprocessing 9-stage\n\nJargon domain (`KM Dlm`β†’`kamar mandi "
"dalam`), ekstraksi harga, stemming Sastrawi β€” langkah demi langkah."),
code(PREP),
md("## 3. Lima model pada satu query\n\n"
"`\"kos putri dekat unila wifi murah\"` β€” bandingkan judul top-5 "
"tiap model."),
code(QUERY),
md("## 4. Smart pipeline (model live)\n\nMemecah query jadi constraint "
"terstruktur (gender/harga/fasilitas/anchor), lalu fusi teks + geo "
"+ atribut dengan hard filter."),
code(SMART),
md("## 5. Evaluasi 30 query β€” metrik standard\n\nMAP, P@K, NDCG, MRR "
"per model."),
code(EVAL),
code(CHART),
md("## 6. Constraint Satisfaction @5 (lensa kebutuhan user)\n\n"
"Bebas pooling bias: mengukur apakah hasil benar-benar memenuhi "
"gender + budget + fasilitas + jarak kampus."),
code(CONSTRAINT),
md("## 7. Eksperimen pendukung\n\nAblation preprocessing & kuantifikasi "
"pooling bias."),
code(EXPERIMENTS),
md("## Kesimpulan\n\n**Smart** unggul di MAP standard (0.359) dan "
"dominan di CS@5 (0.867 vs BM25 0.527, p=0.0001) tanpa model neural "
"di runtime. Skor standard cenderung meremehkan smart karena "
"*pooling bias* (lihat sel 7): saat pool dibuat adil, jarak smart "
"vs BM25 makin lebar. Detail metodologi di `LAPORAN.md`."),
]
nb.metadata["kernelspec"] = {"name": "python3", "display_name": "Python 3", "language": "python"}
print("[execute] menjalankan notebook (load neural model, butuh ~1-2 menit)...")
ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
ep.preprocess(nb, {"metadata": {"path": str(BACKEND)}})
NB_OUT.parent.mkdir(parents=True, exist_ok=True)
nbf.write(nb, str(NB_OUT))
print(f"[saved] {NB_OUT}")
exporter = HTMLExporter()
exporter.exclude_input_prompt = False
body, _ = exporter.from_notebook_node(nb)
HTML_OUT.parent.mkdir(parents=True, exist_ok=True)
HTML_OUT.write_text(body, encoding="utf-8")
print(f"[saved] {HTML_OUT} ({len(body)//1024} KB)")
return 0
if __name__ == "__main__":
sys.exit(main())