概要

項目内容
案件IDMAS-338
カテゴリデータ取込・銀行突合
PhaseP1
優先度★★★
所要時間4-6 時間 (Step A-F 合計)
対象ファイル500_import/502_bank_importer.js, 600_report/601_datamart_ingest.js (検証のみ), 800_ops/823_migration_bank_diff_backfill.js (新規), 900_test/901_test_runner.js
前提案件MAS-077 (settlement_date_sync), MAS-145 (bank_csv_import), MAS-162 (bank_combo_match / Pass 2.5 ソフト合算 / PR #465)
兄弟案件MAS-336 (je_inv_phase1_recognition) — 98,837 円乖離調査の他 2 件分

目的

PR #465 で導入された Pass 2.5 ソフト合算 (銀行明細 vs 複数 STL 合計の ±max(1,000, 1%) 一致) で消込された取引について、銀行実額と STL 合計の差額 (典型: 振込手数料・口座振替割引・為替差) を 33_wrk_bank の 差額(手数料等) 列に自動記録し、71_bs / 91_fs_bs の現預金 cash plug 整合性を確保する。

71_bs 2026-04 現預金乖離調査で発覚した 98,837 円乖離のうち 1,000 円差 1 件 (2026-04-30 武生年金 149,950 円出金 vs STL 合計 150,950 円) が、本機能未実装に起因していた。MAS-336 (je_inv_phase1_recognition) の Q4「Pass 2.5 ソフト合算差額の自動記録は別仕様で切り出す」と決定された範囲を本仕様で扱う。

現在のコード

1. 単一 STL マッチ時のみ差額を記録 (500_import/502_bank_importer.js L393-402)

if (anyProcessed) {
  if (stlIds.length === 1) {
    var stlDataRow = bankData[stlRowMap[stlIds[0]] - 1];
    var stlAmt = Math.abs(Utils.parseAmt(stlDataRow[bankIdx['税込金額_決済']]));
    var txAmt = Math.abs(Utils.parseAmt(row[importIdx['出金金額(円)']]) || 0)
              + Math.abs(Utils.parseAmt(row[importIdx['入金金額(円)']]) || 0);
    var diff = txAmt - stlAmt;
    if (Math.abs(diff) >= 1 && bankIdx['差額(手数料等)'] !== undefined) {
      bankSheet.getRange(stlRowMap[stlIds[0]], bankIdx['差額(手数料等)'] + 1).setValue(diff);
    }
  }
}

問題点:

  • stlIds.length === 1 のガードにより合算 STL (2 件以上) 時は差額が一切記録されない
  • 差額処理科目 (差額処理科目 列) の自動推定がない
  • 監査ログ (Utils.auditLog) が呼ばれていない

2. datamart 側の差額読取 (600_report/601_datamart_ingest.js PHASE 2 L199-210)

// 33タブの 差額(手数料等) を unionData に追加
if (bankIdx['差額(手数料等)'] !== undefined) {
  var diffAmt = Number(row[bankIdx['差額(手数料等)']]) || 0;
  if (Math.abs(diffAmt) >= 1) {
    var diffAcc = String(row[bankIdx['差額処理科目']] || '').trim() || '支払手数料';
    unionData.push({
      // ... 差額処理仕訳の生成 (科目フォールバック = 支払手数料)
    });
  }
}

datamart 側は既に「差額が記録されていれば」処理できる構造。問題は書込側のみ

3. 33_wrk_bank のスキーマ確認

DDL (100_config/101_sys_config.js) で 差額(手数料等) / 差額処理科目 列が定義されている前提。

修正方針

4.1 全体方針

  • 合算 STL (stlIds.length > 1) でも差額を記録する。差額計上先 STL を単一選択し、差額(手数料等)差額処理科目 の 2 列に書き込む
  • 単一 STL マッチ時の既存挙動は完全に保持する (回帰テスト F338-04 で担保)
  • 差額処理科目の自動推定ロジックを新設 (符号・金額・摘要キーワード)
  • 監査ログ (Utils.auditLog('SETTLE_DIFF', ...)) を必須化
  • UI 強化 (黄色ハイライト + プルダウン + ツールチップ)

4.2 applyBankSettlement の改修 (Step B)

if (anyProcessed) {
  // 合算対応: 全 STL の決済額合計を計算
  var stlAmtTotal = 0;
  for (var sj = 0; sj < stlIds.length; sj++) {
    var stlDataRow = bankData[stlRowMap[stlIds[sj]] - 1];
    stlAmtTotal += Math.abs(Utils.parseAmt(stlDataRow[bankIdx['税込金額_決済']]));
  }
  var txAmt = Math.abs(Utils.parseAmt(row[importIdx['出金金額(円)']]) || 0)
            + Math.abs(Utils.parseAmt(row[importIdx['入金金額(円)']]) || 0);
  var diff = txAmt - stlAmtTotal;

  if (Math.abs(diff) >= 1 && bankIdx['差額(手数料等)'] !== undefined) {
    // 差額計上先 STL を選定 (Q1 推奨: 案 B 最大金額計上)
    var targetStlId = chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff);
    var targetRow = stlRowMap[targetStlId];

    // 既存の差額処理科目が手動入力済なら上書きしない
    var existingDiffAcc = bankIdx['差額処理科目'] !== undefined
      ? String(bankData[targetRow - 1][bankIdx['差額処理科目']] || '').trim()
      : '';

    // 差額処理科目を自動推定
    var memo = String(row[importIdx['摘要']] || '');
    var inferredAcc = inferDiffAccount_(diff, memo);

    bankSheet.getRange(targetRow, bankIdx['差額(手数料等)'] + 1).setValue(diff);
    if (bankIdx['差額処理科目'] !== undefined && !existingDiffAcc) {
      bankSheet.getRange(targetRow, bankIdx['差額処理科目'] + 1).setValue(inferredAcc);
    }

    // 監査ログ (Step D)
    Utils.auditLog('SETTLE_DIFF', {
      action: stlIds.length > 1 ? 'combo_diff' : 'single_diff',
      stlIds: stlIds,
      targetStlId: targetStlId,
      diff: diff,
      inferredAcc: inferredAcc,
      existingDiffAcc: existingDiffAcc,
      memo: memo,
    });

    // 差額が異常に大きい場合は警告ログ
    if (Math.abs(diff) > 5000) {
      Utils.logWarn('[SETTLE_DIFF] 差額が 5,000 円超: diff=' + diff
        + ' / stlIds=' + stlIds.join(',') + ' / memo=' + memo);
    }
  }
}

4.3 ヘルパー関数 (Step A)

(1) chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff)

差額計上先の STL_ID を返す。Q1 推奨案 B (最大金額計上) 採用。

function chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff) {
  // 案 B: 最大金額の STL に差額を計上 (シンプル + 監査追跡容易 + 端数累積回避)
  var maxAmt = -Infinity;
  var targetId = stlIds[0];
  for (var i = 0; i < stlIds.length; i++) {
    var dataRow = bankData[/* stlRowMap[stlIds[i]] - 1 */];
    var amt = Math.abs(Utils.parseAmt(dataRow[bankIdx['税込金額_決済']]));
    if (amt > maxAmt) {
      maxAmt = amt;
      targetId = stlIds[i];
    }
  }
  return targetId;
}

(2) inferDiffAccount_(diff, memo)

差額処理科目を自動推定。

function inferDiffAccount_(diff, memo) {
  var memoNorm = String(memo || '').toUpperCase();

  // 摘要キーワードベースの推定 (優先)
  if (/振込手数料|フリコミテスウリヨウ|ATM/.test(memo)) return '支払手数料';
  if (/為替差/.test(memo)) return diff > 0 ? '為替差損' : '為替差益';
  if (/割引|ワリビキ/.test(memo)) return '雑収入';

  // 符号 + 金額ベースの推定 (フォールバック)
  if (diff > 0) {
    if (diff <= 500) return '支払手数料';        // 小額正差: 振込手数料
    if (diff <= 10000) return '支払手数料';       // 中額正差: 振込手数料
    return '雑損失';                              // 大額正差: 要人間確認
  } else {
    if (Math.abs(diff) <= 500) return '雑収入';   // 小額負差: 端数処理益
    if (Math.abs(diff) <= 2000) return '雑収入';  // 中額負差: 口座振替割引等
    return '雑収入';                              // 大額負差: 要人間確認
  }
}

推定ロジックテーブル (符号 × 金額)

差額の符号差額の額推定科目典型シーン
正 (+)1〜500 円支払手数料振込手数料 (端数)
正 (+)500〜10,000 円支払手数料振込手数料 (通常)
正 (+)10,000 円超雑損失要人間確認
負 (-)1〜500 円雑収入端数処理益
負 (-)500〜2,000 円雑収入口座振替割引・引去手数料控除
負 (-)2,000 円超雑収入要人間確認

摘要キーワード推定 (上書き優先)

摘要パターン推定科目
「振込手数料」「フリコミテスウリヨウ」「ATM」支払手数料
「為替差」為替差損 (diff>0) / 為替差益 (diff<0)
「割引」「ワリビキ」雑収入

4.4 UI 改善 (Step C)

36_wrk_bank_import のマッチ確認画面で:

  • 差額が ±1 円以上の行は 黄色背景 + ⚠ アイコンで視覚的に強調
  • 差額処理科目セルにプルダウン (支払手数料 / 雑収入 / 雑損失 / 為替差損 / 為替差益 / その他) を setDataValidation で設定
  • ホバーツールチップ (setNote) に「現在の差額: ±X 円 / 推定科目: Y / 上書き可」を表示
  • 差額が 5,000 円超の場合は 赤文字 + 警告アイコン

4.5 監査ログ強化 (Step D)

  • Utils.auditLog('SETTLE_DIFF', payload) を必須呼び出し
  • payload には stlIds, targetStlId, diff, inferredAcc, existingDiffAcc, memo, action (single_diff / combo_diff) を含む
  • 差額 > 5,000 円は Utils.logWarn で別途警告

4.6 既存データ遡及 migration (Step E)

800_ops/823_migration_bank_diff_backfill.js:

  • 対象期間: PR #465 マージ日 (2026-04-26 想定) 以降の Pass 2.5 合算消込済 STL
  • 抽出条件: 33_wrk_bank で 合算消込済 = TRUE かつ 差額(手数料等) が空または 0 の STL グループ
  • 銀行明細 (36_wrk_bank_import) と再突合し、差額を計算 → 差額(手数料等) + 差額処理科目 を補填
  • 冪等性: 差額(手数料等) === 0 || 空 のレコードのみ補填 (二重実行で値が変わらない)
  • ファイル番号: 822 は MAS-336 で予約済 → 823 を採番

影響範囲

ファイル変更内容規模
500_import/502_bank_importer.jsapplyBankSettlement の差額記録ブロック改修 + ヘルパー 2 関数追加~100 行追加
600_report/601_datamart_ingest.js修正なし (現行ロジックが合算先頭 STL に差額が乗っているケースをそのまま処理可能)。検証のみ実施0 行
800_ops/823_migration_bank_diff_backfill.js新規 (既存データ遡及補填)~150 行
100_config/101_sys_config.jsメニュー登録 (「🔧 マイグレーション」配下に「差額補填 (823)」を追加)~5 行
900_test/901_test_runner.jsF338-01〜F338-12 のテストケース追加~120 行
マスタ (12_mst_account)「差額処理科目」用途フラグ列を将来追加 (v1 拡張)0 行 (今回は対象外)

既存動作への影響:

  • 単一 STL マッチ時の差額記録 (現行ロジック) は完全保持 → F338-04 で回帰確認
  • datamart 側 (601_datamart_ingest.js PHASE 2 L199-210) は無変更 → 既存テストへの影響なし
  • 71_bs / 91_fs_bs の現預金 cash plug 整合性が向上 (98,837 円乖離のうち 1,000 円分が解消)

注意事項

  1. PR #465 (Pass 2.5 ソフト合算) と密接連動: Pass 2.5 でヒットした合算は 必ず人間確認 (確認FLG=false) が前提。本仕様の自動推定はあくまで初期値であり、人間が UI で承認・上書き可能であること。確認FLG=true の合算 STL に対してのみ差額が記録される動線を維持する
  2. 単一 STL マッチ時の既存挙動は壊さない: stlIds.length === 1 のケースで差額が記録される現行ロジックを完全保持する。回帰テスト F338-04 で担保。改修後も「単一マッチ時に差額処理科目フォールバック (支払手数料) が動く」既存仕様をキープ
  3. 差額が異常に大きい (> 5,000 円) ケース: 警告ログ (Utils.logWarn) + ユーザー確認 prompt。自動推定のみで処理を完結させない。具体的なしきい値は実運用で調整 (将来拡張)
  4. 同一摘要で連続発生する場合の学習: 摘要キャッシュ (例: 「武生年金事務所」 → 雑収入 学習) を v1 で追加余地。本 v0.1 では固定マッピング
  5. failure_patterns #35 (フィルター silent-fail) との関係: 差額書込は setValue 単発で問題なし (フィルター対象列ではない)。ただし将来 setValues 化検討時は failure_patterns #35 の挙動を再確認
  6. 為替差損益 (海外送金 Anthropic / Stripe / Wise 等) は本仕様の対象外: 別仕様 MAS-XXX_fx_translation で切り出し推奨。本仕様の自動推定では「摘要に『為替差』」のみ簡易判定し、通貨マスタ + 日次レート連動は MAS-FX 案件に委譲
  7. 部分決済との整合性: 18,378 円の INV を 12,000 円だけ部分決済したケースの「未決済残高 6,378 円」と「割引による打ち切り」の判別は本仕様の対象外。Q4 で別仕様化を推奨
  8. MAS-077 settlement_date_sync との関係: 本仕様は MAS-077 が確立した settlement_date 同期パターンを前提に動作。Action B での決済日転記が完了している前提で差額記録ロジックが走る
  9. 既存データの遡及補填 (Step E migration): PR #465 マージ後、Pass 2.5 で消込された STL のうち差額が抜けているレコードを抽出して補填。冪等性ガード (差額(手数料等) === 0 || 空 のレコードのみ補填) を必須化
  10. テストランナー追加: F338-01〜F338-12 を 901_test_runner.js に追加 (Step F)
  11. 既存の単一マッチ時差額処理科目フォールバック (支払手数料): 改修後もキープ。自動推定で上書き可能化するが、フォールバック自体は datamart 側 (601_datamart_ingest.js L199-210) で機能している
  12. failure_patterns #18-#20 (命名造語禁止): 推定科目の名前は 12_mst_account に実在するもののみ採用 (支払手数料 / 雑収入 / 雑損失 / 為替差損 / 為替差益)。Read で実マスタを確認してから採用

