MAS-147: 請求書 PDF → INV 自動起票 (v2: Document AI + Gemini ハイブリッド)
概要
| 項目 | 値 |
|---|---|
| 案件ID | MAS-147 |
| カテゴリ | 自動入力パイプライン |
| 優先度 | P1 ★★★ |
| ステータス | 仕様書完了 (v2) |
| バージョン | v2 (Document AI + Gemini ハイブリッド) |
| 前提案件 | MAS-215 (GCP プロジェクト整備・Document AI 有効化) / MAS-152 (電帳法リネーム、フォルダ運用統合先) |
| 関連案件 | MAS-157 (写真 OCR)、MAS-150 (証憑→20番台マスタ)、MAS-171 (CC N:1 合算マッチ・Subset Sum 共通ヘルパー) |
| 関連 ADR | ADR-0007 Gemini API を領収書解析に使用 |
| 対象ファイル (新規・主) | 500_import/504_invoice_importer.js / 400_domain/413_invoice_matcher.js / templates/invoice_hitl_sidebar.html |
| 対象ファイル (改修) | 000_infra/001_env.js (DocAI プロセッサ ID 参照) / 000_infra/004_utils.js (Jaccard・Subset Sum) / appsscript.json (スコープ追加) |
| v2 改訂日 | 2026-04-21 |
| v1 資産 | feat/I-03-invoice-ocr-auto-posting ブランチに凍結 (基本起票 / 多ページ重複排除 / buildInvDtoFromOcr_ / 日付別 ID 採番)。v2 実装着手時に必要部分のみ cherry-pick 再利用 |
目的
顧客から受領した請求書 PDF を OCR + AI でパースし、既存の予算マスタ (20 番台タブ) と突合して 32 タブ WRK_INVC に自動起票する。責務分離の方針:
- Document AI (Fact 抽出): T 番号・金額・日付・明細行座標など、税法上 1 円のブレも許されない確定値を機械学習確率モデル (0.0-1.0) で抽出
- Gemini (Reasoning 推論): 真のベンダー推定 (Stripe 等の決済代行排除)・請求タイプ判定 (SUBSCRIPTION / USAGE_BASED / ONE_OFF)・請求構造判定 (SINGLE / BUNDLE / MULTI) などの意味的推論のみ
→ 誤マッチによるデータ汚染リスクをゼロに抑えつつ、Auto-Link 率 85% 超を実現。HitL 工数を月 60 分 → 月 7.5 分に削減 (月間 100 通想定)。
v1 (Gemini 単独 OCR) は「30 枚未満/月」の小規模運用では有効だが、bizlp の成長期には v2 ハイブリッドが妥当と MAS-306 で結論付けた。
現状のコード
既存 OCR 基盤 (MAS-157 で実装済、流用可能)
500_import/502_receipt_reader.js:callGeminiForReceipt_()で領収書 PDF/画像を Gemini で OCR 抽出GEMINI_API_KEYをスクリプトプロパティ経由で参照generativelanguage.googleapis.comエンドポイント (MAS-216 で Vertex AI 移行予定)
既存の WRK_INVC (32 タブ) 構造
- DDL 管理、
100_config/101_sys_config.jsのschemas.WRK_INVCで定義 - T 番号カラム未搭載 → v2 で追加必須
- 既存列: 請求ID / 取引先名 / 発生日 / 税込金額_計画 / 税抜金額_計画 / 消費税額_計画 / 請求ステータス / 等
既存の 12_mst_partner
- T 番号カラム未搭載 → v2 で追加必須
- MAS-120 (取引先マスタ拡張) で決済条件列は追加済。T 番号はインボイス対応で今回追加
v1 凍結資産 (feat/MAS-147-invoice-ocr-auto-posting ブランチ)
- 基本起票関数の骨格
- 多ページ PDF の重複排除ロジック
buildInvDtoFromOcr_()ヘルパー- 日付別 ID 採番 (既存パターン)
→ v2 でも再利用可能な部分は cherry-pick して取り込む。
修正方針
v1 からの継承 / 変更 / 廃止
| 項目 | ステータス | 理由 |
|---|---|---|
| T 番号カラム追加・HitL サイドバー | 🟢 継承 | 誤マッチ 0% を保証する絶対キー |
| 多段 Pass / 1 円差許容 | 🟢 継承 | Pass 0〜3, 5 のロジックと消費税誤差吸収は不変 |
| OCR 抽出エンジン | 🟡 変更 | Gemini 単独 → DocAI (構造抽出) + Gemini (意味推論) ハイブリッド |
| 非同期キュー バッチサイズ | 🟡 変更 | 3〜5 件 → 1〜2 件 (DocAI 処理時間増のため) |
| 確信度スコア | 🟡 変更 | LLM 主観スコア → DocAI の確率モデル (0.0-1.0) ベースの複合計算式 |
| 1:N (明細分割) マッチ (Pass 4) | 🟡 変更 | ハルシネーション懸念で「見送り」 → DocAI 明細信頼性を盾に「実装」 |
| ファイル保存・リネーム運用 | 🔵 新規 | MAS-152 統合で月次サブフォルダ + 電帳法リネームをパイプライン化 |
| Gemini JSON Schema 抽出 | 🔴 廃止 | Gemini に金額/日付抽出させるプロンプト全廃、推論専用の軽量プロンプトへ |
Step 1: DocAI インフラ + OAuth 権限設定 (W1)
1-A: GCP 側の Document AI 有効化
前提: MAS-215 (GCP プロジェクト整備) 完了。dev/prod 両プロジェクトで以下を実施。
- Google Cloud Console → 対象プロジェクトを選択
- 「API とサービス」→「ライブラリ」→「Document AI API」を有効化
- Document AI → プロセッサ作成:
- タイプ: Invoice Parser (
INVOICE_PROCESSOR) - リージョン:
asia-northeast1優先 (未提供時はus) - プロセッサ ID を控える
- タイプ: Invoice Parser (
- スクリプトプロパティ
DOCAI_PROCESSOR_ID_DEV/DOCAI_PROCESSOR_ID_PRODを両環境の GAS エディタに登録
1-B: 000_infra/001_env.js への Env メソッド追加
Env.vertexRegion() に倣って docaiProcessorId() / docaiLocation() を追加:
/** @returns {string} DocAI プロセッサ ID (環境別) */
docaiProcessorId: function () {
var key = _load().isDev ? 'DOCAI_PROCESSOR_ID_DEV' : 'DOCAI_PROCESSOR_ID_PROD';
return _getProps().getProperty(key) || '';
},
/** @returns {string} DocAI プロセッサのリージョン (未設定は asia-northeast1) */
docaiLocation: function () {
return _getProps().getProperty('DOCAI_LOCATION') || 'asia-northeast1';
},
1-C: appsscript.json の OAuth スコープ追加 (失敗パターン #26 対応)
Document AI は https://www.googleapis.com/auth/cloud-platform が必須。既存の全スコープを完全列挙した上で追加する。
⚠️ 絶対禁止: 部分宣言 (新スコープだけ追加) は自動検出を OFF にし、既存の SpreadsheetApp / DriveApp / MailApp / Utils.auditLog を全て破壊する (failure_patterns #26)。
正しい手順:
- GAS エディタ → プロジェクト設定 → OAuth スコープ の現在値をすべてコピー
appsscript.jsonのoauthScopes配列に元の全スコープを明示列挙- 末尾に
"https://www.googleapis.com/auth/cloud-platform"を追加
1-D: 500_import/504_invoice_importer.js の通信テスト関数
軽い確認用関数 testDocaiConnection() を追加:
- 簡易 1 ページ PDF を DocAI に送信 →
entities.length > 0を検証 fieldMask=entities,textで必要項目のみ取得 (50MB 制限回避)
Step 2: 物理ファイル ルーティング基盤 (W1)
MAS-152 (電帳法リネーム) と統合し、処理済み PDF を月次サブフォルダに自動配置 + 電帳法準拠リネーム。
2-A: フォルダ階層設計
[受信箱ルート]/
01_inbox/ ← 未処理 PDF の投入先
02_processed/ ← 電帳法対応の保管庫
請求書/
2026-04/ ← 月次サブフォルダ (自動生成)
20260421_..._税込_docNumber.pdf
2026-05/
03_hold_hitl/ ← HitL 保留 (未承認)
99_error/ ← 抽出失敗 / 通貨違い等
月次サブフォルダ必須の理由: フラットな大フォルダは Drive API タイムアウトの原因になる。
2-B: リネーム規則
YYYYMMDD_取引先名_税込金額_docNumber.pdf
- 電帳法検索要件を担保 (日付・相手方・金額の 3 要素)
- 同名衝突時は
_(1)_(2)を付与 - 抽出失敗時は
_UNKNOWN_{元ファイル名}.pdf(HitL 補正後に再リネーム)
2-C: 権限管理 (MAS-206 / MAS-201 連携)
02_processed/はシステム以外の編集・削除不可 (Drive 共有権限で「閲覧者」に絞る)- 電帳法 10 年保管ポリシー適用 (MAS-201 バックアップ対象)
2-D: ヘルパー関数
500_import/504_invoice_importer.js に追加:
routeInvoicePdf_(fileId, meta)— メタ情報を元に02_processed/請求書/YYYY-MM/に移動 + リネームresolveMonthlyFolder_(yyyymm)— 月次サブフォルダを getOrCreate
Step 3: ハイブリッド抽出パイプライン (W2)
DocAI パース → Gemini 推論 → 統合 JSON の流れを実装。
3-A: Document AI 標準エンティティ → WRK_INVC 対応表
| DocAI エンティティ | 取得方針 | WRK_INVC / 活用先 |
|---|---|---|
supplier_name | 🤖 DocAI | 取引先名_OCR |
supplier_tax_id | 🤖 DocAI (T 番号形式検証) | T 番号 (新規列) |
supplier_address 等 | 🚫 破棄 (fieldMask で除外) | - |
supplier_website | 🤖 DocAI | Gemini 推論コンテキストへ |
supplier_bank_account | 🤖 DocAI | 振込先 (HitL 表示) |
supplier_payment_terms | 🤖 DocAI | 決済日_計画 補強 |
receiver_name | 🤖 DocAI (宛名検証) | 自社宛チェックフラグ |
invoice_id | 🤖 DocAI | 請求書番号 |
invoice_date | 🤖 DocAI (和暦→西暦) | 発生日 |
due_date | 🤖 DocAI (欠損時 partner_terms 補完) | 決済日_計画 |
currency | 🤖 DocAI (JPY 検証) | 通貨エラーフラグ |
total_amount | 🤖 DocAI (最重要、confidence アンカー) | 税込金額_計画 |
net_amount | 🤖 DocAI | 税抜金額_計画 |
total_tax_amount | 🤖 DocAI | 消費税額_計画 |
line_items[] | 🤖 DocAI | Pass 4 (1:N 分割) のソース |
line_items/description | 🤖 DocAI | 摘要ファジーマッチキー |
line_items/amount | 🤖 DocAI | 分割起票時の金額 |
tax_details[] | 🤖 DocAI | 税率混在検知 |
trueVendorName | 🧠 Gemini 推論 | 真の取引先名 (Stripe 排除) |
billingType | 🧠 Gemini 推論 | 請求タイプ (SUBSCRIPTION/USAGE_BASED/ONE_OFF) |
invoiceStructure | 🧠 Gemini 推論 | 請求構造 (SINGLE_SVC/BUNDLE_SVC/MULTI_INDEP) |
mainServiceSummary | 🧠 Gemini 推論 | 摘要 (15 文字以内) |
3-B: 500_import/504_invoice_importer.js の主要関数
callDocAiForInvoice_(pdfBlob)— DocAI Invoice Parser 呼び出し (fieldMask=entities,text)callGeminiForInvoiceReasoning_(docAiResult)— Gemini に意味推論のみ依頼 (軽量プロンプト)mergeDocAiAndGemini_(docAiResult, geminiResult)— 統合 JSON 生成
3-C: Gemini プロンプト設計 (推論専用)
あなたは会計士アシスタントです。以下の Document AI 抽出結果から
「真の提供者」「請求タイプ」「請求構造」「摘要 (15 文字)」のみを推論してください。
金額・日付・T 番号の抽出は禁止 (Document AI の確定値を上書きしてはいけません)。
入力:
{docAiJson}
出力 (JSON):
{
"trueVendorName": "...",
"billingType": "SUBSCRIPTION" | "USAGE_BASED" | "ONE_OFF",
"invoiceStructure": "SINGLE_SVC" | "BUNDLE_SVC" | "MULTI_INDEP",
"mainServiceSummary": "...",
"confidenceScore": 0.0-1.0
}
3-D: 確信度スコア統合
const amtConf = docAi.total_amount.confidence;
const vndConf = docAi.supplier_tax_id
? docAi.supplier_tax_id.confidence
: docAi.supplier_name.confidence;
const baseFactConf = Math.min(amtConf, vndConf); // 最弱リンク
const geminiConf = geminiOutput.confidenceScore;
const finalScore = (baseFactConf * 0.8) + (geminiConf * 0.2);
閾値:
- HIGH (≥ 0.85): Auto-Link (バックグラウンド自動紐付)
- MEDIUM (0.60〜0.84): HitL (サイドバーで 1 クリック承認)
- LOW (< 0.60): OCR エラー警告、手動補正
即死ルール (即 LOW に落とす):
total_amount.confidence < 0.80currency != JPY
Step 4: マッチングエンジン + HitL サイドバー実装 (W3-W4)
4-A: 多段 Pass マッチング (Pass 0〜6)
400_domain/413_invoice_matcher.js (新規) に実装:
| Pass | 条件 | 確度 | 処理 |
|---|---|---|---|
| Pass 0 | T 番号完全一致 + 金額完全一致 (許容 ±0 円) | 最高 | Auto-Link |
| Pass 1 | T 番号完全一致 + 金額 ±1 円 (消費税端数吸収) | 高 | Auto-Link if HIGH |
| Pass 2 | 取引先名完全一致 + 金額完全一致 | 高 | Auto-Link if HIGH |
| Pass 3 | 取引先名完全一致 + 金額 N:1 合算マッチ (Subset Sum) | 中 | HitL 候補提示 |
| Pass 4 | 1:N 分割マッチ (line_items.description × 予算 品目名 Jaccard ≥ 0.7) | 中 | HitL 候補提示 |
| Pass 5 | 取引先ファジーマッチ (004_utils.js の正規化 + Levenshtein) | 低 | HitL 候補提示 |
| Pass 6 | 新規予算登録推奨 (MAS-150 連携) | 最低 | 20 番台マスタ追加提案 |
Pass 3 Subset Sum: MAS-171 CC N:1 合算マッチ の共通ヘルパーを 000_infra/004_utils.js に統合 (重複実装を避ける)。
4-B: DDL 拡張 (T 番号カラム追加)
100_config/101_sys_config.js で以下を変更:
schemas.MST_PARTNER.headers末尾に"T番号"を追加schemas.WRK_INVC.headersの適切位置に"T番号"を追加setupAllSchemas()実行で既存シートに列追加 (既存データの T 番号列は空欄、別途マイグレーション or 手動投入)
既存取引先のマイグレーション: I-28 法人番号自動取得 (国税庁 Web-API) と連携、本案件では DDL 列追加までが範囲。
4-C: 32_wrk_invoice への書込み
- 確信度 HIGH → 新規 INV レコード作成、
請求ステータス=処理中、自動仕訳JNL_ID=null(Action A 待ち) - 確信度 MEDIUM →
確認FLG=FALSE(HitL 保留扱い)、サイドバーで承認
4-D: HitL サイドバー (templates/invoice_hitl_sidebar.html)
UI 要素:
- PDF プレビュー (左ペイン):
DriveApp.getFileById(id).getBlob()の iframe 埋込 - 抽出結果パネル (右上): Document AI の各フィールド + 確信度バー
- マッチング候補 (右中): Pass 2〜5 の候補を優先度順で 3 件提示
- アクションボタン (右下):
- ✅ 承認 (候補の 1 つを選択)
- ✏️ オーバーライド (値を手動修正)
- 🗑️ スキップ (
99_error/に移動) - ➕ 新規予算登録 (MAS-150 連携ダイアログ)
承認フロー:
02_processed/請求書/YYYY-MM/に移動 + 電帳法リネーム (Step 2)32_wrk_invoiceに書込み +確認FLG=TRUEUtils.auditLog('APPROVE', '32_wrk_invoice', invId, ...)記録- Action A 待ち状態へ (既存ワークフローに合流)
4-E: 非同期キューイング (処理順序制御)
03_hold_hitl/にある PDF を 1 バッチ 1〜2 件ずつ処理 (DocAI + Gemini で 10〜15 秒/件)- GAS 時間トリガー (5 分間隔) で
runInvoiceBatch_()実行 - 6 分実行時間制限を超えないよう 1 件毎に
LockServiceで排他制御
影響範囲
| 対象 | ファイル | 変更量 | 既存動作への影響 |
|---|---|---|---|
| 新規 インポーター | 500_import/504_invoice_importer.js | 約 400 行新規 | なし (新規ファイル) |
| 新規 マッチャー | 400_domain/413_invoice_matcher.js | 約 300 行新規 | なし (新規ファイル) |
| 新規 HitL UI | templates/invoice_hitl_sidebar.html | 約 200 行新規 | なし (新規ファイル) |
| Env モジュール拡張 | 000_infra/001_env.js | 約 10 行追加 | 既存 Env.xxx メソッドは変更なし |
| Utils 拡張 | 000_infra/004_utils.js | Jaccard / Subset Sum 追加 約 80 行 | MAS-171 と共通化、既存関数は変更なし |
| DDL 変更 | 100_config/101_sys_config.js (schemas.MST_PARTNER / WRK_INVC) | 各 1 列追加 | setupAllSchemas() 再実行で既存シートに列追加 |
| appsscript.json | OAuth スコープに cloud-platform 追加 | 1 行追加 | 既存全スコープの完全列挙が必須 (failure_patterns #26) |
| MENU_DEFINITION | Constants.MENU_DEFINITION に「📥 請求書 PDF 取込」追加 | 1 項目 | 既存メニュー変更なし |
| 新 OAuth スコープ | cloud-platform | 有効化 | ユーザー再認証が必要 |
注意事項
- OAuth スコープ崩壊を絶対に避ける (failure_patterns #26):
appsscript.jsonのoauthScopesを変更する際は、既存の全スコープを完全列挙してから新スコープを追加する。部分追加は全機能崩壊を招く - Document AI の 50MB レスポンス制限:
fieldMask=entities,textでレスポンスを必要最小限に絞る - DocAI 処理時間 10〜15 秒/件: バッチサイズを 1〜2 件に抑え、6 分実行時間制限内に収める
- Gemini に金額・日付・T 番号を抽出させない: 推論専用プロンプトでこれらを禁止 (DocAI の確定値を上書きしてはいけない)
- 即死ルール:
total_amount.confidence < 0.80またはcurrency != JPYは強制 LOW に落とす - T 番号カラムの DDL 追加: setupAllSchemas 実行で既存シートに列追加。既存行の T 番号データは別途 MAS-170 (法人番号 API 連携) で投入
- 月次サブフォルダの自動生成: フラットフォルダは API タイムアウトの原因。必ず
02_processed/請求書/YYYY-MM/で分離 - Gemini の Vertex AI 移行: MAS-216 完了後は Gemini 推論も Vertex AI 経由に移行。現時点は
generativelanguage.googleapis.comで並行稼働可
エッジケース
| 条件 | 挙動 | 対策 |
|---|---|---|
| 多ページ PDF (3 ページ以上) | DocAI タイムアウトリスク | 最初の 2 ページのみを DocAI に送信 (総額・サマリは先頭に集中) |
| 外貨 (USD/EUR 等) | 誤読リスク | currency != JPY で即 LOW に落とし HitL で警告 |
| 手書き領収書 | DocAI 精度低下 | docType 判定で確信度低ければ Gemini 直接入力 (v1 フォールバック) |
| T 番号欠損 | 信頼性低下 | supplier_name の confidence で代替、MEDIUM ゾーンへ |
| 複数請求書 1 PDF | DocAI が正しく分割しないリスク | 1 ファイル 1 請求書の運用を徹底、HitL で手動分割 |
SUM(line_items) != total_amount | 割引・送料の分離等 | 1:N 探索を放棄し BUNDLE_SVC として HitL 手動分割 |
| 軽減税率混在 | 8% / 10% 混在時の税額計算ブレ | tax_details で複数税率検知、HitL で警告 |
| 同名ファイル衝突 | 電帳法リネーム時に衝突 | _(1) _(2) サフィックス付与 |
| DocAI プロセッサ ID 未設定 | docaiProcessorId() が空文字 | graceful degradation で 99_error/ 直行 + ログ |
| Drive フォルダ存在しない | 保存先欠落 | getOrCreate で動的生成 |
| 非同期キューの多重起動 | 同一 PDF の二重処理 | LockService で排他制御 |
| Gemini JSON パース失敗 | 推論フィールド欠損 | invoiceStructure を SINGLE_SVC に fallback、HitL 候補提示のみ |
| 確信度 HIGH でも新規取引先 | T 番号一致なし | Pass 6 で新規予算登録推奨ダイアログ (MAS-150 連携) |
実データ検証
実装前に以下を確認:
- Document AI プロセッサ作成: dev プロジェクトで Invoice Parser を作成、プロセッサ ID 取得
- 既存請求書 PDF サンプル (10 件程度) で DocAI 抽出精度検証
- T 番号付き請求書 と T 番号なし請求書 の両方で確信度スコアが期待値通りか確認
- 外貨請求書 で即死ルールが発動するか (強制 LOW) 検証
- 多ページ請求書 (2〜5 ページ) でタイムアウト挙動を確認
- line_items の抽出精度 (物理座標ベースでハルシネーションなし) を確認
12_mst_partner.T番号カラムの命名規則確認 (既存 MAS-120 の列と整合)Env.docaiProcessorId()の戻り値が正しい (未設定時は空文字)
関連ドキュメント
| ドキュメント | 関連箇所 |
|---|---|
| ADR-0007 Gemini API を領収書解析に使用する判断 | MAS-147 の OCR 戦略の前提 |
| ADR-0008 本番 AI API を Vertex AI に集約 | MAS-216 完了後の Gemini 呼び出しを Vertex 経由に切替 |
| MAS-215 GCP プロジェクト整備 | Document AI 有効化の前提 |
| MAS-152 電帳法リネーム | Step 2 フォルダ運用の統合先 |
| MAS-157 写真 OCR | OCR 基盤の流用元 |
| MAS-171 CC 明細 N:1 合算マッチ | Pass 3 Subset Sum の共通ヘルパー |
| MAS-150 証憑取込→20番台マスタ | Pass 6 新規予算登録の連携先 |
| MAS-306 設計検討 Gemini Deep Think 結果 | v2 の根拠となる設計結果 |
| failure_patterns #26 OAuth スコープ崩壊 | Step 1-C の必須対応 |
人間が検討すべき事項
- DocAI プロセッサのリージョン:
asia-northeast1優先だが、Invoice Parser が未提供の場合はusリージョン + データ residency の考慮 - Gemini → Vertex AI 移行のタイミング: MAS-216 完了前に MAS-147 実装を始める場合、先に
generativelanguage.googleapis.comで開発 → MAS-216 完了時に{region}-aiplatform.googleapis.comにエンドポイント切替 - T 番号未登録の取引先の扱い: 自動起票時に HitL で警告するか、Pass 6 で新規予算登録を推奨するか
- 1:N 分割マッチの閾値: Jaccard 0.7 は仮置き。実データで検証してチューニング
- 非同期キューのバッチサイズ: 1〜2 件が推奨だが、将来 DocAI の処理時間が改善されれば 3〜5 件に戻すか
- Auto-Link の閾値 (HIGH = 0.85): 保守的な数値。誤マッチ 0 件を優先するなら 0.90 に引き上げも検討
- 新規予算登録の自動化 (MAS-150 連携): Pass 6 で新規取引先を検知した際、20 番台マスタへの自動追加を Human-in-the-Loop 承認付きでやるか、運用者手動に委ねるか
- 電帳法対応範囲: MAS-152 と統合するが、請求書以外 (領収書・契約書等) の保管フォルダも同じ階層ポリシーにするか
- HitL サイドバーのデスクトップ幅: PDF プレビュー + 候補提示で横幅を広く取る必要あり (Google Sheets の標準サイドバー 300px では不足の可能性)
- 失敗時のリトライ戦略: DocAI 5xx エラー時の指数バックオフ、Gemini タイムアウト時の Gemini 単独 fallback
実装プロンプト (Claude Code 用)
あなたは GAS 会計システム (bizlp-gas-accounting) のシニア開発者です。
案件 MAS-147「請求書 PDF → INV 自動起票 (v2 Document AI ハイブリッド)」を実装してください。
v2 設計の根拠は MAS-306 結果 (docs/_internal/research_prompts/RQ-008_I-03_matching_design_result.md) 参照。
## 実行前タスク
1. docs/dev/dev_mas-147_invoice_ocr_auto_posting.md を Read して v2 仕様を全把握
2. docs/_internal/failure_patterns.md #26 (OAuth スコープ崩壊) を必ず確認
3. 既存 OCR 基盤 `500_import/502_receipt_reader.js` を Read して OCR パターンを把握
4. `100_config/101_sys_config.js` の schemas.MST_PARTNER / WRK_INVC の現状列構造を Read
5. `000_infra/001_env.js` の Env オブジェクトの拡張パターンを把握
6. `docs/dev/dev_mas-171_cc_combo_matching.md` の Subset Sum ロジックを確認 (共通化対象)
7. dev GCP プロジェクトで Document AI プロセッサを手動作成し、プロセッサ ID を取得済であることを確認
8. スクリプトプロパティ `DOCAI_PROCESSOR_ID_DEV` / `DOCAI_LOCATION` が dev GAS に登録済であることを確認
9. v1 凍結ブランチ feat/MAS-147-invoice-ocr-auto-posting から再利用可能な部分 (多ページ重複排除 / buildInvDtoFromOcr_) を cherry-pick 候補として特定
## 修正対象ファイル
- 新規: 500_import/504_invoice_importer.js (約 400 行)
- 新規: 400_domain/413_invoice_matcher.js (約 300 行)
- 新規: templates/invoice_hitl_sidebar.html (約 200 行)
- 改修: 000_infra/001_env.js (約 10 行追加、docaiProcessorId() / docaiLocation())
- 改修: 000_infra/004_utils.js (Jaccard + Subset Sum 追加)
- 改修: 100_config/101_sys_config.js (schemas.MST_PARTNER / WRK_INVC に T 番号列追加)
- 改修: appsscript.json (OAuth cloud-platform スコープ追加、既存全スコープ完全列挙)
- 改修: 000_infra/002_constants.js (MENU_DEFINITION に「📥 請求書 PDF 取込」追加)
## 実装順序 (Step 1-4)
### Step 1: DocAI インフラ + OAuth 権限設定
- Env モジュールに docaiProcessorId / docaiLocation 追加
- appsscript.json の OAuth スコープ更新 (既存全スコープ完全列挙 + cloud-platform)
- testDocaiConnection() でサンプル PDF 通信テスト
### Step 2: 物理ファイル ルーティング基盤
- routeInvoicePdf_() / resolveMonthlyFolder_() 実装
- フォルダ階層 01_inbox → 02_processed/請求書/YYYY-MM/ → 03_hold_hitl → 99_error
- 電帳法リネーム YYYYMMDD_取引先名_税込金額_docNumber.pdf
### Step 3: ハイブリッド抽出パイプライン
- callDocAiForInvoice_() で DocAI Invoice Parser 呼び出し (fieldMask=entities,text)
- callGeminiForInvoiceReasoning_() で意味推論のみ取得 (金額/日付抽出は禁止)
- mergeDocAiAndGemini_() で統合 JSON 生成
- 複合確信度スコア計算 (baseFactConf × 0.8 + geminiConf × 0.2)
- 即死ルール (total_amount.confidence < 0.80 or currency != JPY) で強制 LOW
### Step 4: マッチングエンジン + HitL サイドバー
- 413_invoice_matcher.js に Pass 0〜6 実装
- schemas.MST_PARTNER / WRK_INVC に T 番号列追加 + setupAllSchemas 動作確認
- invoice_hitl_sidebar.html で PDF プレビュー + 候補提示 + 承認/オーバーライド/スキップ
- runInvoiceBatch_() で 1〜2 件バッチ処理、LockService で排他、時間トリガー 5 分間隔
## 制約
- appsscript.json の OAuth スコープを変更する際は failure_patterns #26 の手順を必ず守る (既存全スコープを完全列挙してから cloud-platform を追加)
- Gemini に金額/日付/T 番号の抽出を依頼しない (DocAI の確定値を上書きしない)
- fieldMask=entities,text を必ず付けて 50MB レスポンス制限を回避
- MAS-171 の Subset Sum と共通化するため、Pass 3 のロジックは 004_utils.js に切り出す
- 確信度の即死ルールを厳密に守る
- 既存コード (502_receipt_reader.js / BudgetRepository / PartnerRepository 等) のパターンを踏襲
## エッジケース
- 多ページ PDF (3 ページ以上) → 最初の 2 ページのみ DocAI
- currency != JPY → 強制 LOW、HitL 警告
- 手書き領収書 → Gemini 直接入力にフォールバック
- T 番号欠損 → supplier_name confidence で代替
- DocAI プロセッサ ID 未設定 → 99_error/ 直行 + ログ
## 動作確認
1. npm run push:dev でデプロイ
2. testDocaiConnection() で DocAI 通信 OK 確認
3. setupAllSchemas() で T 番号列追加を確認
4. サンプル請求書 PDF を 01_inbox/ に投入
5. runInvoiceBatch_() 手動実行 → Pass 0/1 の Auto-Link が動作するか
6. T 番号欠損 PDF で HitL サイドバー表示確認
7. 外貨 PDF で 99_error/ 直行確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---|---|---|
| 実行前タスク (Read) | あり | v2 仕様の全貌把握 + OAuth 崩壊回避策 |
| Step 1 DocAI 基盤 | あり | OAuth スコープ列挙の完全性確保 |
| Step 2 ルーティング | なし | 仕様書通りの実装 |
| Step 3 抽出パイプライン | あり | Gemini プロンプト設計で精度調整 |
| Step 4 マッチング + HitL | あり | Pass 順序 + 確信度閾値の動作検証 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1 (DocAI インフラ + OAuth) | Claude Opus 4.7 | failure_patterns #26 対応で OAuth 全スコープ列挙の判断が必要 |
| Step 2 (ルーティング基盤) | Claude Sonnet 4.6 | 既存パターン拡張、中程度の判断 |
| Step 3 (ハイブリッド抽出) | Claude Opus 4.7 | DocAI + Gemini 統合ロジック、複合確信度計算、プロンプト設計 |
| Step 4 (マッチングエンジン + HitL) | Claude Opus 4.7 | 多段 Pass 設計、MAS-171 共通化、HitL UX 設計 |
| 動作確認 | Claude Haiku 4.5 | 手順実行のみ |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-17 | v1 初版作成 (Gemini 単独 OCR 前提、4 Step 実装) |
| 2026-04-21 | v2 全面改訂: Gemini 単独 → Document AI + Gemini ハイブリッドに全面書き直し。責務分離 (Fact = DocAI / Reasoning = Gemini)、T 番号カラム必須化、多段 Pass 0〜6、複合確信度スコア (baseFactConf × 0.8 + geminiConf × 0.2) と即死ルール、1:N 分割マッチ実装格上げ、フォルダ分離・電帳法リネーム運用 (MAS-152 統合)、非同期キュー 1〜2 件化、採否判断マトリクス (30 枚/200 枚/1000 枚)。前提案件に MAS-215 / MAS-152 を追加。MAS-171 の Subset Sum 共通化方針を明記。v1 資産 (feat/MAS-147-invoice-ocr-auto-posting ブランチ) は凍結、v2 で必要部分のみ cherry-pick 再利用。MAS-306 Gemini Deep Think 結果が根拠 |