- 減錯
- 省時
撥款通知信的每個日期,都能指回合約
依合約規則產出撥款通知預覽,寄送由人完成;合作方發票上傳後另由 OCR 與規則核對。
背景與使用技術(4 項)
每個結算月要寄給合作方的撥款通知信,兩個日期都得照合約條文逐一數工作日,還要記得補假。改成依方案與狀態分支的產生器,8 封信一次產齊,日期由程式算好並在產出檔案前先跑驗算。合作方回傳的發票則經上傳網頁與 OCR 核對金額、統一編號與辨識信心度,三條規則全過才算收件。
誰在用
負責撥款通知與發票回收的財務人員
原本怎麼做
撥款通知信上的日期是合作方對帳的依據,合約把它們綁死:通知寄出後 7 個工作日內開立發票,再對到下一個約定撥款日。每寄一封信都得翻一次合約、逐日數工作日,還要記得當年哪些日子是補假。合作方案有三種,每種又有不同狀況,展開是 8 封內容不同的信,過去每月手寫一輪。發票回收的金額、統一編號與掃描清晰度,全靠人工逐張比對。
前後對比
量測:實測=有計時或有紀錄;估計=喜特先生推估;客戶自述=客戶回報;規格=設計上的固定條件;待補=尚未取得數字。
流程一眼看懂
翻合約數工作日
手寫 8 種信
人工比對回收的發票
合約條文寫成常數
產生器算日期並驗算
預覽,人工寄送
合作方專屬連結上傳發票
OCR 加 3 條規則收件
流程示意:依本案既有做法整理,非客戶實際畫面。
做了什麼
- 合約條文常數化:每個日期都能指回合約的某一條。
- 產出即預覽:產生靜態網頁給人檢視,寄送仍由人在信件系統完成。
- 產出檔案前自我驗算:9 條驗算涵蓋跨年、補假與各方案的位移,改壞了當場失敗。
- 三條規則收件:金額相符、統一編號相符、辨識信心度不為「低」。
- 限流先於 OCR,避免公開網址被拿來燒費用。
程式碼與實作細節收在頁面下方,需要時再展開。
你的情況像這樣嗎?
- 信上的日期要指回合約,算錯一天就是一來一往的確認。
- 同一種信有好幾種版本,每個月要重寫一輪。
- 要收合作方的發票或憑證,金額和統編得逐張人工核對。
更多成果與運作條件
8 封信一次產齊
方案與狀態的所有組合由同一支產生器輸出,改文案只需改一處
9 條驗算先於產出檔案
日期算式被改壞時當場失敗,不會產出錯誤日期的信
3 條 OCR 規則
金額、統一編號、辨識信心度,三條全過才算收件
- 0 個外部套件:產生器只用 Node.js 內建模組,不需要 npm 安裝
以上為本案既有成果與運作條件;估計值依原始資料標示,其他專案需另行評估,並非通用成效保證。
挑戰:原本遇到的困難
撥款通知信上的日期是合作方對帳的依據,不能自己挑。合約把它們綁死:通知寄出後 7 個工作日內開立發票,再對到下一個約定撥款日(每月 10 日,遇假日順延)。每寄一封信都得翻一次合約、逐日數工作日,還要記得當年哪些日子是補假。
- 通知信長相不只一種:合作方案分三種,每種又有正常撥款、只提醒、需另收補償金等狀況,組合展開是 8 封內容不同的信,過去每月手寫一輪
- 通知寄出日依方案不同:同一句「7 個工作日加下一個 10 日」,換一個方案,結果落在不同的月份
- 算錯的代價高:合作方手上沒有合約可以自己驗算,日期錯了就是一來一往的確認
- 發票回收要核對:金額、統一編號與掃描清晰度原本全靠人工逐張比對;而上傳網址是公開的,每次又會呼叫按次計費的 OCR 服務
所以目標不是「跑得動」,而是信上每一個日期都能指回合約的某一條。
完整解決方案與設計取捨
把合約條文拆成兩張對照表(通知寄出日、撥款月位移)放在程式開頭,產生器依方案與狀態分支組出 8 封信,日期由程式算好後直接寫進信件文字。產出檔案之前先跑 9 條 assert(程式內建的斷言檢查)驗算日期式。發票則由每家合作方各一組專屬識別碼(token)的上傳網頁接收,經 OCR(圖片辨識文字)擷取欄位後套用三條規則,全部通過才收件。
核心設計
- 合約條文常數化:通知日與撥款月位移的對應寫在常數旁,註解標出合約出處
- 產出即預覽:產生靜態 HTML 交給人檢視,寄送仍由人在信件系統完成
- 產出檔案前自我驗算:9 條 assertion 涵蓋跨年、補假與各方案的位移
- 三條規則收件:金額完全相符、統一編號相符、辨識信心度不為「低」
- 限流先於 OCR:同一 token 六小時內最多 20 次,超過就不再呼叫 OCR
技術與實作細節
展開完整實作說明
合約條文寫在常數旁邊
NOTICE_DAY(通知寄出日)與 PAY_MONTH_OFFSET(撥款落在結算月之後第幾個月)是把合約條文搬進程式的兩張表。寫入前,已把三份合約裡「明細通知寄出日」「發票開立期限」「撥款日」的條文逐字對過一遍,再把對應關係註解在表格旁。之後跟法務對條文時,讀註解就夠,不必回頭翻整份合約。
合約沒寫清楚的地方也記在同一處。「7 個工作日」是否包含通知當天,合約未明定,目前採隔日起算,並在註解中標明這是假設,不是定案。
順延邏輯與假日表
假日表是寫死在程式裡的一份日期清單(Set),涵蓋 2026–2027 兩年的政府公告國定假日與補假。銀行營業日的判斷同時看週末與假日表;撥款日從約定的 10 日起每次加一天,直到遇到營業日為止。
這張表沒有接任何行事曆 API,超出涵蓋年份之後撥款日就會算錯,程式碼中有註解直接點出這一點。順延演算法本身與資料來源無關,將來要換成從別處讀取假日時,替換的只有這個常數。
八封信共用一組版面函式
八封信的問候語、金額表格、發票資訊區塊與防詐提醒大量重複。若各自維護八個範本,改一次版面就得改八處,很容易漏掉其中一封。因此版面拆成一組基礎函式(段落、表格、列、提示框、按鈕),兩種信件函式只負責以不同順序把它們組起來。
README 明訂不手改產出的 HTML,因為重跑會被覆蓋,文案一律改產生器後重新執行。
產出檔案前的九條驗算
執行時,腳本會在產出檔案前以 Node 內建的 assert 跑九條驗算,涵蓋撥款日順延(含跨年進位與國慶日補假等邊界)、各方案的撥款月位移,以及發票開立期限的算式。九條全部通過才寫入檔案。
這組驗算沒有覆蓋率報告,它只做一件事:日期算式被改壞時當場失敗,而不是默默產出一個錯誤的日期。
預覽與列印,寄送留給人
產生器會把 logo 與字型編碼成文字(base64)後直接嵌入單一頁面的 index.html,斷網也能檢視;另外產出逐信一頁的列印版,交由無介面瀏覽器(不開視窗、在背景運作的瀏覽器)轉成 PDF。
寄送刻意不放在這支工具裡。信一寄出就是對外的付款承諾,中間保留一道人工檢視比較穩妥。
發票上傳:token 與三條規則
合作方沒有系統帳號,身分只靠每家一組的專屬 token。網頁與實際處理上傳的函式各自驗證一次 token,因為處理函式可以被直接呼叫,只驗網頁那一層等於沒驗。缺 token、token 錯誤、或查無當期資料,一律回覆同一句話,避免錯誤訊息被拿來試探其他合作方的 token。上傳的檔案只在單次請求的記憶體中驗證,不寫入雲端硬碟。
OCR 服務本身會給出「是否通過」的判斷,但那是為內部報帳設計的,與「合作方開出的發票是否正確」不是同一件事,所以不採用,改由三條規則判定:金額必須完全相符、統一編號須對得上公司、辨識信心度不得為「低」。任一條不過就退回,並說明原因。
限流卡在呼叫 OCR 之前。OCR 按次計費,而公開網址任何人都能打,所以以 token 為 key,六小時最多 20 次。這個計數的目的是擋住帳單,不是做稽核用的精確紀錄,因此允許它不是絕對精準。
目前上傳網頁已部署,但尚未發給任何一家合作方。每家合作方的專屬 token 與當期應開立金額,仍由財務人員維護在共用試算表中;這也是結算依據目前唯一的人工環節,合作方主檔、合約條款與實際入座資料尚未串接。