概要

項目内容
案件IDMAS-175
カテゴリ自動入力パイプライン・OCR
PhaseP2
優先度★★
所要時間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.jsimportReceiptPdfs() / callGeminiForReceiptOnVertex_()(MAS-216 で Vertex AI 化予定): Gemini 直接投入の実装知見を流用
  • 200_data/202_repository.jsPartnerRepository.findAll() / findAsMap(): 手動入力フォームのプルダウン
  • 200_data/202_repository.jsInvoiceRepository.findAll() / save() / append(): 手動入力後の INV 登録
  • 400_domain/413_invoice_matcher.js(MAS-147 v2 で新規追加予定): 手動入力後の候補マッチング合流先
  • 000_infra/004_utils.jsUtils.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) が以下を実行
    1. バリデーション(必須項目・形式・三者整合)
    2. 証憑種別が「請求書」→ Step 4-D HitL サイドバーの matchInvoice に合流(MAS-147 v2 InvoiceMatcher.match() 呼び出し)
    3. 証憑種別が「請求書」以外 → Step 3 の退避フォルダ処理
    4. 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 分岐:

  1. HitL サイドバー合流: 既存 INV との候補マッチがあれば MAS-147 v2 HitL サイドバーへ(matchInvoice → 候補提示 → 承認)
  2. 直接新規 INV 作成: マッチ候補 0 件の場合、InvoiceRepository.append(newDto) で 32_wrk_invoice に新規登録(請求ステータス='未処理' で承認待ち)。取引先マスタに未登録なら MAS-169 インライン自動登録で 12_mst_partner に追加提案
  3. 対象外として別フォルダ保管: 証憑種別「その他」で登録した場合、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.jsFOLDER_KEYS.NOT_INVOICE_FOLDER_ID 追加、MENU_DEFINITION に新セクション追加~10 行
docs/_config.jsonnav 追加~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 該当部)

  1. #18-#20(命名造語禁止): 新規関数名・定数名は 002_constants.js / 003_contracts.js / 004_utils.js / 202_repository.js を Read した上で既存命名規則に合わせる。特に FOLDER_KEYS の他キー名(EVIDENCE_FOLDER_ID 等)の命名パターンを踏襲
  2. #21(getLastColumn 禁止): Drive フォルダ列挙は FolderIterator.hasNext() 必須
  3. #25(並列実装対称性): 「請求書」「領収書」「見積書」「契約書」「その他」の 5 種別を扱う分岐では、書き込み系の値を全分岐で明示的に set する(暗黙のデフォルトに頼らない)
  4. #26(oauthScopes 部分宣言禁止): 本案件で新規 OAuth スコープは不要のため appsscript.jsonoauthScopes には触らない。もし Drive 操作で権限不足が出た場合、既存スコープの自動検出に任せる
  5. #27(API 仕様変動): Gemini API レスポンスは JSON Schema + post-processing 検証方式。response.candidates[0].content.parts[0].text 等の深いパスは try/catch で防御
  6. Drive description への JSON 格納は JSON.stringify(reason).substring(0, 25000) で Drive 制限(description 最大 30KB)対策
  7. 手動入力フォームは GASdoGet(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: 入力後のフロー分岐

影響範囲

注意事項

エッジケース

#条件検知方法期待される挙動ログ出力
E0199_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')
E0299_error/ 配下に PDF が 0 件FolderIterator.hasNext() が初回 falseサイドバーに「失敗 PDF は 0 件です」を表示
E03Drive description が JSON 不正(破損・旧形式)JSON.parse() が throwerrorReason: '(解析不能)' として一覧に表示(UI 側で赤字警告)persistLog('warn', 'description parse failed', {fileId})
E04Gemini API が 429 (Rate Limit) / 503 (Unavailable)UrlFetchApp.fetch の応答ステータスを検査UI に「Gemini 一時エラー。数分後に再試行してください」を表示。99_error/ から移動しないpersistLog('error', 'gemini 5xx/429', {fileId, status})
E05Gemini レスポンスが JSON パース失敗(マークダウン混入等)JSON.parse(responseText) が throw手動入力フォームに自動遷移(Gemini の生テキストを摘要欄の初期値として提示)persistLog('warn', 'gemini JSON parse failed', {fileId})
E06Gemini は成功したが必須フィールド(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})
E09T 番号が /^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 スコープ外)
E13NOT_INVOICE_FOLDER_ID スクリプトプロパティ未設定Utils.getFolderByKey が null 返却Step 3 退避処理で throw CFG: NOT_INVOICE_FOLDER_ID 未設定。UI に「管理者に連絡してください」表示persistLog('error', 'NOT_INVOICE_FOLDER_ID not set')
E14Gemini 抽出結果の日付が未来日付(誤認識)サーバー側で 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 経由で確認する:

  1. 99_error/ フォルダの実在と格納 PDF 数 — MAS-147 v2 実装後の運用状況を把握。PDF が 0 件ならテスト用に DocAI が失敗する PDF(手書き・画像化)を 2-3 件投入
  2. EVIDENCE_FOLDER_ID / NOT_INVOICE_FOLDER_ID スクリプトプロパティ — dev / prod 両方で設定されているか確認
  3. PartnerRepository.findAll() の取引先件数と表記ゆれ — プルダウン候補の規模感把握(件数が 500+ なら検索式プルダウンに切替検討)
  4. Drive description の JSON 格納実例 — MAS-147 v2 handleError_ が格納する JSON スキーマを確認し、errorReason パース仕様を確定
  5. Gemini API レスポンス実例 — MAS-216 完了後、callGeminiForReceiptOnVertex_ のレスポンス構造(candidates[0].content.parts[0].text)を実リクエストで確認

