OptETF Deploy
Deploy OptETF web app (cache-only on server, cross-platform paths, tz+dedup fixes) for HF Spaces
e8ccbfa | # -*- coding: utf-8 -*- | |
| """ | |
| 函式庫版整合範例(不開網頁)—— 直接在你的 Python 程式裡跑問答、拿 9 維權重。 | |
| 與網頁版共用同一個接點 recommender_hook.deliver_weights(): | |
| - 網頁版:python run_web.py(瀏覽器問答,完成後自動交付) | |
| - 函式庫版:本檔(程式內問答,完成後自動交付) | |
| 每一輪回答後都會輸出該輪的 μ(強度讀數)與 σ(不確定性),並把完整逐輪過程 | |
| 存成 elicitation_trace.json,供學術報告完整留存萃取過程。 | |
| 執行(互動測試): | |
| python integrate_example.py | |
| 首次會載入 BGE-M3(本機有 encoder_model/ 則離線,否則自動下載 BAAI/bge-m3,約 1–2 分鐘)。 | |
| """ | |
| import sys | |
| import json | |
| from pathlib import Path | |
| BUNDLE_DIR = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(BUNDLE_DIR)) | |
| from phase3_system import Phase3Engine # noqa: E402 | |
| from recommender_hook import deliver_weights # noqa: E402 ← 共用接點 | |
| TRACE_PATH = BUNDLE_DIR / "elicitation_trace.json" | |
| def extract_preferences(philosophy_text, answer_fn, max_questions=9, verbose=True): | |
| """跑完問答,回傳 (weights, snapshot, trace)。 | |
| trace: 逐輪過程清單,每輪含 | |
| step / dim_key / dim_label / mu(強度) / sigma(不確定性) / | |
| gate_rel(離題守門可靠度) / revision(重問移動量) / is_reask / | |
| Sigma_alpha(當輪整體確定度) / dim_Ew(該維權重) / dim_ci90(該維 90% 信賴區間) | |
| —— 供學術報告完整呈現「每一題如何更新信念」。 | |
| """ | |
| engine = Phase3Engine() | |
| engine.start_session(philosophy_text) | |
| trace = [] | |
| asked = 0 | |
| while asked < max_questions: | |
| q = engine.next_question() | |
| if q is None: | |
| break | |
| snap = engine.submit_answer(answer_fn(q)) | |
| asked += 1 | |
| lt = snap.get("last_turn", {}) or {} | |
| dk = lt.get("dim_key") | |
| rk = next((r for r in snap.get("ranking", []) if r.get("dim_key") == dk), {}) | |
| rec = { | |
| "step": asked, "dim_key": dk, "dim_label": lt.get("dim_label"), | |
| "mu": lt.get("mu"), "sigma": lt.get("sigma"), | |
| "gate_rel": lt.get("gate_rel"), "revision": lt.get("revision"), | |
| "is_reask": lt.get("is_reask"), | |
| "Sigma_alpha": snap.get("Sigma_alpha"), | |
| "dim_Ew": rk.get("Ew"), "dim_ci90": rk.get("ci90"), | |
| } | |
| trace.append(rec) | |
| if verbose: | |
| tag = "(重問)" if rec["is_reask"] else "" | |
| ci = rec["dim_ci90"] or [None, None] | |
| print(f" ↳ μ 強度={rec['mu']:.3f} σ 不確定性={rec['sigma']:.3f} " | |
| f"gate={rec['gate_rel']:.2f} Σα={rec['Sigma_alpha']:.2f} " | |
| f"該維 E[w]={rec['dim_Ew']:.3f} CI90=[{ci[0]:.3f},{ci[1]:.3f}]{tag}") | |
| if snap.get("should_stop"): | |
| break | |
| snap = engine.snapshot() | |
| return snap["Ew"], snap, trace | |
| def run(philosophy_text, answer_fn, max_questions=9, verbose=True, save_trace=True): | |
| """完整流程:萃取權重(逐輪輸出 μ/σ)→ 透過 recommender_hook 交付 → 存逐輪 trace。 | |
| 回傳 (weights, snapshot, trace, delivery_result)。""" | |
| weights, snap, trace = extract_preferences(philosophy_text, answer_fn, max_questions, verbose) | |
| result = deliver_weights(weights, snap) # ← 你的推薦邏輯(在 recommender_hook.py) | |
| if save_trace: | |
| TRACE_PATH.write_text(json.dumps({ | |
| "weights": weights, | |
| "ranking": snap.get("ranking"), | |
| "Sigma_alpha": snap.get("Sigma_alpha"), | |
| "ci_trustworthy": snap.get("ci_trustworthy"), | |
| "ci_note": snap.get("ci_note"), | |
| "trace": trace, | |
| }, ensure_ascii=False, indent=2), encoding="utf-8") | |
| return weights, snap, trace, result | |
| def _interactive(): | |
| print("=" * 60) | |
| print(" Phase 3 偏好誘出 — 函式庫版整合測試(逐輪輸出 μ / 不確定性)") | |
| print("=" * 60) | |
| phil = input("\n請輸入投資理念(直接 Enter 用預設範例):\n> ").strip() | |
| if not phil: | |
| phil = "我希望長期穩健成長,能接受一點波動但很怕大跌,偏好低費用、分散持股的標的。" | |
| print(f"(使用預設) {phil}") | |
| def ask(q): | |
| print(f"\n[第 {q['step']} 題 · {q['dim_label']}]") | |
| return input(f" {q['question']}\n > ").strip() | |
| weights, snap, trace, result = run(phil, ask) | |
| print("\n" + "=" * 60) | |
| print(" 9 維 ETF 偏好權重(總和=1)") | |
| print("=" * 60) | |
| for it in snap["ranking"]: | |
| print(f" {it['Ew']:.3f} {'█'*int(it['Ew']*40):<40} {it['dim_label']}") | |
| print(f"\n 確定度 Σα = {snap['Sigma_alpha']} | {snap['ci_note']}") | |
| print(f" 共 {len(trace)} 輪過程已存檔:{TRACE_PATH.name}") | |
| print(f" recommender_hook 回傳 = {result}") | |
| if __name__ == "__main__": | |
| _interactive() | |