多張保單 OCR — 整合指南

本文涵蓋流程、跨 endpoint 概念、業務情境,協助前端與 PM 快速理解全貌。
欄位 / Request / Response 細節請查 apidocs(單一真相來源)。
版本:v1.71.0 對象:iOS / Android App 每日額度:單張 / 多張共用 20 次 狀態:後端程式碼初版完成

本案做了什麼

新增「保險公會保險存摺投保紀錄表」一次辨識多張保單功能;既有單張 OCR 流程同步擴充加密 PDF 支援要保人比對(對不到既有客戶名單時可保存 OCR 原字串)。
向後相容:所有新欄位 absent 時走既有單張 OCR 行為,老版 App 不受影響。

Endpoint 速查

點 Endpoint 名稱跳至 apidocs 看完整欄位規格。
Endpoint狀態本案變更摘要用途
IE_S_102 擴充 +isMultiPolicy / +filePassword;response +isEncrypted / +quotaRemaining 上傳檔案(單張 / 多張共用入口)
IE_S_105 擴充 response +isMultiPolicy / +policyCount / +quotaRemaining + 多張新階段(pdf_parse / meta_extract) 輪詢狀態 + 重入檢查
IE_S_103 擴充 response +holderMatch / +isMultiPolicy / +policyCount / +quotaRemaining;多張時 policyData=null 單張結果取回
IE_S_113 新增 多張完整結果(per-policy holderMatch / insuredMatch / productMatch / premiumEstimate) 多張結果取回
IE_S_107 / IE_S_108 擴充 + optional recognizedPolicyId(多張時必填) 商品候選清單 / 切換
IE_S_116 新增 atomic 批次儲存 + 逐欄位錯誤回應 多張批次儲存
IE_S_104 / IE_S_106 不變 單張確認 / 放棄(多張不需 104,116 自動 invalidate)
IE_S_54 / IE_S_59 擴充 +applicantRawString(IE_S_54);+applicantRawString / +holderMatch(IE_S_59) 既有單筆保單編輯 / 取得

多張 OCR 完整流程

App 與後端的時序圖,標出每個 endpoint 在生命週期中的位置。
FIG 1 — 多張 OCR 上傳 → 輪詢 → 取結果 → 儲存
App(iOS / Android) Backend API 使用者選 PDF + 輸密碼 IE_S_102(multipart) isMultiPolicy=true · filePassword · customerId · files 200 OK sessionId · isEncrypted · quotaRemaining=19 · ocrStatus=PROCESSING loop · 每 2–3 秒 IE_S_105 輪詢狀態 + 取 isMultiPolicy 用於 dispatch 200 OK hasPending · isMultiPolicy · ocrStatus · progress · currentStage 當 ocrStatus = "SUCCESS" 且 isMultiPolicy=true → 改打 IE_S_113 IE_S_113(新) sessionId 200 OK policyData[](每張含 recognizedPolicyId、holderMatch、productMatch...) customer · unknownFieldsHint? · insuredCandidate? optional · 商品切換 IE_S_107 / IE_S_108 recognizedPolicyId · contractIndex 候選清單 / 切換後的 mappedFields IE_S_116(新) sessionId · customerId · policies[](每筆帶 recognizedPolicyId) 200 OK result="0" + savedPolicies[] or result="955" + failures[].fieldErrors[] IE_S_116 成功會自動 invalidate 暫存,不需再呼叫 IE_S_104。失敗 HTTP 仍為 200,需以 result 判斷。
看完上圖你應該知道 (1) 多張流程共 4 個必經 endpoint:102 → 105 → 113 → 116;(2) IE_S_105 的 isMultiPolicy 是 dispatch 關鍵;(3) IE_S_107/108 是選用(使用者切換商品時才打);(4) IE_S_116 失敗時用 failures[].fieldErrors[].field 定位卡片。

單張 / 多張 Dispatch 決策

