MAS-164: 銀行摘要名の自動学習
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-164 |
| カテゴリ | 外部データ取込 |
| Phase | P1.5 |
| 優先度 | ★★(Top 15 バックログ #12) |
| 所要時間 | 2時間 |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル | 500_import/502_bank_importer.js(学習関数追加), 100_config/101_sys_config.js(メニュー追加) |
| 前提案件 | MAS-145(銀行CSV取込・実装済), MAS-163(名寄せマスタ独立化・依存度低) |
目的
銀行CSV消込で MATCHED または 消込済 になった行の「銀行摘要カナ ↔ 取引先名」の対応を 12_mst_partner の「銀行摘要名」列に自動記録する。次回以降のマッチングで loadBankMemoNameMap_() が自動参照し、摘要名の手動入力なしに名寄せが機能する。初回は手動入力が必要だが、2回目以降は確定実績から自動蓄積される自己学習的な消込強化を実現する。
現在のコード
既存の名寄せ参照(502_bank_importer.js)
現在、loadBankMemoNameMap_() が 12_mst_partner の「銀行摘要名」列を読み込み、isMemoFuzzyMatch_() → resolveBankMemoToVendor_() の経路で名寄せを参照している。
// 500_import/502_bank_importer.js L527-L555
/** 銀行摘要名マップのキャッシュ { 銀行摘要名(正規化): 取引先名_正式 } */
var bankMemoNameMap_ = null;
/** MST_PART の銀行摘要名マップを構築(初回のみ) */
function loadBankMemoNameMap_() {
if (bankMemoNameMap_) return bankMemoNameMap_;
bankMemoNameMap_ = {};
try {
var ss = getWebSpreadsheet_();
var sheet = ss.getSheetByName(Utils.getSheetNameByKey('MST_PART') || '12_mst_partner');
if (!sheet) return bankMemoNameMap_;
var data = sheet.getDataRange().getValues();
var h = data[0];
var iFlag = h.indexOf('有効フラグ');
var iBankMemo = h.indexOf('銀行摘要名');
var iVendor = h.indexOf('取引先名_正式');
// ...
} catch(e) {}
return bankMemoNameMap_;
}
問題: 12_mst_partner の「銀行摘要名」列は手動入力のみで更新される。消込実績から自動的に学習する仕組みが存在しない。
12_mst_partner の関連列(MAS-154 実装済み)
12_mst_partner には以下の列が存在する(MAS-154 で追加済み):
取引先名_正式— マスタの正式取引先名銀行摘要名— 銀行CSVの摘要カナ(手動入力)有効フラグ— FALSE の行はスキップ
applyBankSettlement()(502_bank_importer.js L376-L478)
消込実行時に 33_wrk_bank の 決済ステータス を 消込済 に更新する関数。この処理の後に学習ステップを追加する。
修正方針
全体アーキテクチャ
applyBankSettlement() — 既存の消込処理
↓ 消込完了後に追加
learnBankMemoMapping_() — 新規(内部ヘルパー)
├─ 36_wrk_bank_import の処理結果=消込済 の行を走査
├─ 各行の「摘要(銀行)」と「STL取引先名」を取得
├─ 12_mst_partner の「銀行摘要名」列に未登録なら追記
└─ 学習件数をログ出力
Step 1: 学習ヘルパー関数 learnBankMemoMapping_() の追加
502_bank_importer.js の末尾(writeMatchResult_ の後)に追加する。
/**
* 消込済み行の「銀行摘要 → 取引先名」対応を 12_mst_partner に自動記録する
* @param {Sheet} importSheet 36_wrk_bank_import シート
* @param {Object} importIdx buildHeaderIndex_ で構築したインデックス
* @param {Sheet} mstSheet 12_mst_partner シート
* @param {number[][]} mstData mstSheet.getDataRange().getValues()
* @returns {number} 新規学習件数
*/
function learnBankMemoMapping_(importSheet, importIdx, mstSheet, mstData) {
var FUNC = 'learnBankMemoMapping_';
try {
var mstH = mstData[0];
var iFlag = mstH.indexOf('有効フラグ');
var iBankMemo = mstH.indexOf('銀行摘要名');
var iVendor = mstH.indexOf('取引先名_正式');
if (iBankMemo === -1 || iVendor === -1) {
Utils.logInfo(FUNC, '12_mst_partner に「銀行摘要名」列が見つかりません。学習スキップ');
return 0;
}
// 既存の銀行摘要名セットを構築(正規化済み)
var existingSet = {};
for (var m = 1; m < mstData.length; m++) {
var bm = String(mstData[m][iBankMemo] || '').trim();
if (bm) existingSet[normalizeMerchant_(bm)] = true;
}
// 36_wrk_bank_import を走査: 処理結果=消込済 かつ STL取引先名あり
var importData = importSheet.getDataRange().getValues();
var learnCount = 0;
for (var i = 1; i < importData.length; i++) {
var row = importData[i];
var result = String(row[importIdx['処理結果']] || '').trim();
if (result !== '消込済') continue;
var bankMemo = String(row[importIdx['摘要']] || '').trim();
var vendorName = String(row[importIdx['STL取引先名']] || '').trim();
if (!bankMemo || !vendorName) continue;
var normalizedMemo = normalizeMerchant_(bankMemo);
if (existingSet[normalizedMemo]) continue; // 既登録はスキップ
// 12_mst_partner の vendorName 行を検索し「銀行摘要名」に追記
var targetRow = -1;
for (var m2 = 1; m2 < mstData.length; m2++) {
if (iFlag !== -1 && (mstData[m2][iFlag] === false || String(mstData[m2][iFlag]).toUpperCase() === 'FALSE')) continue;
var mstVendor = String(mstData[m2][iVendor] || '').trim();
if (mstVendor === vendorName) { targetRow = m2 + 1; break; }
}
if (targetRow > 0) {
mstSheet.getRange(targetRow, iBankMemo + 1).setValue(bankMemo);
existingSet[normalizedMemo] = true; // 重複防止
learnCount++;
Utils.logInfo(FUNC, '学習: ' + bankMemo + ' → ' + vendorName);
}
}
return learnCount;
} catch (e) {
Utils.logError(FUNC, e);
return 0;
}
}
Step 2: applyBankSettlement() に学習呼び出しを追加
既存の applyBankSettlement() 内のアラートダイアログ直前に以下を挿入する(L463〜L470 付近)。
既存コードの挿入位置(if (processCount === 0) の直前):
// --- 消込実績から銀行摘要名を自動学習 ---
if (processCount > 0) {
var mstSheet_ = ss.getSheetByName(Utils.getSheetNameByKey('MST_PART') || '12_mst_partner');
if (mstSheet_) {
var mstData_ = mstSheet_.getDataRange().getValues();
var learnCount = learnBankMemoMapping_(importSheet, importIdx, mstSheet_, mstData_);
if (learnCount > 0) {
bankMemoNameMap_ = null; // キャッシュをリセット(次回マッチングで再構築)
Utils.logInfo('applyBankSettlement', '名寄せマスタに ' + learnCount + ' 件を自動学習しました。');
}
}
}
Step 3: 手動学習メニューの追加(任意)
運用安定後に蓄積済み実績から学習をやり直せるよう、独立したメニュー関数を追加する。
/**
* メニュー: 🏦 銀行摘要名 手動学習(消込済実績から再学習)
* 36_wrk_bank_import の消込済行から名寄せマスタへ一括登録する。
* applyBankSettlement と同じ学習ロジックを手動起動できる。
*/
function learnBankMemoMappingManual() {
var FUNC = 'learnBankMemoMappingManual';
try {
var ss = getWebSpreadsheet_();
var ui = SpreadsheetApp.getUi();
var importSheet = Utils.getSheetByKey('WRK_BANK_IMPORT', '36_wrk_bank_import');
if (!importSheet) return ui.alert('36_wrk_bank_import が見つかりません。');
var importIdx = buildHeaderIndex_(importSheet.getDataRange().getValues()[0]);
var mstSheet = ss.getSheetByName(Utils.getSheetNameByKey('MST_PART') || '12_mst_partner');
if (!mstSheet) return ui.alert('12_mst_partner が見つかりません。');
var mstData = mstSheet.getDataRange().getValues();
var learnCount = learnBankMemoMapping_(importSheet, importIdx, mstSheet, mstData);
bankMemoNameMap_ = null;
ui.alert('🏦 名寄せ学習 結果', learnCount + ' 件の摘要名を学習しました。', ui.ButtonSet.OK);
Utils.logInfo(FUNC, '手動学習完了: ' + learnCount + ' 件');
} catch (e) {
Utils.logError(FUNC, e);
SpreadsheetApp.getUi().alert('エラー: ' + e.message);
}
}
Step 4: メニュー登録(101_sys_config.js)
onOpen_() の「🔍 消込・マッチング」メニューの銀行CSVセクションに追加:
.addItem('🏦 銀行摘要名 手動学習(消込済実績から)', 'learnBankMemoMappingManual')
影響範囲
| 変更対象 | 変更内容 | 変更量 |
|---|---|---|
500_import/502_bank_importer.js | learnBankMemoMapping_() 追加 + applyBankSettlement() に呼び出し追加 + learnBankMemoMappingManual() 追加 | ~80行 |
100_config/101_sys_config.js | メニュー項目1件追加 | ~1行 |
12_mst_partnerの「銀行摘要名」列に書き込みが増える(既登録のもの以外)applyBankSettlement()の実行時間が若干増加するが、1回の消込バッチで数件程度のためパフォーマンス影響は無視できるloadBankMemoNameMap_()のキャッシュ(bankMemoNameMap_)が学習後にリセットされ、次回マッチングで再構築される
注意事項
1取引先に複数の銀行摘要名: 12_mst_partner の「銀行摘要名」列は現状1列のみ。1取引先に複数のカナ表記がある場合(振込名義変更、法人名/屋号の使い分け等)は上書きではなく 先着1件のみ登録。複数対応は MAS-163(名寄せマスタ独立化)で別タブに移行後に検討する
normalizeMerchant_()への依存: 正規化関数は501_cc_importer.jsで定義されており、GASグローバルスコープで参照可能。再定義しないexistingSetの初期化スコープ:learnBankMemoMapping_()内で新規登録した摘要名をexistingSetに追加することで、同一バッチ内での重複登録を防止する有効フラグ=FALSEの行: 無効化された取引先マスタに対しては銀行摘要名を追記しない(検索時にiFlag判定でスキップ)STL取引先名の信頼性:
applyBankSettlement()でマッチしたSTLの取引先名を学習に使用する。SUGGEST(候補提案)でユーザーが確認FLGを立てた場合も学習対象になるため、誤ったSUGGESTを承認した場合に誤学習が起きる。削除は12_mst_partnerで手動修正部分一致の摘要名: 銀行摘要は「フクイギンコウ カラ テスト」のような長い文字列になる場合がある。
existingSetでの完全一致チェックに加え、loadBankMemoNameMap_()での部分一致参照により多少の表記揺れは吸収されるgetWebSpreadsheet_()の使用:applyBankSettlement()は既にgetWebSpreadsheet_()でスプレッドシートを取得済み。学習処理ではssを再利用する(変数スコープに注意)
エッジケース
| 条件 | 処理 | 理由 |
|---|---|---|
銀行摘要名 列が12_mst_partnerに存在しない | 学習をスキップしてログ出力。消込処理自体は続行 | MAS-154 が未実装の環境での安全な退行動作 |
| STL取引先名が12_mst_partnerに登録されていない | その摘要名の学習をスキップ | マスタ未登録の取引先に対して孤立した摘要名レコードを作らない |
| 同一摘要名が複数の消込済行に存在する(同一バッチ内) | existingSet チェックにより2件目以降をスキップ | 同一バッチ内での重複登録防止 |
| 同一摘要名が既に12_mst_partnerに登録済み | スキップ(上書きしない) | 手動で修正した摘要名を自動学習で上書きしない。既登録値を優先する |
| 銀行摘要が空文字列 | スキップ | 空の摘要名を学習しない |
STL取引先名 が空文字列 | スキップ | 対象外明細(金額ゼロ等)ではSTL取引先名が未セットの場合がある |
処理結果 が 消込済 以外(MATCHED, SUGGEST等) | 学習対象外 | applyBankSettlement() 実行後に処理結果が 消込済 に更新された行のみを学習対象にする |
| 1取引先に複数の銀行摘要名(振込名義変更等) | 先着1件のみ登録。2件目以降は existingSet でスキップ | MAS-163(名寄せマスタ独立化)が前提となる複数対応は現スコープ外 |
| SUGGEST で誤ってマッチした行を承認した場合 | 誤った摘要名が学習される | ユーザーが確認FLGを立てた場合は信頼ある操作として学習。誤り修正は12_mst_partnerを手動編集 |
| 処理件数が0件(消込対象なし) | 学習処理自体を実行しない | if (processCount > 0) の分岐で制御 |
実データ検証
| 確認項目 | 確認方法 | 理由 |
|---|---|---|
| 12_mst_partner に「銀行摘要名」列が存在するか | シートのヘッダー行をMCPまたは目視で確認 | MAS-154 が実装済みでないと列が存在しない。列がなければ学習が全スキップされる |
| 12_mst_partner の「取引先名_正式」列の値が33_wrk_bankの「取引先名」列と一致しているか | MCP で双方のユニーク値を比較 | マスタの取引先名と不一致だと学習先行を特定できない |
| 36_wrk_bank_import の「摘要」列ヘッダー名 | importIdx に 摘要 が存在するか確認 | importIdx['摘要'] で取得しているが、実際の列名が異なる場合は undefined になる |
| 36_wrk_bank_import の「STL取引先名」列が存在するか | importIdx['STL取引先名'] を確認 | applyBankSettlement 後に値がセットされているはず |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| dev_mas-145_bank_csv_import.md | applyBankSettlement() の消込実行パターン。MAS-164の追加ロジックはこの関数の末尾に追加 |
| dev_mas-162_bank_combo_match.md | 合算マッチの実装。MAS-164はMATCHED/SUGGEST/SUGGEST_COMBOいずれも学習対象 |
| dev_mas-154_partner_logical_abbr.md | 12_mst_partnerへの「銀行摘要名」列追加(MAS-164の前提となる列) |
| CLAUDE.md | コーディング規約(列参照はヘッダー名ベース、有効フラグFALSEのスキップ) |
人間が検討すべき事項
| # | 項目 | 詳細 |
|---|---|---|
| 1 | MAS-163(名寄せマスタ独立化)との実行順序 | MAS-164 は現状 12_mst_partner の「銀行摘要名」列に直接書き込む。MAS-163 が実装されると独立タブ(例: 17_mst_bank_alias)に移行するため、MAS-164 の学習先も変更が必要。MAS-163 実装後は MAS-164 の学習対象列・シートを変更するリファクタリングが必要。開発順序として MAS-164 → MAS-163 の順を推奨(MAS-163 未実装でも MAS-164 は動作可能) |
| 2 | 誤学習の修正手順をユーザーに周知 | 誤ったSUGGESTを承認すると誤った摘要名が学習される。修正は 12_mst_partner の「銀行摘要名」列を直接編集し、bankMemoNameMap_ キャッシュは次回 importBankStatement() 実行時に自動リセット。運用ドキュメントへの記載が必要 |
| 3 | 1取引先・複数摘要名への対応 | 振込名義が変更された場合(個人名→法人名等)、既登録の摘要名が残り新摘要名が登録されない。現状は先着1件のみ。MAS-163 実装まで暫定対処として、ユーザーが12_mst_partnerを手動追記する運用とする |
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-164「銀行摘要名の自動学習」を実装してください。
## 実行前タスク
以下のファイルを Read してください:
1. `500_import/502_bank_importer.js` — 実装対象ファイル。以下を重点確認:
- `applyBankSettlement()` 全体(L376〜L478): 消込実行ロジックと変数 `ss`, `importSheet`, `importIdx`, `processCount` のスコープ
- `loadBankMemoNameMap_()` / `resolveBankMemoToVendor_()` (L527〜L568): 既存の名寄せ参照ロジック
- `bankMemoNameMap_` グローバル変数(L528): キャッシュのリセット方法
2. `100_config/101_sys_config.js` — メニュー追加箇所を確認(「🔍 消込・マッチング」メニュー内の銀行CSVセクション)
3. `docs/dev/dev_mas-164_bank_description_learning.md` — 本仕様書
## 修正対象ファイル
- `500_import/502_bank_importer.js` — 関数追加 + `applyBankSettlement()` への呼び出し追加
- `100_config/101_sys_config.js` — メニュー項目1件追加
## 実装内容
### A: `learnBankMemoMapping_()` ヘルパー関数の追加
`502_bank_importer.js` の末尾(`writeMatchResult_` 関数の後)に、仕様書「修正方針 Step 1」に記載のコードを追加する。
主要ロジック:
- 12_mst_partner のヘッダーから `有効フラグ`/`銀行摘要名`/`取引先名_正式` 列インデックスを取得
- `銀行摘要名` 列が存在しない場合は学習スキップしてログ出力(`Utils.logInfo`)
- 既登録の銀行摘要名セット(`existingSet`)を `normalizeMerchant_()` 正規化で構築
- 36_wrk_bank_import を走査し `処理結果=消込済` かつ `摘要` / `STL取引先名` が非空の行を抽出
- `existingSet` に未登録の摘要名について、`取引先名_正式` が一致する12_mst_partnerの行を検索し「銀行摘要名」列に書き込む
- 登録した摘要名を `existingSet` に追加して同バッチ内の重複を防止
### B: `applyBankSettlement()` に学習呼び出しを追加
`if (processCount === 0)` の直前(既存のアラート処理の直前)に仕様書「修正方針 Step 2」のコードを挿入する。
注意: `ss` は `applyBankSettlement()` の冒頭で取得済みの変数。新たに取得しない。
### C: `learnBankMemoMappingManual()` 手動学習関数の追加
仕様書「修正方針 Step 3」のコードを `learnBankMemoMapping_()` の直後に追加する。
### D: メニュー登録
`101_sys_config.js` の「🔍 消込・マッチング」メニューの銀行CSVセクション(「🏦 銀行CSV消込実行」の直後)に以下を追加:
`.addItem('🏦 銀行摘要名 手動学習(消込済実績から)', 'learnBankMemoMappingManual')`
## 制約
- `501_cc_importer.js` は変更しない
- `normalizeMerchant_()` は `501_cc_importer.js` で定義済みのため再定義しない
- 12_mst_partner の既存摘要名は上書きしない(先着1件のみ登録)
- 12_mst_partner の `有効フラグ=FALSE` の行には追記しない
## 動作確認
`npm run push:dev` 後:
1. 36_wrk_bank_import に銀行CSVデータを貼り付け、`importBankStatement()` を実行してマッチング確認
2. MATCHED行の確認FLGがTRUE、SUGGEST行でも承認したいものをTRUEに変更
3. メニュー「🔍 消込・マッチング」→「🏦 銀行CSV消込実行」を実行
4. **検証**: 消込完了ダイアログが表示されること
5. **検証**: 12_mst_partner の「銀行摘要名」列に、消込済み行の摘要が自動登録されていること
6. **検証**: 既に銀行摘要名が登録済みの取引先については上書きされないこと
7. メニュー「🔍 消込・マッチング」→「🏦 銀行摘要名 手動学習」を実行
8. **検証**: 「X 件の摘要名を学習しました」ダイアログが表示されること(2回目実行では0件)
9. 再度 `importBankStatement()` を実行し、今回学習した摘要名でMATCHEDが増加することを確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| ファイル読み込み・構造理解 | あり | applyBankSettlement の変数スコープ・挿入位置の確認 |
| learnBankMemoMapping_ 実装 | なし | 仕様書でロジック定義済み |
| メニュー追加 | なし | 定型作業 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本ドキュメント) | Claude Sonnet 4.6 | MAS-145/MAS-162 で既に確立した 502_bank_importer.js のパターンを読み解き、挿入位置・変数スコープを特定する中程度の判断が必要 |
| 実装 | Claude Haiku 4.5 | 仕様書でコードが完全定義済み。applyBankSettlement の末尾への挿入と新関数追加のみで判断要素が少ない |
| 動作確認 | ユーザー手動 | 消込実行 → 12_mst_partner 確認の手動操作が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成 |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
作業ディレクトリ: /workspaces/bizlp-gas-accounting
現在のブランチ: docs/add-spec-i-22 (main から分岐済・クリーン状態)
以下の指示書の内容を厳密に実行してください。
## 実行スコープ
- Phase 1(Read による関連ファイル調査): 完全実行
- Phase 2(仕様書の分割作成 Step 2-1〜2-4): 完全実行
- Phase 3: Step 1(docs/_config.json 追記)と Step 2(docs/_internal/changelog.md 追記)のみ実行
案件: I-22「銀行摘要名の自動学習」
## Phase 1 調査ファイル一覧
- docs/_internal/TODO_future.md(I-22 の案件定義)
- 500_import/502_bank_importer.js(実装対象・既存の名寄せ参照ロジック確認)
- docs/dev/dev_mas-145_bank_csv_import.md(I-01 仕様書・消込パターン参照)
- docs/dev/dev_mas-162_bank_combo_match.md(I-20 仕様書・合算マッチ参照)
- docs/_internal/dev_spec_prompt_template.md(仕様書テンプレート)
- docs/_config.json(nav 登録先確認)
- docs/_internal/changelog.md(先頭追記先確認)
## Phase 2 仕様書出力先
docs/dev/dev_mas-164_bank_description_learning.md
## ゴール
- docs/dev/dev_mas-164_bank_description_learning.md が作成されていること
- docs/_config.json の §E.6 セクションに I-22 のエントリが追加されていること
- docs/_internal/changelog.md の先頭に I-22 のエントリが追加されていること