関連ドキュメント

関連仕様書関連箇所
dev_mas-147_invoice_ocr_auto_posting.mdv2 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.mdVertex AI 移行(Gemini 直接投入基盤)
dev_mas-152_evidence_filename_rename.md退避先フォルダのリネーム運用
ADR-0007 Gemini Receipt ParsingGemini 直接投入の設計方針

人間が検討すべき事項

  1. Gemini 直接投入の料金影響 — MAS-216 並行稼働中は小規模だが、月 100 枚の失敗 PDF × 再試行 2 回 = 月 200 Gemini コール想定。gemini-2.5-flash の単価で試算し、月 1000 円超なら料金アラート設定
  2. 「対象外」判定の自動化閾値 — 現状は人間が手動分類するが、将来 Gemini に「この PDF は請求書ですか?」と 1 問問い合わせて自動分類する経路も検討可(精度と料金次第)
  3. 手動入力の負荷軽減策:
    • 過去入力履歴の類推(同じ取引先名 + 金額レンジなら自動補完)
    • OCR 生テキスト(DocAI が部分的に返したもの)を摘要欄に初期値表示
    • ショートカットキー対応(Cmd+Enter で送信)
  4. MAS-157(写真レシート OCR)との統合余地 — 画像化 PDF は実質写真レシートと同等なので、Step 4 の Gemini 再試行で「領収書プロンプト」に切替える経路を入れるか
  5. 「見積書」のための独立案件起票タイミング — 見積書が月 10 件以上になったら I-XX(見積書管理)を新規起票。本案件では 06_not_invoice/見積書/ への退避のみ
  6. サイドバー配置 — 「🚨 OCR 失敗 PDF」セクションを既存「📥 経理業務」の下に置くか、独立セクションにするか(MAS-217 のサイドバー統合方針との整合)
  7. UI: サイドバー vs モーダルダイアログ vs 別ウィンドウ — 手動入力フォームを 3 選択肢のどれにするか(現状案: モーダルダイアログで PDF プレビュー + フォームを横並び)
  8. Gemini の信頼度スコア — Gemini 自身の confidence 自己申告は精度が低いとされるため、本案件では固定値 0.6 を付与。実運用で Gemini 結果の正答率を計測し、後日閾値調整
  9. prod 環境での初期展開 — 運用開始直後は dev で 20 件程度の失敗 PDF に対して手動入力 + Gemini 再試行の混合テストを実施してから prod push
  10. 監査ログ — 「削除」アクションは 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.65 公開関数の設計判断(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 信頼度が低い国際フォーマット請求書)