App 在每個關鍵點都要用 isMultiPolicy 決定走單張路徑還是多張路徑。
FIG 2 — Client dispatch 決策樹
進入 OCR 頁面 或 上傳完成後 呼叫 IE_S_105 hasPending? 有未完成暫存? false → 顯示上傳入口 true ocrStatus? PROCESSING → 繼續輪詢 IE_S_105 FAILED → 處理 errorCode (969/971/950-966...) SUCCESS isMultiPolicy? 關鍵 dispatch false(單張) → IE_S_103 取結果 true(多張) → IE_S_113 取結果 同一個 isMultiPolicy flag 也出現在 IE_S_103 response,作為防呆校驗
為什麼需要 dispatch 單張結果結構與多張結果結構不同(多張是陣列、有 unknownFieldsHint / insuredCandidate)。isMultiPolicy 是 server 在 session 建立時記下、App 重入時取出的「我這個 session 是哪一種」。App 不應自己記錄這個 flag — server 是單一可信來源(支援跨裝置 + kill-restore)。

加密 PDF 錯誤路徑

PDF 密碼問題在 IE_S_102 上傳時同步判定,不會進入 PROCESSING、不會在 IE_S_105 輪詢時出現。
FIG 3 — 加密 PDF 同步錯誤(967 / 970)
App Backend API OCR 後端服務 IE_S_102 filePassword="錯誤密碼" + 加密 PDF /extract-async(multipart) pdf_password 轉發 PDF_PASSWORD_INCORRECT(同步) 不建立 task 200 OK · result="970" message="PDF 密碼不正確,請重新輸入" 同步流程:不會建立 OCR task、不會進 PROCESSING、IE_S_105 看不到此錯誤 App 應 inline error 提示,使用者可重新輸入密碼後直接重打 IE_S_102(不消耗額度)
967 OCR_PDF_ENCRYPTED — PDF 加密但 App 沒帶 filePassword(fallback;應由 pdf.js 預先攔截)
970 OCR_WRONG_PASSWORD — 密碼錯誤(App 端 inline error,可重新輸入後重送)
967 / 970 不消耗額度。額度只在 OCR 辨識成功階段扣 1 次。

Kill-Restore / 跨裝置重入

使用者切換 tab / kill app / 換裝置後,App 必須能正確接回未完成的 OCR session。
FIG 4 — 重入時的 dispatch(IE_S_105 是唯一入口)
切 tab / kill app App 記憶體被清 換裝置同帳號 完全沒有本地狀態 呼叫 IE_S_105 不傳 sessionId — server 依會員找最新 Response 取出 isMultiPolicy + ocrStatus 這兩個欄位是恢復狀態的關鍵 App 不本地記憶 — server 是 single source of truth PROCESSING → 繼續輪詢 SUCCESS + isMultiPolicy=false → IE_S_103 SUCCESS + isMultiPolicy=true → IE_S_113
Client 不本地持久化 OCR 狀態 為了支援跨 app lifecycle(kill-restore)與跨裝置同帳號同步,server 是 OCR session 狀態的 single source of truth。每次回到 OCR 頁面都先打 IE_S_105,從 response 取狀態,不從本地 cache 取。

核心概念 1:recognizedPolicyId

多張流程中每張保單的 correlation id,串連 IE_S_113(取結果)IE_S_107/108(商品切換)IE_S_116(批次儲存) 三個 endpoint,定位「哪一張保單」。後端不寫 DB、不理解格式,純做 echo back。
FIG 5 — recognizedPolicyId 生命週期
IE_S_113 response 後端為每張保單填 fallback id policyData[i]. recognizedPolicyId App 端處理 可採用 fallback,或為每張 保單覆寫為 client 生成的 UUID (建議覆寫以避免後端 id 撞 client 卡片) 後續 request 帶上 IE_S_107 / IE_S_108 / IE_S_116 指定「對哪張保單操作」 後端純做對應,不寫 DB IE_S_116 response echo back 至 savedPolicies[] / failures[] 為什麼不能用陣列 index? 多保單編輯頁的卡片狀態(編輯內容 / 是否被刪除 / 排序)由 App 維護,可能與 IE_S_113 回傳順序不同。 Index 在 App 端 reorder / filter 後容易失效。 用穩定 id 才能在「第 n 張儲存失敗」時準確標紅對應卡片。

核心概念 2:額度扣抵時機

