這份文件回答一個問題:要把這個專案交給別人,需要給什麼、不用給什麼、怎麼跑起來。
程式碼全在 git 裡(src/ + web/ + docs)。git 沒有也不該有的是大資料產物,必須另外傳(雲端硬碟 / 外接碟):
| 必給 | 是什麼 | 大小 |
|---|---|---|
data/handoff.db |
清空 per-query bake 層的 SQLite:raw SO 資料 + extracted practice + authority。給接手者一個乾淨的 bake 起跑點。 | ~1.5 GB |
data/canonical_index/ |
cold-path RAG 用的語料向量(embeddings.npy + question_ids.npy) |
~1.4 GB |
接手的人自備一份 .env(自己的 API keys,見下)。給了這兩個產物,他可以直接把站台跑起來、丟新 query 走 cold-path bake —— 不用重抓 SO 資料、不用重燒 embedding、不用重算 authority。
想連開發期 bake 出來的 demo query 一起交? 那就改交完整的
data/so_data_react_2021_2026_processed.db(含已 bake 的 query,landing 立刻有東西看),見下面〈兩種交法〉。不用給
data/so_data_react_2021_2026.db(純 raw、無 computed 表)—— 它是 processed 的子集,processed/handoff 已含同一份 raw。只有「想從乾淨 raw 自己重跑整條 pipeline」才需要它。
一顆 shared SQLite integration bus(見 src/store/):raw 表是 source of truth,其餘都是同一顆裡算出來的衍生表。三層保留、一層清空:
| 層 | 表 | 角色 | 交接 |
|---|---|---|---|
| Raw(source of truth) | questions / answers / comments / users / interactions(221212 / 253351 / 604681 / 187701 / 778676) | 原始 SO 資料 | ✅ 留 |
| Extracted practice(group-independent) | practice_extractions(~680) |
一 practice 一列、按 answer keying;answer body 的純函數 | ✅ 留 |
| Authority(全圖算、與 query 無關) | user_authority(~170k) / user_pagerank_yearly(~182k) / authority_run_meta |
network PageRank 等中心性 | ✅ 留 |
| Per-query bake 層 | submissions / canonical_group / practice_clusters / query_practice / camp_signal / query_signal / query_narrative / query_viz_cache |
一次 query 的 resolve→cluster→signal→narrative→viz 結果 | ❌ 清空(clean handoff)/ ✅ 留(完整交) |
practice_extractions留著是為了設計意圖 + 未來跨-group reuse,但⚠️ 目前不會幫接手的人省 bake 錢:extract.py對每個 pool 進來的 answer 都重抽(cross-group reuse 還沒接,見其 docstring)。它今天不是成本快取。
第三個產物 data/canonical_index/ 不在 db 裡,是獨立 .npy:~221k 題的 OpenAI embedding 矩陣,free-text query → canonical group 全靠它。沒有它 cold-path 進不去。
A. 乾淨交接(預設,交 handoff.db) —— 清掉開發期的 demo bake,接手者拿到空的 landing、自己 bake。務必在副本上做,別動工作 DB(不然本機 demo 全沒、要重 bake 才能繼續開發):
cp data/so_data_react_2021_2026_processed.db data/handoff.db
sqlite3 data/handoff.db <<'SQL'
DELETE FROM submissions; DELETE FROM canonical_group; DELETE FROM practice_clusters;
DELETE FROM query_practice; DELETE FROM camp_signal; DELETE FROM query_signal;
DELETE FROM query_narrative; DELETE FROM query_viz_cache;
DROP TABLE IF EXISTS cluster_aggregations; -- legacy 空表
VACUUM; -- raw 為大宗,縮不多(1.6G→1.5G)
SQL
# 交 data/handoff.db + data/canonical_index/已實測:乾淨 DB 能從零跑完整條 cold-path(resolve→extract→cluster→aggregate→narrate→materialize,~88s,query_viz_cache.status='ready')。
B. 完整交接(交 ...processed.db) —— 直接交工作 DB,含你已 bake 的 demo query;接手者一打開 landing 就看得到結果。沒有刪除步驟,照原檔交即可。
兩種都搭 data/canonical_index/ 一起交。
db / index 路徑全部 import 自一個地方,換檔名 / 指到別的 snapshot 用環境變數即可,不用改任何程式碼(相對路徑以 repo root 為基準,絕對路徑照用):
# src/config.py
DB_PATH # 預設 data/so_data_react_2021_2026_processed.db
CANONICAL_INDEX_DIR # 預設 data/canonical_indexCOMMUNITY_DB=data/handoff.db PYTHONPATH=src uv run uvicorn serve.app:app --reload
COMMUNITY_INDEX_DIR=/mnt/big/canonical_index PYTHONPATH=src python -m canonical.build_indexauthority 模組保留舊的 AUTHORITY_DB 當 legacy override(有設就贏),沒設就跟著 src/config.py 走,確保整條 spine 讀同一顆 db。
需求:Python ≥ 3.11、uv、Node + pnpm。
uv sync # 1. 後端依賴
cd web && pnpm install && cd .. # 2. 前端依賴
cp .env.example .env # 3. 填 API keys(.env 已 gitignored)
# SE_API_KEY — 只有「重抓 SO 資料」才需要
# OPENAI_API_KEY — bake 新 query / 重建 canonical_index 需要(LLM + embedding)
⚠️ 程式不會自動讀.env(沒有 load_dotenv)。env 變數要實際存在於 shell 環境:用direnv(repo 用.envrc,已 gitignored),或臨時set -a; . ./.env; set +a再跑。
把上面兩個資料產物放進 data/。
兩個 process,前端固定打 http://localhost:8000、serve 的 CORS 只放行 :5173:
# 後端 serve plane(warm reads + cold-path 入口)
COMMUNITY_DB=data/handoff.db PYTHONPATH=src uv run uvicorn serve.app:app --reload # → :8000
# 前端(另開一個終端機)
cd web && pnpm dev # → :5173打開 http://localhost:5173:
- 乾淨交接(handoff.db):landing 是空的 —— 在輸入框丟一個 React query,走 cold-path(resolve → bake → dashboard)即生出第一個結果。
- 完整交接(processed.db):landing 直接列出已 bake 的 groups,點進去看 force-field + narrative。
(選用)authority Streamlit dashboard —— 探索用旁支工具,需另裝 streamlit / plotly:
PYTHONPATH=src streamlit run -m authority.dashboard # 預設讀 config.DB_PATH整條鏈路都在 git 裡,可以重跑,但要花 API 額度與時間:
- 重抓 raw(Stack Overflow API,需
SE_API_KEY,量大、耗時)—— 見src/ingest/README.md。產物 = raw db。 - 建 canonical index(需
OPENAI_API_KEY,一次性 ≈ $0.66)——PYTHONPATH=src uv run python -m canonical.build_index - bake(需
OPENAI_API_KEY,每個 query 約 $0.3 量級)—— in-window query 走 serve 的POST /queriescold-path,或用orchestration.driver單跑。
computed 表(practice / cluster / signal / narrative)是 LLM 產物 —— 非決定性、且要花錢。這正是直接交接 db 的價值:省下重 bake 的成本與不可重現性。
data/so_data_react_2021_2026.db(純 raw,processed 的子集,見 TL;DR)data/smoketest.db之類的拋棄式副本(驗完即刪)__pycache__/、.venv/、web/node_modules/(重裝即得)data/golden_*.json、src/breakdown/data/canonical_q54069253.json(retired demo 的 fixture / snapshot,留著只為能重跑舊 demo)- 任何
*.npy(embedding,gitignored)/.env(keys 各自帶)