# fintrack

個人財務資料管線：把各機構的原始檔整合成可追溯、可重建、可驗證的 SQLite。

> 給 AI coding agent 的入口說明。任何 agent 動工前先讀這份，再依序往下。

**動工前先讀：**

1. `DESIGN.md` — 憲法：§0、§1、§2.5、§2.6、§12 是必讀，其他節用到再查。
   實作與它衝突時，要嘛修實作，要嘛修它並在 `DESIGN-CHANGELOG.md` 留修訂紀錄（§13 指路），
   **不得放任兩者長期不一致**。
   **任何對 DESIGN.md 的修改，動手前都要先跟使用者討論**：先呈報兩個選項（修實作或修文件），
   討論定案後才動 DESIGN.md（使用者：它是確保專案沒有走偏的工具，
   agent 自行修改憲法，把關機制就失效了）。
2. `STATUS.md` — 現況：做到哪、還缺什麼、有哪些坑（每做完一段就更新它）。
   很長，先讀開頭（最後更新、最近做完的事、一句話現況），需要再往下。
   **歷史在 `STATUS-ARCHIVE.md`，不必每次讀**——需要考古「某條鏈路當初為什麼
   那樣做」時才翻。維護規則（「最近做完的事」只留 5 條、結案小節整段搬 archive、
   做完回頭核對「未完成」與「坑」）照 `~/projects/DESIGN.md` §4——那套就是從這裡整理出去的。
3. `docs/sources/{institution_id}.md` — 該機構的格式判定、權威來源指定、自動化限制、格式的坑
4. `docs/data-shape.md` — **資料形狀速查**：每個 parser 產出哪些 section、
   哪些欄位、格式長什麼樣。**由 `fintrack shape --write` 自動生成，不要手改**，
   parser 改了就重跑。要寫查詢或報表之前先看這份，可以省掉一輪一輪的試探性 SQL。
   （樣本欄印的是格式不是真值：數字換成 9、文字只留字數，所以它可以進 Git。）

**使用者給檔案只有一個窗口：`data/inbox/`。**
使用者說「我上傳了」「我把資料給你了」——**第一件事是 `ls data/inbox/`**，
不要去 Downloads、不要 find 整個家目錄、不要回問「檔案在哪」。
這是 DESIGN §10 訂的唯一人工投放點，agent 自己講過的規則自己要遵守。
（使用者指出這件事連續發生過幾次。）
`data/inbox/` 裡的東西不會自己消失：處理完要**明確決定去處**——
歸檔進 `data/raw/`（是證據）或移到 `data/aside/`（不是證據，見下），不要留在原地。

最容易誤觸的鐵則：`data/raw/` 唯讀；金額禁用 float；判斷結果不寫 DB；
不確定就 `unknown` 不猜測；pipeline runtime 不呼叫 LLM。
**報表層的判斷也不寫程式**（DESIGN §7.2 規矩 9）：起畫日、帳戶分組、估值來源、撥款日之類
寫 `config/report_scope.json`／`loans.json`／`manual_valuations.json`，`analysis/` 只讀——
這樣 `analysis/` 才能像使用者要的那樣「有問題就整個打掉重建」而不丟掉任何決定。

**`data/aside/` 旁置區**：使用者拿來對照、但**明確聲明不是來源**的東西
（自己做的試算表、臨時導出）。規則是「**要備份，但不進檔案庫**」——
不歸檔 `data/raw/`、不建 parser、不建 txn、不影響 verify，
但它會跟著 `backup_finance.py` 一起備份，**不是可以隨手刪掉的暫存**。

**統一帳本**：每家機構有自己的 `data/db/{institution}.db`，
`fintrack unify` 把它們併成 `data/db/fintrack.db` 並跑跨機構連結（DESIGN §6）。
改完任何一條機構鏈路要再跑一次 `unify`，報表才會反映。

**定時任務有兩個**（`deploy/systemd/`，失敗都推告警 topic）：
- `fintrack-daily.timer` 每天早上重抓行情、重產績效報表並發布。
  它**只跑報表層**；淨值類報表是文件口徑，丟文件、跑完 build／unify 之後要自己
  `python -m analysis.run --publish`。
- `fintrack-gmail-sync.timer` 每週跑 `gmail_sync.py`：把 `config/gmail_sync.json`
  的每個候選池從 Gmail 同步到 `data/raw/_gmail/`，**只抓沒看過的信**，
  log 在 `logs/gmail_sync/latest.md`。查「上週抓到什麼新檔」→ 讀那份 log。
  新檔只到候選區、**不會自動入庫**；有新檔會推本專案的 topic（失敗才走告警）。

