Nat 鍾承恩

實戰 2026 / 06 約 6 分鐘

讓 AI 安全記帳:碰錢的工程紀律

AI 能不能碰帳本,取決於帳本本身設計得多嚴。這不是信任問題——是工程問題。

任何讓 AI 能直接寫入帳本的系統,在動第一行 code 之前都得先問一個問題:如果 AI 理解錯了一個數字,或者使用者說的話有歧義,最壞的情況是什麼?

答案是:帳對不起來,而且你不知道從哪一步開始錯的。

所以讓 AI 碰帳本之前,我先把帳本設計成「就算出錯也能追、就算進了壞資料也能修」。下面六條是實際跑進生產的工程紀律,從 schema 到 AI 的寫入路徑,每一條都針對一個具體的失敗模式。

一、金額與利率,一律存成放大整數

浮點數在錢上會累積誤差。這不是理論上的問題——任何跨幣別換算、或長時間的加總,都可能讓「差幾分」變成真實的 bug。

做法:金額欄位存最小貨幣單位的整數(臺幣就是「元」,不再往下分),欄位後綴 _minor;利率存基本點整數(1% = 100 bp),後綴 _bp。看到後綴就知道單位不是「元」、不是「百分比」——這個命名慣例是給人讀的恆常提醒,也是給 code reviewer 的主動訊號。

型別層面,MinorUnits = NewType('MinorUnits', int) + mypy strict 把這條約束釘死,讓靜態分析幫你守住混用的邊界。

二、餘額不落地儲存,從交易用 view 重算

我不存任何「當前餘額」欄位。帳本只存開戶餘額與每筆交易記錄;當前餘額永遠由 view 即時算出來。

原因很簡單:同一件事如果你同時存「事件」和「彙總」,兩者遲早會漂移,而你不會知道哪個才對。把交易列當唯一真相源(single source of truth),餘額就是這些事件的純函數——要稽核就 replay,要校正就補一筆新事件,view 自動反映。

有了第一條的放大整數,view 重算的每一步都是整數算術,沒有浮點誤差;兩條紀律互相支撐。

三、金額存正數,方向用獨立欄位表達

同樣一筆「帳戶扣款」,對銀行帳是「支出」,對信用卡帳是「刷卡增債」——如果你用正負號編碼方向,同一個數字在不同帳別下語意不同,讀 code 的人要記住一套隱含規則。

做法:amount_minor 永遠 > 0(DB CHECK 強制),方向用獨立的列舉欄位:direction(inflow/outflow)、debt_type(payable/receivable)。語意顯式、可被 enum 驗證;view 算餘額時依帳別套不同符號規則,signed 結果只在「輸出」那層出現。

把「大小」和「方向」拆開存,是讓 CHECK 約束和 enum 驗證都能機械咬住的前提。

四、改錯補反向交易,不動原列

帳本是 append-only 的。這是複式記帳的底線,也是稽核存在的意義。

撤銷一筆錯誤交易:補一筆金額相同、方向相反的反向交易。不 UPDATE、不 DELETE 原列。這筆 void 記錄的 occurred_at 複製原交易的時間戳,不是 now()——這個設計讓 UNIQUE constraint 自然擋掉「同一筆重複撤銷」,不需要另寫應用層邏輯。

校正開戶餘額:不改舊欄位,補一筆 source='manual' 的補正交易,view 自動還原。

「修正」是新增一個抵銷事實,不是抹掉舊事實——歷史是稽核資產,不是雜訊。

五、解析用 grammar + 白名單,曖昧就反問

這是 AI 與帳本之間的入口。我不讓模型「感覺」一句自然語言說的是什麼——我讓它走明文 grammar。

具體做法:金額用 regex + 後綴白名單(萬|千|k|K),其他寫法 → 反問;相對日期解析成絕對日後回顯確認;「借」沒有明確方向 token → 拒絕並反問;帳戶未指定且有多個活躍帳戶 → 反問;類別猜不到 → 寫 NULL,不亂填。

在錢的入口,「拒絕 + 反問」比「猜一個多半對的值」安全。把確定性高的部分(數字格式、方向 token、日期)交給規則;真正模糊的地方顯式丟回給人釐清,不讓模型替你腦補。

六、AI 寫入走兩段:preview 確認,才 commit

前五條把資料模型設計成「可稽核、可機械驗證」。這一條針對寫入路徑本身。

會 mutate 帳本的操作都走兩段:自然語言先解析成 preview 暫存(TTL 1 小時),把解析後的絕對值——金額(最小單位整數)、絕對日期、帳戶名稱、方向——攤給人看;人確認後才 commit 寫入。

重點:preview 顯示的是「系統懂成什麼」,不是「你說了什麼」。讓人核對的是解析結果,不是原句。LLM 解自然語言通常會對——但「通常」不夠格直接動錢。在解析和寫入之間插這一道人類確認閘,把不可逆的那一步留給人。


為什麼這六條放在一起

前四條是帳本本身的工程紀律:格式、計算、語意、更正路徑——每一條都把一個可能出錯的地方變成 DB constraint 或純函數,讓「對的狀態」是唯一合法狀態。

後兩條是 AI 介入點的工程紀律:入口的 grammar 閘(第五條)、寫入的兩段確認(第六條)。它們成立的前提,是前四條把帳本設計得夠嚴——有了 view 重算、有了 append-only、有了整數約束,即使 AI 在 preview 階段理解有誤,commit 之前人還有一道確認;就算不慎 commit 了壞資料,補一筆反向交易就能還原。

這不是不信任 AI。這是讓 AI 能真正被信任的前提——把邊界設計成機械可驗,而不是靠「模型通常會對」撐著。

這六條用在我的帳務 Agent,見fins:幫我月底對帳的個人+公司財務 Agent