最終更新: 2026/06/22 18:56
MAS-175: OCR 失敗 PDF の手動取込・分類ツール
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-175 |
| カテゴリ | 自動入力パイプライン・OCR |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 6-8h(UI 構築を含む) |
| 対象ファイル | 500_import/502_receipt_reader.js / 500_import/504_invoice_importer.js / templates/operations_sidebar.html(新規セクション) / templates/ocr_fallback_form.html(新規) / 400_domain/414_ocr_fallback_service.js(新規) / 000_infra/002_constants.js(定数追加) / docs/_config.json |
| 前提案件 | MAS-147 v2(特に Step 4-C runInvoiceBatch_ / 99_error/ フォルダ運用完了) / MAS-216(Vertex AI 移行・Gemini 直接投入基盤) |
| 関連案件 | MAS-147 v2(HitL サイドバー) / MAS-157(写真レシート OCR) / MAS-216(Vertex AI) / MAS-169(マスタインライン自動登録) / MAS-150(取込後マスタ提案) |
目的
MAS-147 v2 で DocAI 抽出失敗した請求書 PDF(99_error/ 配置)を「闇に消さず」救済する。画像化 PDF・手書き混在・DocAI 未対応フォーマット・請求書でない誤投入等を手動入力 + Gemini 直接投入の 2 系統で最後まで処理し、月末締め時点で「取込漏れ PDF の放置」をゼロにする。
現在のコード
MAS-147 v2 runInvoiceBatch_ の失敗パス(Step 4-C 実装後)
失敗時の挙動は以下のみで、救済 UI は存在しない:
// 500_import/504_invoice_importer.js(I-03 v2 実装想定)
function handleError_(fileId, error) {
// 1. Drive ファイルを 99_error/ 配下に移動
moveFileTo99Error_(fileId, error);
// 2. persistLog に失敗記録(ファイル名・理由・スタックトレース)
Utils.persistLog('error', 'OCR抽出失敗', { fileId, error: error.message });
// 3. 以降ユーザー操作なし(99_error/ はユーザーが手動で見る運用)
}
関連既存関数
500_import/502_receipt_reader.js—importReceiptPdfs()/callGeminiForReceiptOnVertex_()(MAS-216 で Vertex AI 化予定): Gemini 直接投入の実装知見を流用200_data/202_repository.js—PartnerRepository.findAll() / findAsMap(): 手動入力フォームのプルダウン200_data/202_repository.js—InvoiceRepository.findAll() / save() / append(): 手動入力後の INV 登録400_domain/413_invoice_matcher.js(MAS-147 v2 で新規追加予定): 手動入力後の候補マッチング合流先000_infra/004_utils.js—Utils.getFolderByKey('EVIDENCE_FOLDER_ID')/Utils.toastResult/Utils.persistLog
修正方針
本案件は 5 Step で段階実装する。Step 1-3 は手動救済の最小経路、Step 4-5 は自動化拡張。
Step 1: 99_error/ PDF 一覧 UI(サイドバー「🚨 OCR 失敗 PDF」セクション)
templates/operations_sidebar.htmlに新規セクション🚨 OCR 失敗 PDFを追加(既存「📥 経理業務(RPA / Action)」セクションの下)400_domain/414_ocr_fallback_service.js新規作成し、公開関数listFailedPdfs()を実装Utils.getFolderByKey('EVIDENCE_FOLDER_ID')→99_error/サブフォルダを取得FolderIteratorで全 PDF を列挙(.hasNext()必須: failure_patterns #21 対策)- 各 PDF について
{ fileId, fileName, size, createdAt, errorReason, previewUrl }を返す errorReasonは Drive description に格納された JSON(MAS-147 v2 Step 4-C パターン)から取得
- サイドバーは
google.script.run.withSuccessHandler(render).listFailedPdfs()で取得し、テーブル表示 - 各行に 4 アクション: 手動入力 / Gemini 再試行 / 対象外(分類して退避) / 削除
Step 2: 手動メタデータ入力フォーム(templates/ocr_fallback_form.html 新規)
- サイドバー「手動入力」クリック →
showManualEntryDialog(fileId)がダイアログを開く(google.script.run.openManualEntryDialog(fileId)) - ダイアログ左側に PDF プレビュー(
iframe src="https://drive.google.com/file/d/{fileId}/preview")、右側にフォーム - 入力項目(10 項目):
- 取引先名(
PartnerRepository.findAll()でプルダウン化 + 自由入力、未登録は MAS-169 インライン自動登録フローへ) - 税込金額 / 税抜金額 / 消費税(三者整合自動検証: 税抜 + 消費税 = 税込、差額 ±1 円許容)
- 発生日 / 決済日(日付ピッカー)
- T 番号(
/^T\d{13}$/形式バリデーション、空欄許可) - docNumber(請求書番号、自由入力)
- 摘要(自由入力)
- 証憑種別(Step 3 の 5 択ラジオボタン)
- 取引先名(
- 「登録」ボタン →
google.script.run.submitManualEntry(fileId, formData)で送信 - サーバー側:
OcrFallbackService.submitManualEntry(fileId, data)が以下を実行- バリデーション(必須項目・形式・三者整合)
- 証憑種別が「請求書」→ Step 4-D HitL サイドバーの
matchInvoiceに合流(MAS-147 v2InvoiceMatcher.match()呼び出し) - 証憑種別が「請求書」以外 → Step 3 の退避フォルダ処理
- Drive ファイルを
99_error/→02_processed/or 該当フォルダへ移動
Step 3: 書類種別の分類・退避フォルダ運用
- 証憑種別 5 択: 請求書 / 見積書 / 領収書 / 契約書 / その他
- 請求書: MAS-147 v2 の通常フロー(
matchInvoice→ HitL 承認 →InvoiceRepository.append)に合流 - 領収書:
35_wrk_receiptに直接登録(MAS-157 の写真レシート OCR 経路と統合。既存502_receipt_reader.jsの知見流用) - 見積書:
06_not_invoice/見積書/YYYY-MM/フォルダに退避。32_wrk_invoiceには登録しない。将来 I-XX(見積書管理案件・未起票)で対応予定の旨を仕様書末尾にメモ - 契約書:
06_not_invoice/契約書/YYYY-MM/フォルダに退避。将来案件扱い - その他:
06_not_invoice/その他/YYYY-MM/フォルダに退避 - 退避先フォルダは
Constants.FOLDER_KEYS.NOT_INVOICE_FOLDER_ID(新規追加)で一元管理
Step 4: Gemini 直接投入フォールバック
- サイドバー「Gemini 再試行」クリック →
OcrFallbackService.retryWithGemini(fileId)を呼ぶ - 内部実装: 既存
callGeminiForReceiptOnVertex_(pdfBlob, prompt)(MAS-216 完了後)を流用し、請求書用プロンプトに差し替えて構造化抽出- 抽出対象: 取引先名 / 税込金額 / 税抜金額 / 消費税 / 発生日 / docNumber / T 番号 / 摘要
- プロンプトは JSON Schema 付きで「必ず JSON で返答。欠落フィールドは null」と明示(failure_patterns #27 対策: 空レスポンス・名前変動に備えて post-processing で検証)
- 成功時: 抽出結果 +
confidence: 0.6固定(Gemini はスコア自己申告精度が低いため保守的に固定値)を MAS-147 v2 のInvoiceMatcher.match()に渡し、通常 HitL フローに合流 - 失敗時(JSON パース失敗 or 必須フィールド欠落): Step 2 の手動入力フォームにフォールバック(抽出できた項目は初期値として表示)
Step 5: 入力後のフロー分岐
手動入力 or Gemini 再試行で抽出が完了した後の 3 分岐:
- HitL サイドバー合流: 既存 INV との候補マッチがあれば MAS-147 v2 HitL サイドバーへ(
matchInvoice→ 候補提示 → 承認) - 直接新規 INV 作成: マッチ候補 0 件の場合、
InvoiceRepository.append(newDto)で 32_wrk_invoice に新規登録(請求ステータス='未処理'で承認待ち)。取引先マスタに未登録なら MAS-169 インライン自動登録で 12_mst_partner に追加提案 - 対象外として別フォルダ保管: 証憑種別「その他」で登録した場合、
06_not_invoice/その他/に格納して終了(32/35 タブ登録なし)
影響範囲
変更ファイル
| ファイル | 変更種別 | 量 |
|---|---|---|
400_domain/414_ocr_fallback_service.js(新規) | 新規 | ~300 行 |
templates/operations_sidebar.html | 追記(1 セクション) | ~30 行 |
templates/ocr_fallback_form.html(新規) | 新規 | ~250 行(HTML + JS + CSS) |
000_infra/002_constants.js | FOLDER_KEYS.NOT_INVOICE_FOLDER_ID 追加、MENU_DEFINITION に新セクション追加 | ~10 行 |
docs/_config.json | nav 追加 | ~3 行 |
500_import/504_invoice_importer.js(MAS-147 v2 実装済想定) | handleError_ 内で Drive description に JSON 格納(MAS-147 v2 で既に組み込み済ならスキップ) | 既存想定 |
既存動作への影響
- MAS-147 v2 通常フロー(成功パス)に影響なし(本案件は失敗パスの新規経路のみ追加)
PartnerRepository/InvoiceRepositoryの読取のみ利用、既存 API に変更なし99_error/配下の既存 PDF がサイドバーに列挙される(運用上は望ましい)
注意事項
失敗パターン対策(docs/_internal/failure_patterns.md 該当部)
- #18-#20(命名造語禁止): 新規関数名・定数名は 002_constants.js / 003_contracts.js / 004_utils.js / 202_repository.js を Read した上で既存命名規則に合わせる。特に
FOLDER_KEYSの他キー名(EVIDENCE_FOLDER_ID等)の命名パターンを踏襲 - #21(getLastColumn 禁止): Drive フォルダ列挙は
FolderIterator.hasNext()必須 - #25(並列実装対称性): 「請求書」「領収書」「見積書」「契約書」「その他」の 5 種別を扱う分岐では、書き込み系の値を全分岐で明示的に set する(暗黙のデフォルトに頼らない)
- #26(oauthScopes 部分宣言禁止): 本案件で新規 OAuth スコープは不要のため
appsscript.jsonのoauthScopesには触らない。もし Drive 操作で権限不足が出た場合、既存スコープの自動検出に任せる - #27(API 仕様変動): Gemini API レスポンスは JSON Schema + post-processing 検証方式。
response.candidates[0].content.parts[0].text等の深いパスは try/catch で防御 - Drive description への JSON 格納は
JSON.stringify(reason).substring(0, 25000)で Drive 制限(description 最大 30KB)対策 - 手動入力フォームは GAS の
doGet(e)ベースの Web App ではなく、SpreadsheetApp.getUi().showModalDialog()で HTML ダイアログとして開く(既存サイドバーパターンと統一)
Human-in-the-Loop(HitL)ポリシー
- Gemini 再試行結果は 必ず人間承認 を経て 32_wrk_invoice に登録(自動登録禁止)
- 「削除」アクションは
SpreadsheetApp.getUi().alert(YES_NO_CANCEL)で二重確認 - 手動入力フォームの「登録」ボタンは連打防止(
disabled切替)
Step 1: 99_error/ PDF 一覧 UI(サイドバー「🚨 OCR 失敗 PDF」セクション)
Step 2: 手動メタデータ入力フォーム
Step 3: 書類種別の分類・退避フォルダ運用
Step 4: Gemini 直接投入フォールバック
Step 5: 入力後のフロー分岐
影響範囲
注意事項
エッジケース
| # | 条件 | 検知方法 | 期待される挙動 | ログ出力 |
|---|---|---|---|---|
| E01 | 99_error/ フォルダ自体が存在しない(MAS-147 v2 実装前 or 削除された) | Utils.getFolderByKey('EVIDENCE_FOLDER_ID') → getFoldersByName('99_error') の .hasNext() が false | サイドバーに「失敗 PDF は 0 件です(99_error/ 未作成)」を表示。エラー扱いしない | persistLog('info', '99_error not found') |
| E02 | 99_error/ 配下に PDF が 0 件 | FolderIterator.hasNext() が初回 false | サイドバーに「失敗 PDF は 0 件です」を表示 | — |
| E03 | Drive description が JSON 不正(破損・旧形式) | JSON.parse() が throw | errorReason: '(解析不能)' として一覧に表示(UI 側で赤字警告) | persistLog('warn', 'description parse failed', {fileId}) |
| E04 | Gemini API が 429 (Rate Limit) / 503 (Unavailable) | UrlFetchApp.fetch の応答ステータスを検査 | UI に「Gemini 一時エラー。数分後に再試行してください」を表示。99_error/ から移動しない | persistLog('error', 'gemini 5xx/429', {fileId, status}) |
| E05 | Gemini レスポンスが JSON パース失敗(マークダウン混入等) | JSON.parse(responseText) が throw | 手動入力フォームに自動遷移(Gemini の生テキストを摘要欄の初期値として提示) | persistLog('warn', 'gemini JSON parse failed', {fileId}) |
| E06 | Gemini は成功したが必須フィールド(total_amount / vendor)が null | 抽出結果に対し必須項目チェック | 手動入力フォームに自動遷移(取得できた項目は初期値、欠落項目は空欄) | persistLog('info', 'gemini partial extract', {fileId, missing}) |
| E07 | 税抜 + 消費税 ≠ 税込(差額 > ±1 円) | フォーム送信前にクライアント側 JS で検証 | 「金額の整合性が取れていません(差額: X 円)」を赤字表示して送信ブロック | — |
| E08 | 取引先名が PartnerRepository 未登録 | PartnerRepository.findAsMap().has(name) === false | 「新規取引先として 12_mst_partner に追加しますか?」ダイアログ(MAS-169 インライン自動登録)。YES → 追加後に INV 登録、NO → 「手入力扱い(マスタ未登録)」として INV 登録 | persistLog('info', 'new partner registered', {name}) |
| E09 | T 番号が /^T\d{13}$/ 不一致(桁数違い・プレフィックス欠落) | クライアント側 regex 検証 | 「T 番号は T + 13 桁の形式で入力してください」を赤字表示。空欄は許可(未登録事業者想定) | — |
| E10 | 同一 fileId の PDF が複数タブから同時に手動入力操作された(並行実行) | サーバー側で LockService.tryLock(5000) | 後勝ちではなく、ロック取得失敗で「他のユーザーが処理中です」エラー返却 | persistLog('warn', 'manual entry conflict', {fileId}) |
| E11 | 手動入力途中で Drive ファイルが削除された | DriveApp.getFileById(fileId) が throw | フォーム送信時に「対象 PDF は削除済み」エラーを返却。UI は一覧を再読込 | persistLog('warn', 'file deleted during entry', {fileId}) |
| E12 | 証憑種別「その他」選択だが、内容確認で請求書だった場合の事後訂正 | — | 06_not_invoice/その他/ に保管後、手動で移動 → 再度サイドバーで取込(運用対応。システム内での再分類 UI は v2 スコープ外) | — |
| E13 | NOT_INVOICE_FOLDER_ID スクリプトプロパティ未設定 | Utils.getFolderByKey が null 返却 | Step 3 退避処理で throw CFG: NOT_INVOICE_FOLDER_ID 未設定。UI に「管理者に連絡してください」表示 | persistLog('error', 'NOT_INVOICE_FOLDER_ID not set') |
| E14 | Gemini 抽出結果の日付が未来日付(誤認識) | サーバー側で date > today を検査 | 手動入力フォームに遷移し、日付欄を赤字マーク | persistLog('warn', 'future date extracted', {fileId, date}) |
| E15 | 同一 PDF を複数回「Gemini 再試行」した場合 | — | 毎回 Gemini を叩く(キャッシュなし・v1 スコープ)。料金累積に注意し、UI に「3 回以上の再試行は手動入力推奨」を表示 | persistLog('info', 'gemini retry count', {fileId, count}) |
| E16 | 画像化 PDF のサイズが Gemini API 上限(2025 時点 20MB)超過 | pdfBlob.getBytes().length > 20 * 1024 * 1024 を事前チェック | 「ファイルサイズが Gemini 上限を超えます。手動入力してください」を表示。Gemini は呼ばない | persistLog('warn', 'pdf too large for gemini', {fileId, size}) |
| E17 | 「削除」アクションの確認ダイアログで CANCEL | — | 何もしない(99_error/ に PDF を保持) | — |
実データ検証(MCP でのデータ確認が必要な項目)
実装着手前に以下を MCP 経由で確認する:
99_error/フォルダの実在と格納 PDF 数 — MAS-147 v2 実装後の運用状況を把握。PDF が 0 件ならテスト用に DocAI が失敗する PDF(手書き・画像化)を 2-3 件投入EVIDENCE_FOLDER_ID/NOT_INVOICE_FOLDER_IDスクリプトプロパティ — dev / prod 両方で設定されているか確認PartnerRepository.findAll()の取引先件数と表記ゆれ — プルダウン候補の規模感把握(件数が 500+ なら検索式プルダウンに切替検討)- Drive description の JSON 格納実例 — MAS-147 v2
handleError_が格納する JSON スキーマを確認し、errorReasonパース仕様を確定 - Gemini API レスポンス実例 — MAS-216 完了後、
callGeminiForReceiptOnVertex_のレスポンス構造(candidates[0].content.parts[0].text)を実リクエストで確認
関連ドキュメント
| 関連仕様書 | 関連箇所 |
|---|---|
| dev_mas-147_invoice_ocr_auto_posting.md | v2 Step 4-C runInvoiceBatch_ / Step 4-D HitL サイドバー(本案件の合流先) |
| dev_mas-157_photo_ocr.md | 写真レシート OCR(画像化 PDF との統合余地) |
| dev_mas-169_master_auto_registration.md | 取引先マスタインライン自動登録(手動入力フォームで活用) |
| dev_mas-216_vertex_ai_migration.md | Vertex AI 移行(Gemini 直接投入基盤) |
| dev_mas-152_evidence_filename_rename.md | 退避先フォルダのリネーム運用 |
| ADR-0007 Gemini Receipt Parsing | Gemini 直接投入の設計方針 |
人間が検討すべき事項
- Gemini 直接投入の料金影響 — MAS-216 並行稼働中は小規模だが、月 100 枚の失敗 PDF × 再試行 2 回 = 月 200 Gemini コール想定。
gemini-2.5-flashの単価で試算し、月 1000 円超なら料金アラート設定 - 「対象外」判定の自動化閾値 — 現状は人間が手動分類するが、将来 Gemini に「この PDF は請求書ですか?」と 1 問問い合わせて自動分類する経路も検討可(精度と料金次第)
- 手動入力の負荷軽減策:
- 過去入力履歴の類推(同じ取引先名 + 金額レンジなら自動補完)
- OCR 生テキスト(DocAI が部分的に返したもの)を摘要欄に初期値表示
- ショートカットキー対応(Cmd+Enter で送信)
- MAS-157(写真レシート OCR)との統合余地 — 画像化 PDF は実質写真レシートと同等なので、Step 4 の Gemini 再試行で「領収書プロンプト」に切替える経路を入れるか
- 「見積書」のための独立案件起票タイミング — 見積書が月 10 件以上になったら I-XX(見積書管理)を新規起票。本案件では
06_not_invoice/見積書/への退避のみ - サイドバー配置 — 「🚨 OCR 失敗 PDF」セクションを既存「📥 経理業務」の下に置くか、独立セクションにするか(MAS-217 のサイドバー統合方針との整合)
- UI: サイドバー vs モーダルダイアログ vs 別ウィンドウ — 手動入力フォームを 3 選択肢のどれにするか(現状案: モーダルダイアログで PDF プレビュー + フォームを横並び)
- Gemini の信頼度スコア — Gemini 自身の confidence 自己申告は精度が低いとされるため、本案件では固定値
0.6を付与。実運用で Gemini 結果の正答率を計測し、後日閾値調整 - prod 環境での初期展開 — 運用開始直後は dev で 20 件程度の失敗 PDF に対して手動入力 + Gemini 再試行の混合テストを実施してから prod push
- 監査ログ — 「削除」アクションは 98_audit_log に
Utils.auditLog('DELETE', '99_error/PDF', fileId, ...)で記録必須
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-175「OCR 失敗 PDF の手動取込・分類ツール」を実装してください。
前提として MAS-147 v2 Step 4-C `runInvoiceBatch_` / `99_error/` フォルダ運用と MAS-216 Vertex AI 移行が完了していることを確認してください。
## 実行前タスク
1. `docs/dev/dev_mas-175_ocr_fallback_manual_entry.md` 全文を読み込み設計方針を把握
2. `docs/dev/dev_mas-147_invoice_ocr_auto_posting.md` の v2 Step 4-C / 4-D セクションを読み合流仕様を把握
3. `500_import/504_invoice_importer.js`(MAS-147 v2 実装済)の `handleError_` 関数を読み Drive description JSON 形式を確認
4. `500_import/502_receipt_reader.js` の `callGeminiForReceiptOnVertex_` を読み Gemini 直接投入の実装知見を把握
5. `200_data/202_repository.js` の `PartnerRepository` / `InvoiceRepository` の API シグネチャを確認
6. `templates/operations_sidebar.html` のセクション構成を確認(新規セクション追加位置の特定)
7. `000_infra/002_constants.js` の `FOLDER_KEYS` / `MENU_DEFINITION` の命名規則を把握
## 修正対象ファイル
- `400_domain/414_ocr_fallback_service.js`(新規作成のみ。約 300 行)
- `templates/ocr_fallback_form.html`(新規作成のみ。HTML+JS+CSS 約 250 行)
- `templates/operations_sidebar.html`(新セクション「🚨 OCR 失敗 PDF」追記のみ)
- `000_infra/002_constants.js`(`FOLDER_KEYS.NOT_INVOICE_FOLDER_ID` と `MENU_DEFINITION` 追記のみ)
- `docs/_config.json`(nav 追記)
## 実装内容
### Step 1: 定数追加
`000_infra/002_constants.js` の `FOLDER_KEYS` に以下を追加:
```js
NOT_INVOICE_FOLDER_ID: 'NOT_INVOICE_FOLDER_ID',
```
`MENU_DEFINITION` に新セクション「🚨 OCR 失敗 PDF」を追加(項目: 失敗PDF一覧 / 手動入力 / Gemini 再試行)。
### Step 2: サービスクラス作成
`400_domain/414_ocr_fallback_service.js` 新規作成:
- `listFailedPdfs()`: 99_error/ 内の PDF 一覧 + メタデータ返却
- `retryWithGemini(fileId)`: Gemini 直接投入経路
- `submitManualEntry(fileId, formData)`: 手動入力受け口
- `classifyAndMove(fileId, docType)`: 書類種別分類して退避
- `deleteFailedPdf(fileId)`: 削除(auditLog 必須)
### Step 3: フォーム HTML 作成
`templates/ocr_fallback_form.html` を `templates/operations_sidebar.html` のコーディング規約(`google.script.run` + `withFailureHandler` パターン)に合わせて作成。
左半分: PDF プレビュー iframe、右半分: 10 項目入力フォーム + 三者整合検証 JS。
### Step 4: サイドバーに新セクション追加
`templates/operations_sidebar.html` の「📥 経理業務(RPA / Action)」セクションの下に「🚨 OCR 失敗 PDF」セクションを追加。クリックで `showOcrFallbackSidebar()` 呼び出し。
### Step 5: nav 登録
`docs/_config.json` の `nav` 配列 §E.6(パイプライン・RPA・外部連携)に本仕様書を追加。
## 制約
- MAS-147 v2 通常フロー(成功パス)のコードには一切変更を加えない
- `502_receipt_reader.js` の `callGeminiForReceiptOnVertex_` を流用するだけで、同関数の既存実装を変更しない
- `appsscript.json` の `oauthScopes` には触らない(failure_patterns #26)
- 新規関数の命名は 002/003/004/202 の既存関数を Read してから決定(#18-#20)
- Drive 列挙は `FolderIterator.hasNext()` 必須(#21)
- 5 種別分岐は全分岐で明示的に値を set(#25)
## エッジケース
仕様書の §エッジケース テーブル(E01〜E17)を全件カバーすること。特に:
- E04/E15/E16: Gemini API エラー・サイズ制限
- E07: 税抜+消費税≠税込の三者整合検証
- E10: LockService.tryLock による並行制御
- E17: 削除ダイアログの CANCEL 対応
## 実データ検証
実装着手前に以下を MCP で確認:
- `99_error/` 配下の PDF 件数(0 件ならテスト用に 2-3 件投入)
- `EVIDENCE_FOLDER_ID` / `NOT_INVOICE_FOLDER_ID` スクリプトプロパティの設定状況
- `PartnerRepository.findAll()` の取引先件数(500+ なら検索式プルダウンに切替検討)
- Drive description の JSON 実例(MAS-147 v2 格納スキーマ確認)
## 動作確認
1. `npm run push:dev` で dev 環境にデプロイ
2. dev スプレッドシートのサイドバーから「🚨 OCR 失敗 PDF」を開く
3. `99_error/` に手動で DocAI 失敗 PDF を投入し、一覧に表示されることを確認
4. 「手動入力」をクリック → フォームが開き、PDF プレビューと入力欄が表示されることを確認
5. 入力 → 送信 → `32_wrk_invoice` に新規レコードが追加されることを確認
6. 「Gemini 再試行」をクリック → 成功時は HitL サイドバーに遷移、失敗時は手動入力フォームに初期値付きで遷移することを確認
7. 「対象外」で「見積書」選択 → `06_not_invoice/見積書/YYYY-MM/` に移動することを確認
8. 「削除」で CANCEL → PDF が `99_error/` に残ることを確認
9. 「削除」で OK → PDF が削除され、98_audit_log に記録されることを確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---|:---:|---|
| Phase 1 調査 | あり | MAS-147 v2 / 502_receipt_reader の既存関数シグネチャ裏取り |
| Phase 2 清書 | なし | 設計は完了済。Step ごとに書き下すのみ |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1 定数追加 | Claude Haiku 4.5 | 機械的追記のみ |
| Step 2 サービスクラス | Claude Sonnet 4.6 | 5 公開関数の設計判断(Lock パターン・Drive API 呼出順) |
| Step 3 HTML フォーム | Claude Sonnet 4.6 | クライアント JS での三者整合検証・既存サイドバー HTML パターン適用 |
| Step 4 サイドバー追記 | Claude Haiku 4.5 | 既存セクション下に追加のみ |
| Step 5 nav 登録 | Claude Haiku 4.5 | _config.json 末尾追加 |
| 統合テスト設計 | Claude Opus 4.7 (1M context) | 99_error/ → 手動入力 → HitL 合流の end-to-end シナリオ検証 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-22 | 初版作成(PR #299 予定)。5 Step 構成(一覧 UI / 手動入力 / 書類分類 / Gemini フォールバック / フロー分岐)。MAS-147 v2 Step 4-C の 99_error/ 運用を前提とし、MAS-216 Vertex AI 移行完了後に Step 4 Gemini 再試行が有効化される設計。エッジケース 17 件・人間検討事項 10 件定義 |
仕様書作成プロンプト
展開して表示(本仕様書を生成する際のユーザー投入プロンプト全文)
【サブワークスペースで実行】
「OCR 失敗 PDF の手動取込・分類ツール」の新規案件を
docs/_internal/TODO_future.md に起票してください。
git pull origin main してから作業。
## 案件概要(TODO_future.md 用)
- 案件ID: 未使用の I-番号 (例: I-31, I-32 等)
- 案件名: OCR 失敗 PDF の手動取込・分類ツール
- カテゴリ: 自動入力パイプライン・OCR
- Phase: P2
- 優先度: ★★
- ステータス: 未着手
- 前提案件: I-03 v2 完了 (特に Step 4-C runInvoiceBatch_ / 99_error フォルダ運用)
- 関連案件: I-03 v2 (HitL サイドバー)、I-13 (写真レシート OCR)、N-40 (Vertex AI)
## 詳細(dev_I-XX_ocr_fallback_manual_entry.md として作成)
標準テンプレートに従って仕様書を作成。要点:
**ユースケース**:
- I-03 v2 の `runInvoiceBatch_` で DocAI 抽出失敗した PDF (99_error/ 配置) を救済する
- 発火シナリオ:
- 画像化 PDF (スキャン画質が悪い、手書き混在)
- そもそも請求書でない (見積書 / 納品書 / 契約書 が間違って投入された)
- DocAI 未対応の特殊フォーマット (縦書き、枠線混み合った表)
- totalAmount / vendor 等の必須フィールドが抽出不能
**設計方針**:
### (A) 99_error/ の PDF 一覧 UI
- サイドバー新規セクション「🚨 OCR 失敗 PDF」
- ファイル名・サイズ・Drive プレビュー・処理失敗理由を一覧表示
- 各 PDF に「手動入力」「Gemini 再試行」「対象外 (別ワークフロー)」「削除」のアクション
### (B) 手動メタデータ入力フォーム
- Step 4-D の HitL サイドバーと別画面 or タブ切替
- 入力項目: 取引先名 / 税込金額 / 発生日 / 決済日 / 税抜金額 / 消費税 /
T 番号 / docNumber / 摘要 / 証憑種別
- 取引先名は PartnerRepository とプルダウン連携
- 金額は自動で税抜/税込/消費税の三者整合検証
- 入力後、I-03 v2 の matchInvoice に投入して候補提示 → 通常 HitL フローに合流
### (C) 書類種別の分類
- 「請求書」「見積書」「領収書」「契約書」「その他」から選択
- 「請求書」以外は別フォルダ (06_not_invoice/ 等) に退避、32_wrk_invoice には入れない
- 「見積書」は将来 I-XX (見積書管理) 案件で対応予定の旨メモ
### (D) Gemini 直接投入フォールバック
- DocAI 失敗時、Gemini gemini-2.5-flash に PDF を直接投げて構造化抽出を試みる
- Gemini 成功すれば I-03 v2 通常フローに合流 (merged + confidence 生成)
- Gemini も失敗なら (B) 手動入力へ
- I-03 v1 で実装済 `callGeminiForReceiptOnVertex_` の知見流用可
### (E) 入力後のフロー分岐
- Step 4-D HitL サイドバーに合流(候補提示 → 承認 → 32_wrk_invoice 更新)
- OR 直接新規 INV 作成(26_bud_adhoc 新規登録も提案)
- OR 「対象外」として別フォルダ保管
## 検討事項
- Gemini 直接投入は画像化 PDF に強いが、手書きは精度低下
- 手動入力の負荷軽減策(過去入力履歴から類推 / マスタ参照プルダウン等)
- Gemini 直接投入の料金 (N-40 並行稼働中は負担小)
- 「対象外」判定の閾値(画像のみ / テキスト抽出不能 / 請求書でない)
- I-13 (写真レシート OCR) との統合余地(画像 PDF は写真レシート相当)
## 前提案件・依存関係
- I-03 v2 Step 4-A〜D 完了 (特に runInvoiceBatch_ + HitL サイドバー)
- N-40 Vertex AI 移行完了 (Gemini 直接投入用)
- PartnerRepository / InvoiceRepository 利用
## 出力フォーマット
標準テンプレート準拠:
- 概要 / 目的 / 現状のコード / 修正方針 / 影響範囲 / 注意事項 /
エッジケース / 実データ検証 / 関連ドキュメント / 人間が検討すべき事項 /
実装プロンプト / 推奨実行モデル / 変更履歴
## 関連情報源
- 現状の I-03 v2 `handleError_()`: 99_error/ 移動 + persistLog 記録のみ
- Drive description の JSON 格納パターン (Step 4-C 参考)
- 失敗パターン #18 (DocAI 信頼度が低い国際フォーマット請求書)