pipeline 永遠不排程（要人看 verify 結果）：Gmail 同步後有新檔，要人跑該機構 build→verify→unify。

**交付慣例**：改 parser 要升 `VERSION` 並更新 fixture 與回歸測試；
無法自動化的部分依 DESIGN §2.5 Stage E 明確回報，不得靜默略過。

**掃描檔要逐頁看**（通用規則在 root `AGENTS.md`）：銀行的契約組是「封面定型化＋中間夾具體條款」
的結構，第 1 頁最像廢話的文件反而常把數字藏在中間頁（曾只看第 1 頁就宣稱某筆「缺契約」，
實際第 3–4 頁就是那筆的條款）。影像掃描檔一律 `pdftoppm` 全部頁面、逐頁看完才下結論；
轉錄的數字要跟已知資料互相驗算，對不上就重看。

## 跑哪些測試、什麼時候跑 verify（實測後訂）

全套 300 多個測試要 159 秒，其中 **155 秒是四個「從 raw 重建整個機構的 DB
再逐字比對」的類別**。它們守的是 DESIGN §0 判準 3（可重建），**永遠不准刪、不准放寬**。
但 `analysis/` 是唯讀的、pipeline 不 import 它（DESIGN §7.2 單向依賴），
**改報表層不可能弄壞重建**，所以不必每次都付這 155 秒。

| 你改了什麼 | 跑什麼 | 大約多久 |
|---|---|---|
| **什麼都沒改**：只查資料、讀 DB、解釋報表 | **什麼都不用跑**。查「現在什麼狀態」→ `fintrack status`（唯讀 0.3 秒）；查「verify 盯不盯某件事」→ 讀 `src/fintrack/verify/{機構}.py`；查「上次 verify 說了什麼」→ 讀 `data/db/verify-latest-{db}.json`；查資料長什麼樣 → 先讀 `docs/data-shape.md`，不要試探性 SQL | **0 秒** |
| 只有 `analysis/`、`reports/`、報表專用的 config | `python -m pytest -m "not rebuild"` ＋ `python -m analysis.run --offline` | **約 10 秒** |
| `src/fintrack/`、parser、schema、pipeline 用的 config、`tests/` | `python -m pytest -n auto`（全套，平行） | **約 87 秒** |
| 任何會寫 DB 的東西（build / unify） | 上面那條 ＋ `fintrack verify` | ＋39 秒 |

- **`fintrack verify` 只在 DB 變動後跑。** 它重算的是帳本；`analysis/` 唯讀，不可能讓它變色。只改報表卻跑 verify 是白等 39 秒。
- **一律用 `python -m pytest`**，不要光打 `pytest`：repo root 不在 sys.path 時某測試會 import 不到 `analysis`。
- **真要跑 verify／測試：輸出直接存到暫存檔再 grep**，不要為了看不同段落重跑（一個只查資料的 session 曾把 verify 跑了四次，使用者感覺到慢）。
- **`-n auto` 一個斷言都不省**，只是分到多核上跑。測試都用暫存目錄，互不干擾。
- 標記在 `pytest.ini_options` 註冊，`--strict-markers` 開著，打錯字會直接報錯而不是默默失效。
- **改報表程式時一律加 `--offline`。** 五份報告全跑：`--offline` **1.6 秒**，連網 **超過 2 分鐘**。差別不在算，在抓行情：快取的新鮮度是 12 小時，而免費端點有節流。改程式的時候你要驗的是程式，不是行情。
- **懷疑的時候跑全套。** 這張表是省時間用的，不是拿來賭的。

**Git 工作方式（使用者決定）**：這個 repo 只有使用者一人，
**一律直接在 `main` 上工作，不開 branch**——分支只會增加混亂（曾發生四條機構鏈路留在分支上沒人發現）。做完一段就 commit。
**沒有遠端**：異地副本由雲端每週加密備份承擔（含 repo 與 `.git`，見 `~/projects/INFRA.md` §5），GitHub remote 已移除、不再 push。

## 回答理財問題時

- **使用者把「省事」看得很重**：建議商品時，能塞進既有定期定額方案的優先，差異不大就選省事的。
- 使用者放在 `data/aside/` 的參考資料是「以後我問問題可以參考」用的，回答前先看有沒有現成的。
