MAS-162: 銀行CSV合算マッチング(複数STL→1明細)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-162 |
| カテゴリ | 外部データ取込 |
| Phase | P1.5 |
| 優先度 | ★★ |
| 所要時間 | 2時間 |
| 対象ファイル | 500_import/502_bank_importer.js |
| 前提案件 | MAS-145(銀行CSV取込), MAS-154(名寄せ辞書) |
目的
まとめ振込(月額給与3ヶ月分、社保2件分等)で1つの銀行明細に複数STLが対応するケースを自動候補提案する。
実装済みの設計判断(2026-04-16 実装時の知見)
マッチング優先順位
| Pass | 条件 | 結果 | 信頼度 |
|---|---|---|---|
| 1 | 金額完全一致 + 日付±3日 + 入出金区分一致 | MATCHED | 最高 |
| 2 | 同一取引先の複数STL合算が金額完全一致 (±1 円厳密) | SUGGEST_COMBO | 高 |
| 2.5 | 同一取引先の複数STL合算が ±max(1,000 円, 銀行金額×1%) で一致 (ソフト合算) | SUGGEST_COMBO_SOFT | 中 |
| 3 | 金額±10% + 摘要キーワード一致 | SUGGEST | 中 |
重要: Pass 2(合算厳密)→ Pass 2.5(合算ソフト)→ Pass 3(単一候補)の順で実行する。理由: 合算は金額完全一致のため信頼度が高い。Pass 3 を先にすると、合算対象のSTLが個別に消費されてしまう。Pass 2.5 は Pass 2 で不成立だった場合のフォールバックで、口座振替割引等の少額差を救済する (PR #465 で追加)。
Pass 2.5 ソフト合算 (PR #465 追加)
契機
社会保険料 (厚年金 + 健保) の口座振替で、銀行引落額 149,950 円 と STL 合計 150,950 円 (74,575 + 76,375) が ちょうど 1,000 円 ずれて合算候補に提案されない事象を観測。口座振替割引と推測。Pass 2 (±1 円厳密) では救済不可だったため、Pass 2.5 として実装。
実装
findComboGroup_(candidates, txAmt, txType, txYm, tolerance) に許容誤差 tolerance (円) を引数化リファクタ。
| Pass | tolerance 引数 | 用途 |
|---|---|---|
| Pass 2 | 1 | 厳密合算(±1 円・端数対応のみ) |
| Pass 2.5 | Math.max(1000, txAmt * 0.01) | ソフト合算(±1,000 円 or ±1% の大きい方) |
UI 表示
| 項目 | Pass 2 (厳密) | Pass 2.5 (ソフト) |
|---|---|---|
| ラベル | 合算候補 | 合算候補(差額+1,000円) 等で差額明示 |
| 背景色 | 緑 #D5E8D4 | 黄色 #FFF2CC (視覚区別) |
| 確認FLG | false (人間確認必須) | false (人間確認必須・必ず差額の妥当性を判断する) |
注意事項
- 必ず Pass 2 の後に実行する: 厳密一致が優先。±1 円で合致するケースを Pass 2.5 が先取りしてはいけない。
- 差額の妥当性は人間判断: 1,000 円の差は口座振替割引で説明可能だが、5,000 円の差は調査要 (摘要欄不一致・別案件混入の可能性)。
- Pass 3 (単一候補) との優先順位: Pass 2.5 が候補を出した場合は Pass 3 をスキップ (
combo変数にヒット結果が残るため)。
取引先別グルーピング
全STLの合計ではなく、同一取引先内でグルーピングしてから合計を照合する。
理由: 異なる取引先のSTLが偶然合計一致する誤マッチを防止。
部分集合の探索方式
同一取引先の全件合計 ≠ 銀行金額のケースが頻出する(例: 4ヶ月分のSTLがあるが振込は3ヶ月分)。以下の3方式で順に探索:
- 全件合計一致: 同一取引先の全STL合計が銀行金額と一致
- 貪欲法: 日付昇順にSTLを1件ずつ加算し、ちょうど一致したら採用
- 同一金額N件: 同一金額のSTLが N件あり、
N × 単価 = 銀行金額で件数マッチ
STLロック
合算マッチ成功時に candidates[ci].matched = true でロックする。これがないと次の銀行明細で同じSTLが再利用される。
処理順序のソート
銀行明細を勘定日の昇順で処理する。行の並び順ではなく内部的にソートし、書き込みは元の行番号に行う。
理由: 古い銀行明細が古いSTLに優先マッチし、日付的な整合性を維持。
Date オブジェクトのソート注意
STLの dueDate が Date オブジェクトの場合、String() でタイムゾーン付き文字列になりソート順が壊れる。Utils.parseDateToYm() で YYYY-MM 形式に統一してからソートすること。
エッジケース
| 条件 | 動作 | 理由 |
|---|---|---|
| 同一取引先の全件合計 ≠ 銀行金額 | 貪欲法 → 同一金額N件で部分集合を探索 | 4ヶ月分のSTLがあるが3ヶ月分の振込 |
| 異なる金額のSTLの合算(社保控除74,575 + 法定福利費76,375 = 150,950) | 貪欲法で日付昇順に加算して一致 | 同一金額N件では検出できない |
| 同じ金額・同じ取引先の明細が2行ある(2月分と3月分の社保) | 1行目の処理でSTLがロックされ、2行目は残りのSTLからマッチ | matched=true ロックで二重消費防止 |
| 36タブの行順序が勘定日と異なる | 内部ソートにより常に古い明細が先に処理される | 処理順序のソートで整合性確保 |
| 社会保険料の口座振替割引 (銀行引落 149,950 円 vs STL 合計 150,950 円・差 1,000 円) | Pass 2 (±1 円) では不成立 → Pass 2.5 (±max(1000, txAmt × 1%) = ±1,500 円) で SUGGEST_COMBO_SOFT 候補化・ラベル 合算候補(差額+1,000円)・黄色背景 | 口座振替割引等の少額差を救済 (PR #465)。差額の妥当性は人間が判断 (確認FLG=false) |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| MAS-145 銀行CSV取込 | 基盤の2段階マッチング |
| MAS-154 取引先略称 | 名寄せ連携(銀行摘要名列) |
| F.4 失敗パターン一覧 | 合算マッチ関連の失敗 #13-#17 / フィルター silent-fail #35 / 単一マッチ前提ロジック未追従 #36 |
| BUG_tracking.md | MAS-301 (Pass 2.5 追加経緯) / MAS-302 (applyBankSettlement silent-fail) / MAS-303 (合算時差額未記録・MAS-338 で対応) |
| MAS-338 STL 消込時 差額自動処理機能 | 下流の派生案件。Pass 2.5 ソフト合算ヒット時の差額 (±1,000 円等) を 差額(手数料等) 列に按分記録し、自動推定科目 (雑収入 / 支払手数料 等) を提案する仕組み |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-05-01 | Pass 2.5 ソフト合算追記 (PR #465 反映): findComboGroup_ を tolerance 引数化リファクタし、Pass 2 (±1 円厳密) で不成立時に Pass 2.5 (±max(1000, txAmt × 1%)) で再試行する仕組みを追加。社会保険料の口座振替割引 (1,000 円差) のケースを救済。ラベル 合算候補(差額+1,000円) + 黄色背景 #FFF2CC で人間確認を要求 (Pass 2 厳密合算 = 緑 #D5E8D4 と視覚区別)。エッジケース表に契機を追記。MAS-301 (BUG_tracking) として記録。 |
| 2026-04-16 | 初版作成(実装完了後に知見を文書化) |