單張 / 多張共用每日 20 次。額度只在 OCR 辨識成功時扣 1 次,不在儲存階段扣。
Endpoint是否扣額度說明
IE_S_102 上傳上傳成功不扣(含加密 PDF 967/970 同步錯誤)
IE_S_105 輪詢輪詢狀態,不影響額度
OCR 辨識成功✅ 扣 1 次內部由 OCR pipeline 完成階段觸發;前端觀察點:105/103/113 首次回 ocrStatus="SUCCESS" 時,quotaRemaining 已下降
IE_S_103 / IE_S_113 取結果不扣(即使首次取結果觀察到 quotaRemaining 變化,是 OCR pipeline 上一步扣的)
IE_S_104 / IE_S_106 / IE_S_116確認 / 放棄 / 批次儲存皆不扣
辨識失敗 / 放棄 / 解密失敗OCR 沒成功 → 不扣
前端顯示策略 所有 response 都有 quotaRemaining,App 直接顯示後端回值即可,不要自己算。額度扣抵時機完全由後端決定。

核心概念 3:暫存(OCR session)生命週期

一個 OCR session 從 IE_S_102 上傳 開始建立,保留 24 小時同一使用者同時只能有一個未完成 session,重複上傳會回 953。
FIG 6 — Session state 轉換
PROCESSING 建立後 (IE_S_102) SUCCESS 辨識完成 + 扣額度 IE_S_104 確認 / IE_S_116 儲存 → 自動 invalidate 暫存 IE_S_106 放棄 → 暫存 + 上傳檔案皆刪 EXPIRED 24h 未動作 24h
953 已有未完成 OCR 任務 — 同一 memberId 想再打 IE_S_102 時,需先用 IE_S_104 / IE_S_106 / IE_S_116 清掉前一個 session
952 暫存已過期 — IE_S_103 / 113 / 107 / 108 / 116 等想取已逾 24h 的 session 時回

業務情境決策表

常見情境對應的 endpoint、UX 處理、邊界注意事項。
情境 1

使用者上傳加密 PDF

步驟App 動作API
1App 用 pdf.js 偵測加密 → 跳「解密同意書」頁讓使用者輸入密碼
2本地驗證密碼正確 → 將「原始加密 binary + filePassword」一起上傳IE_S_102
3a同步收到 970(後端解鎖失敗) → inline error,使用者可重輸密碼
3b同步收到 967(沒帶密碼) → 補帶密碼後重打
3c成功收到 sessionId + isEncrypted=true → 後續正常輪詢
情境 2

OCR 辨識成功但要保人對不到既有客戶

步驟App 動作API
1取結果取到 holderMatch.matchResult="STRING_ONLY"IE_S_113 / IE_S_103
2UI 顯示原 OCR 字串(如 "李O堂")+ 橘字提示「要保人資料不全,建議擇日補齊」
3儲存時帶 applicantRawString="李O堂"applicantId=nullIE_S_116 / IE_S_54
applicantIdapplicantRawString 二擇一,每筆恰一個非 null。
情境 3

商品自動比對失敗 / 使用者想換商品

步驟App 動作API
1取結果看到 productMatch.matchType="MANUAL"(或 "NONE")IE_S_113
2aMANUAL 時 recommendations[] 已含推薦清單,直接顯示讓使用者選
2b使用者想看更多 → 取得完整候選清單IE_S_107
3使用者選了商品 → 重新計算 mappingIE_S_108
多張時 IE_S_107/108 需帶 recognizedPolicyId + contractIndex;單張時 recognizedPolicyId=null 即可。
情境 4

使用者切換 tab / kill app / 換裝置後回到 OCR 頁

步驟App 動作API
1進入頁面先打 IE_S_105(不傳 sessionId,依會員找最新)IE_S_105
2從 response 取 hasPending / isMultiPolicy / ocrStatus
3ahasPending=false → 顯示上傳入口
3bPROCESSING → 接續輪詢
3cSUCCESS + isMultiPolicy=true → 跳多保單編輯頁、打 IE_S_113IE_S_113
3dSUCCESS + isMultiPolicy=false → 跳單張結果頁、打 IE_S_103IE_S_103
情境 5

批次儲存有 1 張保單欄位驗證失敗

步驟App 動作API
1使用者按「全部完成」→ 帶完整 policies[]IE_S_116
2收到 HTTP 200 + result="955" + failures[]
3遍歷 failures[],按 recognizedPolicyId 找到對應卡片
4依 fieldErrors[].field 路徑(如 policyList.0.amount)自動滾到對應 UI 欄位 + 紅字提示
5修正後重打 IE_S_116(整批 atomic,不需挑出失敗的單獨送)IE_S_116
HTTP 仍為 200,需以 result 判斷。本 endpoint 不扣額度,失敗重打不影響額度。
情境 6