エッジケース

#条件検知方法期待される挙動ログ出力
1差額 = 0 (完全一致)Math.abs(diff) < 1何も書かない (現行通り)なし
2差額が異常に大きい (> 5,000 円)Math.abs(diff) > 5000警告ログ + ユーザー確認 prompt + 推定科目はそのまま記録logWarn
3単一 STL マッチ + 振込手数料 (例: 11,440 vs 11,000)stlIds.length === 1 && diff > 0既存挙動完全保持・差額 +440 / 推定 支払手数料auditLog single_diff
4合算 STL マッチ (2 件) + 1,000 円差 (例: 武生年金 149,950 vs 150,950)stlIds.length === 2 && diff < 0最大金額 STL (76,375) に差額 -1,000 計上 / 推定 雑収入auditLog combo_diff
5合算 STL マッチ (3 件) + マイナス差額stlIds.length === 3 && diff < 0最大金額 STL に差額 -X 計上 / 推定 雑収入auditLog combo_diff
6摘要に「振込手数料」/振込手数料/.test(memo)推定科目が 支払手数料 (符号・金額より優先)auditLog memo_match=true
7摘要に「ワリビキ」`/割引ワリビキ/.test(memo)`推定科目が 雑収入
8摘要に「為替差」 + 正差/為替差/.test(memo) && diff > 0推定科目が 為替差損auditLog memo_match=true
9摘要に「為替差」 + 負差/為替差/.test(memo) && diff < 0推定科目が 為替差益auditLog memo_match=true
10既に差額処理科目が手動入力されているexistingDiffAcc !== ''上書きしない (!existingDiffAcc ガード)・差額金額のみ更新auditLog existingDiffAcc=...
11合算 STL の中に 0 円 STL が混入stlAmt === 0スキップ + 警告ログlogWarn zero_stl=stlId
12同一 ORD で複数 STL が同時消込recognizedJeSet パターン二重カウント防止 (unionData レベルで重複排除)auditLog dedupe=true
13確認FLG=false (人間未承認)確認FLG !== true差額記録をスキップ (Human-in-the-Loop 維持)auditLog skipped_unconfirmed
14差額(手数料等) 列が DDL に存在しないbankIdx['差額(手数料等)'] === undefined何も書かない + DDL 修正を促す警告ログlogWarn missing_diff_column

実データ検証

