OptETF Deploy
Deploy OptETF web app (cache-only on server, cross-platform paths, tz+dedup fixes) for HF Spaces
e8ccbfa
Raw
History Blame Contribute Delete
4.86 kB
# -*- 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()