OCR 辨識失敗(非公會表格 / 資訊不全)

狀況errorCode建議文案
PDF 內容非公會表格969 NOT_POLICY_FORMAT「無法辨識為公會投保紀錄表」
辨識內容資訊不全971 INFO_INCOMPLETE「保單資訊辨識失敗,請改用其他方式上傳」
其他 OCR 失敗950 / 958–966顯示後端回的 message(已客製化文案)
辨識失敗皆不扣額度。errorCode 出現在 IE_S_105 / IE_S_103 / IE_S_113 的 response 內,非 HTTP 錯誤。

前端整合 Checklist

上傳階段

  • 多張入口 request 帶 isMultiPolicy=true;單張入口維持既有行為(不帶或 false)
  • 加密 PDF:pdf.js 本地驗證密碼正確後,將「原始加密 binary + filePassword」一併送 IE_S_102(不送明文 PDF)
  • 處理 953 重複任務 → 引導先確認 / 放棄
  • 處理同步 967 / 970 密碼錯誤 → inline error,不算辨識失敗(不會扣額度)

輪詢階段

  • 進入頁面先 IE_S_105 檢查未完成暫存(含跨裝置 / kill-restore)
  • 上傳後 IE_S_105 每 2–3 秒輪詢;UI 顯示 stageLabel + progress
  • 多張新增階段 pdf_parse / meta_extract 要有對應中文文案
  • FAILED 時讀 errorCode,特別針對 969 / 971 客製文案

取結果階段

  • SUCCESS 時依 isMultiPolicy dispatch:true → IE_S_113;false → IE_S_103
  • IE_S_113 收到後為每張保單覆寫自己生成的 recognizedPolicyId(建議用 UUID)
  • 處理 insuredCandidate(被保人姓名不一致對話框)
  • 處理 unknownFieldsHint(未知欄位提示對話框)
  • holderMatch.matchResult="STRING_ONLY" → 顯示原字串 + 「資料不全」橘字
  • productMatch.matchType="MANUAL" → 呈現 recommendations 讓使用者選

儲存階段

  • IE_S_116 request 每筆帶 recognizedPolicyId
  • applicantId / applicantRawString 二擇一(每筆恰一個非 null)
  • 處理 result="955":依 failures[].fieldErrors[].field 自動定位 UI 欄位
  • 不需呼叫 IE_S_104(IE_S_116 成功後自動 invalidate 暫存)
  • quotaRemaining 直接顯示後端回值,不自己算

PM Checklist

業務規則確認

  • 每日 OCR 額度:單張 / 多張共用 20 次,多張 PDF 內 N 張保單只算 1 次
  • 額度扣抵時機:OCR 辨識成功階段扣 1 次;失敗 / 放棄 / 解密失敗皆不扣
  • 辨識結果暫存 24 小時
  • 同一使用者同時只能有一個未完成暫存

UX 文案確認

  • 967 / 970(密碼問題):inline error 文案
  • 969(非公會表格):「無法辨識為公會投保紀錄表」
  • 971(資訊不全):「保單資訊辨識失敗,請改用其他方式上傳」
  • STRING_ONLY 要保人提示:「要保人資料不全,建議擇日補齊」
  • insuredCandidate(被保人姓名不一致)對話框文案
  • unknownFieldsHint(繳費類別 unknown / 職業等級空)對話框文案

部署 / 整合優先序

優先序後端App 對應 stage
1IE_S_102 擴充(additive,無 breaking)Stage 1:入口 + 既有單張流程修補
2IE_S_105 擴充(依賴 102 persist flag)Stage 2:多 source dispatch / kill-restore
3IE_S_113 / IE_S_116 新 + IE_S_54 擴充Stage 3:多保單結果頁 + 批次儲存
4errorCode 967 / 970 / 969 / 971 finalizeStage 4:errorCode 對映收尾
建議:App 端 IE_S_105 dispatch 邏輯 ship 前,後端必須先 ship 同版 IE_S_105(含 isMultiPolicy 欄位)。否則多張 session 重入會被誤判為單張。