実装前に以下を MCP / 手作業で確認:

  1. 武生年金事務所 149,950 円出金 (2026-04-30) の Pass 2.5 合算消込済 STL:

    • STL_20260331_0058 (社保控除 INV_0011): 74,575
    • STL_20260331_0059 (法定福利費 INV_0012): 76,375
    • 期待: 改修後に最大金額 STL_0059 に差額 -1,000 / 推定 雑収入 が記録される
    • 71_bs の現預金が銀行残高 1,680,108 円と一致 (MAS-336 と合わせて 98,837 円乖離が完全解消)
  2. 多月にわたる累積 (2026-01〜2026-04):

    • 71_bs / 91_fs_bs の cash plug が正しく推移
    • 各月の差額合計が 33タブ集計と一致
  3. 単一マッチ (例: 11,440 vs 11,000 振込手数料):

    • 既存挙動完全保持: 差額 +440 / 推定 支払手数料
    • F338-04 で回帰確認
  4. 既存データ遡及 migration:

    • PR #465 マージ後 (2026-04-26〜) の Pass 2.5 合算消込済 STL を抽出
    • 差額補填件数 (期待: 5-10 件程度)
    • 補填後の 71_bs cash plug 改善幅
  5. DDL 列確認:

    • 100_config/101_sys_config.jsWRK_BANK の headers に 差額(手数料等) / 差額処理科目 が存在することを確認
    • 列インデックスが期待通りか (bankIdx['差額(手数料等)'] / bankIdx['差額処理科目'])
  6. 12_mst_account の実マスタ確認:

    • 支払手数料, 雑収入, 雑損失, 為替差損, 為替差益 が登録済みか (failure_patterns #18-#20 命名造語禁止)
    • 未登録の場合は v1 拡張で追加するか、本 v0.1 では既存科目に丸める

関連ドキュメント

仕様書関連箇所
MAS-077 settlement_date_sync上流仕様 (Action B での決済日同期)
MAS-145 銀行 CSV 取込銀行マッチング基盤 (matched フラグ・stlRowMap)
MAS-162 銀行 CSV 合算マッチングPass 2.5 ソフト合算 (PR #465 反映済・本仕様のトリガー)
MAS-336 仕訳振替INV PHASE 1 同月計上兄弟仕様 (98,837 円乖離の他 2 件分) / Q4 で「別仕様化」と決定
docs/_internal/changelog.mdPR #465 (Pass 2.5 ソフト合算追加)
docs/_internal/BUG_tracking.mdMAS-302 (applyBankSettlement silent-fail)
docs/_internal/failure_patterns.md#18-#20 (命名造語禁止) / #35 (フィルター silent-fail)

人間が検討すべき事項

Q1. 合算時の差額按分方式

選択肢:

  • 案 A: 末尾計上 (シンプル・並び順依存で再現性低)
  • 案 B: 最大金額の STL に計上 (シンプル + 監査容易)
  • 案 C: STL 金額で按分 (会計的厳密・端数処理複雑)
  • 案 D: 先頭 + メモ列に「合算差額」フラグ

推奨: 案 B (最大金額の STL に計上)

理由:

  • (a) シンプル + 監査追跡容易: 「どの STL に差額が乗っているか」が一意に決まる
  • (b) 按分 (案 C) は端数処理が複雑化し 1 円ずれが累積する (3 STL を 3 等分すると 1 円残る)
  • (c) 末尾 (案 A) は STL 並び順 (作成順 / ソート順) に依存して再現性が低い
  • (d) 最大金額 STL に乗せると割合的に差額影響が小さく見え、財務諸表上で目立ちにくい
  • (e) 別途「合算差額メモ」列は将来拡張で対応可能

Q2. 自動推定の閾値・科目マッピング

推奨: 上記の表 (符号 × 金額 + 摘要キーワード) は実務に合う想定で v0.1 採用。ただし「業種別 / 取引先別の上書きを許可」は v1 で拡張 (MST_DICT の新カテゴリ「差額処理科目」+ 12_mst_account ルックアップ)

理由:

  • 業種固有の慣習 (例: 海外取引で 為替差頻発 / 製造業で 値引き頻発) は固定マッピングで吸収しきれない
  • 段階拡張: v0.1 で固定マッピング → v1 で取引先マスタ連動 → v2 で機械学習
  • 摘要キーワード推定が符号・金額より優先される設計は維持 (人間の意図反映)

Q3. 既存データの扱い

推奨: migration script で過去の銀行明細と STL を再突合して差額を埋める

実装:

  • 対象期間: PR #465 マージ日 (2026-04-26 想定) 以降の Pass 2.5 合算消込済 STL のみ
  • ファイル: 800_ops/823_migration_bank_diff_backfill.js (822 は MAS-336 で予約済)
  • 冪等性ガード必須: 差額(手数料等) === 0 || 空 のレコードのみ補填
  • MAS-336 の migration script (822) と並行実行可能 (互いに独立)

理由:

  • 71_bs / 91_fs_bs の遡及整合性が向上 (98,837 円乖離の解消に直結)
  • Pass 2.5 リリース日以降の対象範囲が限定的で工数小
  • 冪等性により再実行可能 (誤動作時のロールバックリスク低)

Q4. 部分決済との関係

推奨: 本仕様は「合算消込時の差額」のみ対象。部分決済は別案件として別途起票推奨

理由:

  • (a) 部分決済 (32タブ INV の 部分決済 ステータス) の差額判別は MAS-077 settlement_date_sync の上流仕様で扱うべき領域
  • (b) 「未決済残高」と「割引による打ち切り」の判別はビジネスロジック差で要件定義困難 (取引先との合意次第)
  • (c) 部分決済時の自動科目推定は v2 で別案件起票推奨 (MAS-XXX_partial_settlement_diff)

Q5. 為替差損益の扱い

推奨: 本仕様の対象外。別仕様 MAS-XXX_fx_translation で切り出し

理由:

  • (a) 為替差は 通貨マスタ (currency master) + 日次レート連動 (Bloomberg API / 三菱UFJ TTM 等) が必要で技術スタック差異
  • (b) 海外送金固有の摘要パターン (Anthropic / Stripe / Wise 等の SWIFT 摘要) を別途学習必要
  • (c) スコープ肥大による migration リスク回避

ただし: 本仕様の自動推定で「為替差」摘要キーワードは初期値として 為替差損 / 為替差益 をセットして将来の MAS-FX 案件に橋渡しする (Step A の inferDiffAccount_ 内で対応)

実装プロンプト(Claude Code 用)

Step A: 差額按分ロジックと自動推定ヘルパー関数の追加 (Sonnet)

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-338「STL 消込時 差額自動処理機能」の Step A(ヘルパー関数追加)を実装してください。

## 実行前タスク
1. `500_import/502_bank_importer.js` の L380-410 (applyBankSettlement の差額記録ブロック) を Read
2. `000_infra/004_utils.js` の Utils.parseAmt / Utils.auditLog / Utils.logWarn の実装を Read
3. `12_mst_account` のプルダウン値を MCP で確認 (支払手数料 / 雑収入 / 雑損失 / 為替差損 / 為替差益 の存在確認)
4. 既存の私有関数命名規則 (末尾アンダースコア `_`) を Read で確認

## 修正対象ファイル
- `500_import/502_bank_importer.js` への追記のみ (既存関数の改変なし)

## 実装内容
1. `chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff, stlRowMap)` を追加
   - 案 B (最大金額 STL 計上) ロジック
   - 戻り値: 差額計上先の STL_ID (string)
2. `inferDiffAccount_(diff, memo)` を追加
   - 摘要キーワード優先 (振込手数料 → 支払手数料 / 為替差 → 為替差損益 / 割引 → 雑収入)
   - 符号 + 金額フォールバック (推定ロジックテーブル参照)
   - 戻り値: 推定科目名 (string)

## 制約
- applyBankSettlement 本体は Step B で改修するため Step A では触らない
- 12_mst_account に存在しない科目名は使用禁止 (failure_patterns #18-#20)
- 既存の差額記録ロジック (L393-402) は Step B で改修するため温存

## 動作確認
1. `npm run push:dev`
2. GAS エディタで chooseDiffTargetStl_(['STL_001', 'STL_002'], [[..., 1000], [..., 2000]], {税込金額_決済: N}, -100, {STL_001: 1, STL_002: 2}) を呼び出し → 'STL_002' が返ることを確認
3. inferDiffAccount_(-1000, '武生年金事務所') → '雑収入' が返ることを確認
4. inferDiffAccount_(440, '振込手数料') → '支払手数料' が返ることを確認

Step B: applyBankSettlement 本体の改修 (Opus)

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-338 の Step B(applyBankSettlement 改修)を実装してください。

## 実行前タスク
1. `500_import/502_bank_importer.js` の applyBankSettlement 全体を Read (L300-450 想定)
2. Step A で追加した chooseDiffTargetStl_ / inferDiffAccount_ の実装を Read
3. `33_wrk_bank` の DDL (`100_config/101_sys_config.js` WRK_BANK headers) を Read で確認
4. `36_wrk_bank_import` の DDL (摘要列名) を Read で確認

## 修正対象ファイル
- `500_import/502_bank_importer.js` の applyBankSettlement のみ

## 実装内容
L393-402 の単一 STL マッチ時のみ差額記録するブロックを「修正方針 4.2」のコードに差し替え:
1. `stlIds.length` に関わらず stlAmtTotal を全 STL 合計で計算
2. diff = txAmt - stlAmtTotal で算出
3. chooseDiffTargetStl_ で計上先 STL を選定
4. inferDiffAccount_ で推定科目を決定
5. 既存の差額処理科目が手動入力済なら上書きしない (`!existingDiffAcc` ガード)
6. Utils.auditLog('SETTLE_DIFF', ...) を必須呼び出し
7. Math.abs(diff) > 5000 で Utils.logWarn 警告

## 制約
- 既存の単一マッチ挙動を回帰させない (F338-04 で担保)
- 確認FLG=false の合算 STL は差額記録をスキップ (Human-in-the-Loop 維持)
- matched=true ロックは Step B では触らない (PR #465 の挙動を保持)
- failure_patterns #35 (フィルター silent-fail): setValue 単発で OK・setValues 化は将来検討

## エッジケース
- 差額 = 0: 何も書かない
- 差額 > 5,000 円: 警告ログ + 推定科目記録継続
- 既存差額処理科目あり: 上書きしない (金額のみ更新)
- 0 円 STL 混入: スキップ + 警告ログ

## 動作確認
1. `npm run push:dev`
2. 武生年金 149,950 円出金 (2026-04-30) を Pass 2.5 合算消込 → 33タブで差額 -1,000 / 推定 `雑収入` が STL_0059 (76,375 円側) に記録されることを確認
3. 単一マッチ (11,440 vs 11,000) → 既存挙動が維持され差額 +440 / 推定 `支払手数料` が記録されることを確認 (回帰)
4. 71_bs の現預金が銀行残高 1,680,108 円と一致 (MAS-336 と合わせて 98,837 円乖離が解消)

Step C: UI 強化 (Sonnet)

案件 MAS-338 の Step C (UI 強化) を実装してください。

## 実装内容
1. 36_wrk_bank_import のマッチ確認画面で:
   - 差額 ±1 円以上の行に黄色背景 (`#fff2cc`) + ⚠ アイコン (絵文字)
   - 差額処理科目セルに setDataValidation でプルダウン (`支払手数料 / 雑収入 / 雑損失 / 為替差損 / 為替差益 / その他`)
   - setNote で「現在の差額: ±X 円 / 推定科目: Y / 上書き可」を表示
   - 差額 > 5,000 円は赤文字 (`#cc0000`) + 警告アイコン
2. プルダウンの選択肢は `12_mst_account` 実値を MCP で確認 (failure_patterns #18-#20)

## 制約
- 既存のマッチ確認画面レイアウトは破壊しない
- 差額が 0 の行は黄色ハイライトしない (視覚的ノイズ削減)

Step D: 監査ログ強化 (Haiku)

案件 MAS-338 の Step D (監査ログ強化) を実装してください。

## 実装内容
Step B の applyBankSettlement 内で:
1. Utils.auditLog('SETTLE_DIFF', payload) を必須呼び出し
2. payload には以下を含む:
   - action: 'single_diff' | 'combo_diff' | 'skipped_unconfirmed'
   - stlIds: string[]
   - targetStlId: string
   - diff: number
   - inferredAcc: string
   - existingDiffAcc: string
   - memo: string
3. Math.abs(diff) > 5000 で Utils.logWarn 警告

## 制約
- 既存の auditLog 呼び出しを変更しない
- logWarn のメッセージプレフィックスは `[SETTLE_DIFF]` で統一

Step E: 既存データ遡及 migration (Sonnet)

案件 MAS-338 の Step E (migration) を実装してください。

## 実行前タスク
1. `800_ops/805_migration_d04_d06.js` を Read (マイグレーションスクリプトの規約確認)
2. CLAUDE.md の「マイグレーションスクリプト運用ガイドライン」セクションを参照
3. 822 が MAS-336 で予約済であることを確認 → 823 を採番

## 修正対象ファイル
- 新規: `800_ops/823_migration_bank_diff_backfill.js`
- 修正: `100_config/101_sys_config.js` (メニュー登録)

## 実装内容
1. `migrationBankDiffBackfill()` 関数を新規作成
   - 対象期間: PR #465 マージ日 (2026-04-26) 以降
   - 対象データ: 33_wrk_bank で 合算消込済 = TRUE かつ `差額(手数料等) === 0 || 空` の STL グループ
   - 銀行明細 (36_wrk_bank_import) と再突合 → 差額計算
   - chooseDiffTargetStl_ + inferDiffAccount_ を流用 (Step A のヘルパー)
   - 補填後 Utils.logInfo + SpreadsheetApp.getUi().alert で結果表示
2. メニュー登録: 「🔧 マイグレーション」配下に「差額補填 (823)」を追加
3. 冪等性: 同じ STL を 2 回処理しても結果が変わらない

## 動作確認
1. dev で実行 → 補填件数 (期待: 5-10 件)
2. 同じ migration を再実行 → 0 件補填 (冪等性確認)
3. 71_bs cash plug の改善幅を確認

Step F: テスト追加 (Haiku)

案件 MAS-338 の Step F (テスト追加) を実装してください。

## 実装内容
`900_test/901_test_runner.js` に以下のテストケースを追加:
- F338-01: 差額 = 0 (何も書かない)
- F338-02: 単一 STL マッチ + 振込手数料 +440 (既存挙動回帰)
- F338-03: 合算 STL マッチ (2 件) + 1,000 円差 (武生年金シナリオ)
- F338-04: 単一マッチ時の差額処理科目フォールバック (`支払手数料`) 維持
- F338-05: 摘要キーワード「振込手数料」 → `支払手数料` 推定
- F338-06: 摘要キーワード「ワリビキ」 → `雑収入` 推定
- F338-07: 摘要キーワード「為替差」 + 正差 → `為替差損` 推定
- F338-08: 摘要キーワード「為替差」 + 負差 → `為替差益` 推定
- F338-09: 既存差額処理科目あり → 上書きしない
- F338-10: 差額 > 5,000 円 → 警告ログ
- F338-11: 0 円 STL 混入 → スキップ + 警告
- F338-12: 確認FLG=false → 差額記録スキップ

## 制約
- テストケースは isolated (互いに依存しない)
- 各テストで Utils.auditLog の呼び出し回数を検証

推奨実行モデル

工程推奨モデル理由
Step A: ヘルパー関数追加Claude Sonnet 4.6純粋関数 + 推定ロジックの実装で既存パターン応用
Step B: applyBankSettlement 改修Claude Opus 4.7 (1M context)合算 + 差額按分 + 既存挙動維持の判断・複数の関数間整合性
Step C: UI 強化Claude Sonnet 4.6既存 setDataValidation / setNote パターン応用
Step D: 監査ログ強化Claude Haiku 4.5Utils.auditLog 呼び出しのみ・コード完全定義済
Step E: migrationClaude Sonnet 4.6既存 migration パターン (805 / 806) の踏襲 + 冪等性ガード判断
Step F: テスト追加Claude Haiku 4.5901_test_runner.js のテスト追加パターン定型

変更履歴

日付変更内容
2026-05-01v0.1 起票 (main 側起票依頼を受けて MAS-338 として起草。Q1-Q5 推奨案を提示・MAS-336 の Q4 で「別仕様化」と決定された Pass 2.5 ソフト合算差額の自動記録を本仕様で扱う・既存単一マッチ挙動は完全保持)

仕様書作成プロンプト

展開して表示
あなたは bizlp-gas-accounting プロジェクトの仕様書起票担当です。新規仕様書 **MAS-338: STL 消込時 差額自動処理機能 (Settlement Diff Handling)** の本体を起草し、ファイルに書き出してください。

## ゴール
`docs/dev/dev_mas-338_settlement_diff_handling.md` を新規作成。約 500〜700 行・14 セクション全網羅・`docs/_internal/dev_spec_prompt_template.md` のテンプレート遵守。

## 案件 ID 採番経緯 (確定済)
- 同セッションで MAS-336 (je_inv_phase1) + MAS-337 (loan_screening) を消化済
- ユーザー希望「MAS-077 settlement_date_sync 近傍」だが MAS-077 cluster は使用済
- failure_patterns #31 (ID 衝突回避) に従い、次の空き **MAS-338** を採用
- 仕様書本文の関連ドキュメントセクションで MAS-077 とのリンクを明示

## 背景 (起票依頼の要約)
main ワークスペース実装担当からの依頼。本仕様は同セッションで起票した **MAS-336 (je_inv_phase1) の Q4 で「別仕様で切り出す」と判断した Pass 2.5 ソフト合算差額の自動記録**を本仕様で扱う。

### 現状の問題
71_bs 2026-04 現預金乖離調査 (98,837 円) の根本原因 3 件のうち **1 件 (1,000 円差)** が、銀行実額と STL 合計の差額を 33_wrk_bank の `差額(手数料等)` 列に記録していないことに起因していた。

#### 実例
- 銀行明細: 武生年金事務所 **149,950 円 出金** (2026-04-30)
- 33_wrk_bank STL:
  - STL_20260331_0058 (社保控除 INV_0011): 74,575
  - STL_20260331_0059 (法定福利費 INV_0012): 76,375
  - STL 合計: **150,950 円**
- 差額: **-1,000 円** (口座振替割引と推定)

PR #465 で **Pass 2.5 ソフト合算** を導入し、銀行明細とソフト一致 (±max(1,000, 1%)) の合算候補を自動マッチできるようになったが、消込実行 (`applyBankSettlement`) は **`stlIds.length === 1` の単一マッチ時にしか差額を記録していない** (`500_import/502_bank_importer.js` line 393-402):

```js
if (anyProcessed) {
  if (stlIds.length === 1) {
    var stlDataRow = bankData[stlRowMap[stlIds[0]] - 1];
    var stlAmt = Math.abs(Utils.parseAmt(stlDataRow[bankIdx['税込金額_決済']]));
    var txAmt = Math.abs(Utils.parseAmt(row[importIdx['出金金額(円)']]) || 0)
              + Math.abs(Utils.parseAmt(row[importIdx['入金金額(円)']]) || 0);
    var diff = txAmt - stlAmt;
    if (Math.abs(diff) >= 1 && bankIdx['差額(手数料等)'] !== undefined) {
      bankSheet.getRange(stlRowMap[stlIds[0]], bankIdx['差額(手数料等)'] + 1).setValue(diff);
    }
  }
}
```

このため Pass 2.5 で「差額付きマッチ」したケースは差額が会計上ロストし、71_bs の現預金がずれる。

### 差額が発生する典型シーン
| シーン | 差額の正体 | 既存処理 | 改善必要性 |
|---|---|---|---|
| 単一 STL + 振込手数料 (例: 11,440 vs 11,000) | 手数料 +440 | ○ 現行で記録される (single match 時) | 維持 |
| 単一 STL + 口座振替割引 (例: 50 円割引) | 割引 -50 | ○ 現行で記録される | 維持 |
| 合算 STL (>1) + 口座振替割引 | 割引 -1,000 | × 記録されない | **修正対象** |
| 合算 STL + 為替差損益 (海外送金) | 為替 ±数百円 | × 記録されない | **修正対象** |
| 部分決済 (一部金額のみ) | 残金 = 未決済残高 | △ 未決済残高列で表現 | 整合性確認 |
| INV と STL の符号不一致 (返金等) | 値引き / 返品 | △ 手動対応 | UI 改善余地 |

### 関連調査結果
- `600_report/601_datamart_ingest.js` PHASE 2 line 199-210 で 33タブ `差額(手数料等)` 列を読み取り、unionData に追加するロジックは既に存在
- つまり 33タブに正しく差額が書き込まれていれば datamart 側は処理できる
- **問題は書き込み側 (`applyBankSettlement`) にあり、合算時の差額が抜けている**

## frontmatter (先頭)
```yaml
---
id: MAS-338
aliases: ["SETTLE-DIFF"]
type: Story
status: Open
---
```

## 仕様書に必ず含めるべき内容

### Section 1. 概要 + Section 2. 目的
依頼の問題意識 + 98,837 円乖離調査結果 + Pass 2.5 ソフト合算 (PR #465) との関係 + MAS-336 (je_inv_phase1) で「別仕様化」と決定された経緯。

### Section 3. 現在のコード (検証済の正確な行番号)
- `500_import/502_bank_importer.js` line 393-402: 単一 STL マッチ時のみ差額記録
- `600_report/601_datamart_ingest.js` PHASE 2 line 199-210: 差額読取 + unionData 追加 (`差額処理科目`列のフォールバック = `支払手数料`)
- 既存の `差額(手数料等)` / `差額処理科目` 列は 33_wrk_bank に存在 (DDL 確認済前提)

### Section 4. 修正方針

**4.1 差額記録ロジックの拡張**
- 合算 STL (>1) でも差額を記録する
- `findComboGroup_` ヒット時の差額 = 銀行金額 - STL 合計

**4.2 `applyBankSettlement` の改修案**
```js
if (anyProcessed) {
  var stlAmtTotal = 0;
  for (var sj = 0; sj < stlIds.length; sj++) {
    var stlDataRow = bankData[stlRowMap[stlIds[sj]] - 1];
    stlAmtTotal += Math.abs(Utils.parseAmt(stlDataRow[bankIdx['税込金額_決済']]));
  }
  var txAmt = Math.abs(Utils.parseAmt(row[importIdx['出金金額(円)']]) || 0)
            + Math.abs(Utils.parseAmt(row[importIdx['入金金額(円)']]) || 0);
  var diff = txAmt - stlAmtTotal;

  if (Math.abs(diff) >= 1 && bankIdx['差額(手数料等)'] !== undefined) {
    var targetStlId = chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff);  // 案 A/B/C/D
    var inferredAcc = inferDiffAccount_(diff, mtx.memo);                       // 自動推定
    bankSheet.getRange(stlRowMap[targetStlId], bankIdx['差額(手数料等)'] + 1).setValue(diff);
    if (bankIdx['差額処理科目'] !== undefined && !existingDiffAcc) {
      bankSheet.getRange(stlRowMap[targetStlId], bankIdx['差額処理科目'] + 1).setValue(inferredAcc);
    }
  }
}
```

**4.3 ヘルパー関数**:

(1) `chooseDiffTargetStl_(stlIds, bankData, bankIdx, diff)` - 差額計上先 STL 選択
(2) `inferDiffAccount_(diff, memo)` - 差額処理科目の自動推定

#### 推定ロジック (Section 4 内に必須記載):

| 差額の符号 | 差額の額 | 推定科目 |
|---|---|---|
| 正 (+) | 1〜500 円 | 支払手数料 (振込手数料) |
| 正 (+) | 500〜10,000 円 | 支払手数料 |
| 正 (+) | 10,000 円超 | 雑損失 (要人間確認) |
| 負 (-) | 1〜500 円 | 雑収入 (端数処理益) |
| 負 (-) | 500〜2,000 円 | 雑収入 (口座振替割引等) |
| 負 (-) | 2,000 円超 | 雑収入 (要人間確認) |

**摘要 (memo) ベースの推定**:
- 「振込手数料」「フリコミテスウリヨウ」 → `支払手数料`
- 「為替差」 → `為替差損 / 為替差益`
- 「割引」「ワリビキ」 → `雑収入`
- 「ATM」 → `支払手数料`

**4.4 UI 改善**:
- 36_wrk_bank_import の マッチ確認画面で差額が出ている行を視覚的に強調 (黄色背景 + アイコン)
- 差額処理科目を選択する prompt セルにプルダウン (`支払手数料 / 雑収入 / 雑損失 / 為替差損益 / その他`)
- ホバーツールチップで「現在の差額: ±X 円 / 推定科目: Y / 調整可」表示

**4.5 監査ログ強化**:
- 合算時に差額を記録する操作は `Utils.auditLog('SETTLE_DIFF', ...)` を必須化
- 合算先頭/末尾どの STL に計上したか、按分方式は何か をログに残す

### Section 5. 影響範囲
- `500_import/502_bank_importer.js`: `applyBankSettlement` 改修 + ヘルパー 2 関数追加 (~100 行)
- `600_report/601_datamart_ingest.js`: PHASE 2 line 199-210 は現行のままで対応可能 (合算先頭 STL に差額が乗っているだけで datamart 計算は変わらない)。ただし以下の検証を追加: (a) 同 ORD 内の複数 STL が同時に消込済になる場合の二重カウント防止 (b) 差額が大きすぎる (例: > 5,000 円) 場合の警告ログ
- マスタ定義: `MST_DICT` の新カテゴリ「差額処理科目」を追加。`12_mst_account` には既存項目があるはずなので、その中から差額用途を許容する科目をフラグ化

### Section 6. 注意事項 (10-12 件)
1. PR #465 の Pass 2.5 ソフト合算と密接連動: Pass 2.5 でヒットした合算は **必ず人間確認 (確認FLG=false)** が前提。本仕様の自動推定はあくまで初期値で、人間が UI で承認・上書き可能であること
2. 単一 STL マッチ時の既存挙動は壊さない (現行ロジック完全保持・回帰テスト必須)
3. 差額が異常に大きい (> 5,000 円) ケースは警告ログ + ユーザー確認 prompt
4. 同一摘要で連続発生する場合は摘要キャッシュで推定科目を学習する余地 (将来拡張)
5. failure_patterns #35 (フィルター silent-fail) との関係: 差額書込も `setValues` 化を検討 (現行の `setValue` 単発でも問題なし・要検証)
6. 為替差損益 (海外送金 Anthropic / Stripe 等) は本仕様の対象外 → 別仕様 `MAS-XXX_fx_translation` で切り出し推奨
7. 部分決済 (32タブ INV の `部分決済` ステータス) との整合性: 18,378 部分決済時の残額は「未決済残高」or「割引」の判別を仕様書で明文化
8. MAS-077 settlement_date_sync との関係: 本仕様は MAS-077 が確立した settlement_date 同期パターンを前提に動作
9. 既存データの遡及補填: PR #465 マージ後、Pass 2.5 で消込された STL のうち差額が抜けているレコードを migration script で補填するか (Q3 で議論)
10. テストランナー追加: F338-01〜F338-12 (`901_test_runner.js`)
11. 既存の単一マッチ時差額処理科目フォールバック (`支払手数料`) はキープ + 自動推定で上書き可能化

### Section 7. エッジケース (10-12 件)
1. 差額 = 0 (完全一致): 何も書かない (現行通り)
2. 差額が異常に大きい (> 5,000 円): 警告ログ + ユーザー確認 prompt
3. 同一摘要で連続発生: 摘要キャッシュで推定科目を学習する余地 (将来)
4. 単一 STL マッチ + 振込手数料: 既存挙動が壊れていない (回帰テスト)
5. 合算 STL マッチ (2 件) + 1,000 円差: 末尾/最大/按分の選択した STL に差額計上される
6. 合算 STL マッチ (3 件) + マイナス差額: 推定科目が `雑収入` になる
7. 摘要に「振込手数料」: 推定科目が `支払手数料` になる
8. 摘要に「ワリビキ」: 推定科目が `雑収入` になる
9. 為替差: 摘要 + 通貨判定で `為替差損 / 為替差益` 推定 (本 v0.1 では「摘要のみ」で簡易判定)
10. 既に差額処理科目が手動入力されている場合: 上書きしない (`!existingDiffAcc` ガード)
11. 合算 STL の中に 0 円 STL が混入: スキップ + 警告
12. 同一 ORD で複数 STL が同時消込: 二重カウント防止 (`recognizedJeSet` パターンを応用)

### Section 8. 実データ検証 (具体シナリオ + 期待値)
1. **武生年金 149,950 を Pass 2.5 で合算消込**: 33タブで差額 -1,000 が記録 → 差額処理科目「雑収入」自動推定 → 71_bs の現預金が銀行残高 1,680,108 と一致 (MAS-336 と合わせて 98,837 円乖離が完全解消)
2. **多月にわたる累積**: 71_bs / 91_fs_bs の cash plug が正しく推移
3. **単一マッチ (例: 11,440 vs 11,000 振込手数料)**: 既存挙動完全保持・差額 +440 / 推定 `支払手数料`
4. **既存データ遡及 migration**: PR #465 マージ後の Pass 2.5 で消込された STL を抽出 → 差額補填

### Section 9. 関連ドキュメント
- [MAS-077 settlement_date_sync](dev_mas-077_settlement_date_sync.md) — 上流仕様
- [MAS-145 銀行 CSV 取込](dev_mas-145_bank_csv_import.md) — 銀行マッチング基盤
- [MAS-162 銀行 CSV 合算マッチング](dev_mas-162_bank_combo_match.md) — Pass 2.5 ソフト合算 (PR #465 反映済)
- [MAS-336 仕訳振替INV PHASE 1 同月計上](dev_mas-336_je_inv_phase1_recognition.md) — 兄弟仕様 (98,837 円乖離の他 2 件分)
- `docs/_internal/changelog.md` PR #465 (Pass 2.5 ソフト合算追加)
- `docs/_internal/BUG_tracking.md` MAS-302 (applyBankSettlement silent-fail)
- `docs/_internal/failure_patterns.md` #35 (フィルター silent-fail)

### Section 10. 人間が検討すべき事項 (Q1-Q5 必須・推奨 + トレードオフ + 推奨理由)

#### Q1. 合算時の差額按分方式
- 案 A: 末尾計上 (シンプル) / 案 B: 最大計上 (端数処理) / 案 C: 按分 (会計的厳密) / 案 D: 先頭+メモ列
- **推奨**: **案 B (最大金額の STL に計上)**。理由: (a) シンプル + 監査追跡容易 (b) 按分は端数処理が複雑化 (1 円ずれが累積する) (c) 末尾は STL 並び順に依存して再現性が低い (d) 最大金額 STL に乗せると割合的に差額影響が小さく見え自然・別途「合算差額メモ」列は将来拡張で

#### Q2. 自動推定の閾値・科目マッピング
- **推奨**: 上記の表は実務に合う想定で v0.1 採用。ただし「業種別 / 取引先別の上書きを許可」は v1 で拡張 (MST_DICT「差額処理科目」+ 12_mst_account ルックアップ)。理由: 業種固有の慣習 (例: 海外取引で 為替差頻発 / 製造業で 値引き頻発) は固定マッピングで吸収しきれない・段階拡張

#### Q3. 既存データの扱い
- **推奨**: **migration script で過去の銀行明細と STL を再突合して差額を埋める**。対象期間は PR #465 マージ日以降 (合算消込から差額が抜けているレコードのみ)。理由: 71_bs / 91_fs_bs の遡及整合性が向上・MAS-336 の migration script (822) と並行実行可能・冪等性ガード必須 (`差額(手数料等) === 0 || 空 のレコードのみ補填`)

#### Q4. 部分決済との関係
- **推奨**: **本仕様は「合算消込時の差額」のみ対象・部分決済は別案件**。理由: (a) 部分決済 (32タブ INV の `部分決済` ステータス) の差額判別は MAS-077 settlement_date_sync の上流仕様で扱うべき領域 (b) 「未決済残高」と「割引」の判別はビジネスロジック差で要件定義困難。部分決済時の自動科目推定は v2 で別案件起票推奨 (`MAS-XXX_partial_settlement_diff`)

#### Q5. 為替差損益の扱い
- **推奨**: **本仕様の対象外・別仕様 `MAS-XXX_fx_translation` で切り出し**。理由: (a) 為替差は 通貨マスタ (currency master) + 日次レート連動 (Bloomberg API 等) が必要で技術スタック差異 (b) 海外送金固有の摘要パターン (Anthropic / Stripe / Wise 等の SWIFT 摘要) を別途学習必要 (c) スコープ肥大による migration リスク回避。**ただし本仕様の自動推定で「為替差」摘要キーワードは初期値として `為替差損 / 為替差益` をセット**して将来の MAS-FX 案件に橋渡し

### Section 11. 実装プロンプト (Claude Code 用・Step A-F)
- **Step A**: 差額按分ロジック (`chooseDiffTargetStl_`) と自動推定 (`inferDiffAccount_`) のヘルパー関数追加 (Sonnet)
- **Step B**: `applyBankSettlement` 本体を改修して合算時も差額を記録 (Opus)
- **Step C**: UI 強化 (黄色ハイライト + ツールチップ + プルダウン) (Sonnet)
- **Step D**: 監査ログ強化 (Haiku)
- **Step E**: 既存データ遡及 migration (`800_ops/823_migration_bank_diff_backfill.js`・822 は MAS-336 で予約済) (Sonnet)
- **Step F**: テスト追加 (`901_test_runner.js` F338-01〜F338-12) (Haiku)

注意: Step E (migration) と Step B (コード改修) は分離 PR 推奨 (MAS-336 の Step A/B 分離原則と同型)

### Section 12. 推奨実行モデル (Step ごと)
- ヘルパー関数: Sonnet (純粋関数 + 推定ロジック)
- applyBankSettlement 改修: Opus (合算 + 差額按分 + 既存挙動維持の判断)
- UI 強化: Sonnet (既存パターン応用)
- 監査ログ: Haiku
- migration: Sonnet
- テスト: Haiku

### Section 13. 変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-05-01 | v0.1 起票(main 側起票依頼を受けて MAS-338 として起草。Q1-Q5 推奨案を提示・MAS-336 の Q4 で「別仕様化」と決定された Pass 2.5 ソフト合算差額の自動記録を本仕様で扱う・既存単一マッチ挙動は完全保持) |

### Section 14. 仕様書作成プロンプト (`<details>` で本プロンプトを丸ごと格納)

## 進め方
1. まず以下の参照ファイルを Read して理解 (それぞれ読み終えたら破棄して構わない・本タスクではノート程度でOK):
   - `docs/_internal/dev_spec_prompt_template.md` (テンプレート構造を把握)
   - `docs/dev/dev_mas-077_settlement_date_sync.md` (settlement_date 同期・上流仕様)
   - `docs/dev/dev_mas-162_bank_combo_match.md` (Pass 2.5 ソフト合算・PR #465 反映済)
   - `docs/_internal/failure_patterns.md` の #35 (フィルター silent-fail)
2. `docs/dev/dev_mas-338_settlement_diff_handling.md` を Write で**1 回の出力で**作成 (約 500〜700 行)
3. テンプレートのセクション構成と本プロンプト記載のセクションリストの両方を満たすこと
4. `<details>` ブロック (Section 14) には**本プロンプト全文を丸ごと**コピーすること

## 制約
- sub workspace 制約: `400_domain/`, `500_import/`, `600_report/`, `webapp_client/src/`, `appsscript.json` は触らない (READ のみ可)
- 仕様書本体のみ書き出す。`docs/_config.json` 反映 / `id_mapping_table.csv` / `todo_master_tables.md` / `changelog.md` 更新 / 関連 spec (MAS-077/MAS-162/MAS-336) のクロスリンク追記は親が担当
- 1 つの Write tool call で全文を出力すること (500-700 行は 1 call で十分)
- 出力後はファイルパスと行数を報告するだけでよい (要約や説明は不要)