File size: 8,371 Bytes
c7e536f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""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())