士業・専門業務

経理の月次決算・残高照合をClaude Codeで仕組み化する実装パターン

銀行残高照合・売掛買掛の消込・補助簿と総勘定元帳の突合をClaude Codeで正規化する実装パターン。名寄せ・まとめ入金・時間差の壁をどう越えるか。

経理の月次決算・残高照合をClaude Codeで仕組み化する実装パターン

結論:月次決算の残高照合・消込は、銀行明細・入金明細・請求一覧を「取引先コード+請求書番号」を軸に正規化し、一致しない分だけを金額・日付窓・名寄せスコアの多段マッチングに落とす決定的ロジックに固定すれば、Claude Codeで正規化から消込、差異レポート作成まで仕組み化できる。

  • 要点1:消込のキーは「請求書番号」を最優先にし、それが取れない入金だけを金額+日付窓+名寄せスコアの多段マッチングに回す
  • 要点2:一致・不一致の最終判定はコード側の決定的ロジックに固定し、差異理由の要約と仕訳の最終判断はAIに渡さない
  • 要点3:銀行から受け取るCSV・PDF明細などの電子取引データは、電子帳簿保存法により電子データのまま保存する義務がある点も踏まえて設計する

対象読者:経理部門とAI活用を進める開発者・PM・経営企画

今日やること:直近1ヶ月分の銀行明細CSVと売掛金・買掛金の請求一覧を1つのフォルダに集め、取引先名の表記がどれだけ揺れているかをまず目視で確認する

「入金はあるのに、どの請求書に対応するのか分からないんです」。中堅企業(月間の入金件数200件超)の経理担当者を支援していたとき、そう相談されたことがあります。

振込人名義が請求書の取引先名と違う(子会社名義・個人名義で振り込まれる)、複数の請求書分をまとめて1回で入金される、振込手数料が差し引かれて数百円だけ端数が合わない——といった「よくある消込の壁」が積み重なり、月末になると経理担当者が半日がかりで銀行明細と請求書リストをExcelで突き合わせていました。この作業を、Claude Codeでどこまで仕組み化できるか。この記事では、その実装パターンを解説します。

事例区分:実装パターン解説
本記事は複数の現場でよく見られる月次決算の残高照合・消込の課題を一般化した実装パターン解説です。登場する数値は特定の実在案件を示すものではなく、実装を検討する際の試算・想定値として記載しています。

何を照合するのか——残高照合と消込の3レイヤー

経理の現場で「月次決算の締め作業」と呼ばれる工程のうち、この記事が対象にするのは主に次の3つのレイヤーです。

  • 銀行残高照合 — 現金預金の帳簿残高と、銀行の残高証明書・通帳残高を照合する作業。差異の主因は未取立小切手や銀行未記帳(タイムラグ)
  • 売掛金・買掛金の消込 — 入金明細・支払明細と、請求書一覧(未収・未払リスト)を突き合わせ、どの請求に対応する入出金かを特定する作業
  • 補助簿と総勘定元帳の突合 — 得意先元帳・仕入先元帳(補助簿)の残高合計が、総勘定元帳の売掛金・買掛金勘定の残高と一致しているかを確認する作業

この3つは別々の作業に見えて、実は「決定的にキーが取れるデータはキーで、取れないデータは近似マッチングで」という同じ設計思想で扱えます。以降はこの中でも特に手間がかかる、銀行明細と請求一覧の消込を中心に解説します。

現場で実際に詰まる5つの壁

消込を「実装したのに毎回差異だらけになる」原因は、たいてい入出金データの表記ゆれと業務ロジックの解像度不足にあります。よく詰まるポイントを5つに整理しました。

  1. 振込人名義のゆれ(名寄せ問題) — 請求書上の取引先名は「株式会社◯◯」でも、振込は子会社名義・個人名義・カタカナ表記でされることがある。表記ゆれを吸収する仕組みがないと、正しい入金でも「該当なし」として溢れる。
  2. まとめ入金・分割入金 — 複数の請求書分をまとめて1回で振り込まれる、逆に1請求書分が複数回に分けて振り込まれるケース。1対1でしか突合できないロジックだと、必ず差異として検出されてしまう。
  3. 振込手数料の差引 — 振込手数料をどちらが負担するかは契約次第で、差し引かれた金額が入金されることがある。数百円単位の差額を毎回「不一致」として検出すると、本当に見るべき差異が埋もれる。
  4. 銀行未達・計上タイミングのズレ — 月末締め後に着金する入金や未取立小切手など、帳簿と銀行側で計上タイミングがずれるケース。
  5. 前受金・仮払金など消込対象外データの混入 — 消込ロジックにそのまま含めると、本来対象外のデータを誤ってマッチさせてしまう。

Claude Codeでどう組むか——全体のアーキテクチャ

この手の消込作業は「表記ゆれのある非構造化に近いテキスト(振込人名義・摘要欄)を正規化する部分」と「正規化されたデータ同士を決定的ロジックで照合する部分」に分けて考えると設計しやすくなります。前者はClaude Codeが得意な領域、後者は通常のPythonコードに任せるのが安全です。

  1. 抽出 — 銀行明細CSV・会計システムの補助簿エクスポート・請求書一覧を共通スキーマ(取引先コード/請求書番号/金額/日付/摘要)に正規化する
  2. 消込 — 請求書番号キーを最優先に、なければ金額+日付窓+名寄せスコアの多段マッチングで照合する(ここはコードで固定ロジックにする)
  3. 要約 — 消込できなかった入金・請求書について、想定される原因を自然文で要約する
  4. レポート — 差異一覧をCSV/HTMLで出力し、経理担当が確認しやすい形に整形する
  5. 記録 — 誰が・いつ・どの差異を確認して承認したかのログを残す

抽出・要約はClaude Codeに任せますが、「一致しているかどうか」の最終判定は必ずコード側の決定的なロジックに固定します。これは後述する「AIに任せてはいけないこと」とも関係する、この実装の一番の設計判断です。

実装コード例1:銀行明細・請求一覧の正規化

まず、銀行明細の振込人名義を得意先マスタと突き合わせ、表記ゆれを吸収します。権限は必要最小限に絞り、読み取り・書き込みだけを許可します。

bank_statements/2026-07.csv   # 銀行明細(残高照会サービスからのCSVエクスポート)
receivables/2026-07.csv       # 売掛金・請求書一覧(会計システムからのエクスポート)
customer_master.csv           # 得意先マスタ(正式名称・振込名義の別名リストを含む)
data/normalized/              # 正規化後の出力先
data/output/                  # 差異レポートの出力先
claude -p "bank_statements/2026-07.csv の摘要欄(振込人名義)を1行ずつ読み、
customer_master.csv の正式名称・別名リストと照合して、最も一致する得意先コードを
推定してください。表記ゆれが大きい、または候補が複数ある行は確定させず、
candidates列に候補を最大3件まで書き出してください。
出力は data/normalized/bank_matched.csv に1明細1行で書き出してください。
不足している情報があれば、最初に質問してから作業を開始してください。" \
  --allowedTools "Read" "Write" \
  --model sonnet \
  --output-format json

件数の多い月次バッチを毎回流すだけなら、名寄せタスク自体はそこまで複雑な推論を要求しないため、コスト重視で軽量モデルに切り替える選択肢もあります(表記ゆれのパターンが少ない取引先だけを対象にする、など条件付きで)。

claude -p "同じ指示で、7月最終週分の明細だけ処理してください" \
  --allowedTools "Read" "Write" \
  --model haiku

実装コード例2:消込ロジックと差異レポート

正規化が終わったら、消込そのものは通常のPythonコードで決定的に行います。ここをAIの自然言語判断に任せると、実行のたびに許容誤差やマッチング順序がぶれるリスクがあるためです。まとめ入金(複数請求書分の合算入金)は、対象を同一取引先・直近の請求書に絞ったうえで、金額の組み合わせを探索して検出します。

# scripts/match_receivables.py
import csv
from decimal import Decimal
from datetime import date
from itertools import combinations
from pathlib import Path

DATE_WINDOW_DAYS = 5        # 入金日と請求書期日のずれとして許容する日数
FEE_TOLERANCE_YEN = Decimal("500")  # 振込手数料差引として許容する金額差
MAX_COMBO = 3                # まとめ入金として組み合わせを試す請求書の最大件数


def normalize_amount(raw: str) -> Decimal:
    return Decimal(raw.replace(",", "").replace("¥", "").strip())


def load_rows(path: Path) -> list[dict]:
    with path.open(encoding="utf-8") as f:
        return list(csv.DictReader(f))


def match_by_invoice_no(payment: dict, invoices: list[dict]):
    """摘要欄に請求書番号が含まれる場合は最優先でマッチさせる"""
    memo = payment.get("memo", "")
    for inv in invoices:
        if inv["invoice_no"] and inv["invoice_no"] in memo:
            return inv
    return None


def match_by_amount_and_customer(payment: dict, invoices: list[dict]):
    """得意先コード+金額一致(手数料差引許容)+日付窓での1対1マッチ"""
    pay_date = date.fromisoformat(payment["date"])
    pay_amount = normalize_amount(payment["amount"])
    for inv in invoices:
        if inv["customer_code"] != payment["customer_code"]:
            continue
        inv_date = date.fromisoformat(inv["due_date"])
        if abs((pay_date - inv_date).days) > DATE_WINDOW_DAYS:
            continue
        inv_amount = normalize_amount(inv["amount"])
        if abs(pay_amount - inv_amount) <= FEE_TOLERANCE_YEN:
            return inv
    return None


def match_bundled_payment(payment: dict, invoices: list[dict]):
    """同一取引先の複数請求書を対象に、金額の組み合わせでまとめ入金を検出する"""
    pay_amount = normalize_amount(payment["amount"])
    candidates = [inv for inv in invoices if inv["customer_code"] == payment["customer_code"]]
    for r in range(2, MAX_COMBO + 1):
        for combo in combinations(candidates, r):
            total = sum(normalize_amount(inv["amount"]) for inv in combo)
            if abs(total - pay_amount) <= FEE_TOLERANCE_YEN:
                return list(combo)
    return None


def reconcile(payments: list[dict], invoices: list[dict]) -> list[dict]:
    results = []
    remaining = list(invoices)
    for payment in payments:
        matched = match_by_invoice_no(payment, remaining)
        method = "invoice_no"
        bundle = None
        if matched is None:
            matched = match_by_amount_and_customer(payment, remaining)
            method = "amount_customer_window"
        if matched is None:
            bundle = match_bundled_payment(payment, remaining)
            method = "bundled_combination"

        if matched is not None:
            remaining.remove(matched)
            results.append({**payment, "status": "matched", "method": method,
                             "matched_invoice_no": matched["invoice_no"]})
        elif bundle is not None:
            for inv in bundle:
                remaining.remove(inv)
            results.append({**payment, "status": "matched_bundle", "method": method,
                             "matched_invoice_no": ",".join(i["invoice_no"] for i in bundle)})
        else:
            results.append({**payment, "status": "review_required", "method": "none",
                             "matched_invoice_no": ""})

    for inv in remaining:
        results.append({"date": inv["due_date"], "customer_code": inv["customer_code"],
                         "amount": inv["amount"], "status": "invoice_unpaid",
                         "method": "none", "matched_invoice_no": inv["invoice_no"]})
    return results


def write_report(results: list[dict], out_path: Path) -> None:
    review_rows = [r for r in results if r["status"] in ("review_required", "invoice_unpaid")]
    with out_path.open("w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(results[0].keys()))
        writer.writeheader()
        writer.writerows(review_rows)
    print(f"要確認: {len(review_rows)}件 / 全{len(results)}件 → {out_path}")


if __name__ == "__main__":
    payments = load_rows(Path("data/normalized/bank_matched.csv"))
    invoices = load_rows(Path("data/normalized/receivables.csv"))
    results = reconcile(payments, invoices)
    write_report(results, Path("data/output/reconciliation_report.csv"))

組み合わせ探索(まとめ入金の検出)は範囲を絞らないと計算量・誤マッチの両面で事故ります。同一取引先・直近の請求書に限定し、組み合わせ件数の上限(MAX_COMBO)を明示しているのはそのためです。差異が出た明細だけを対象に、Claude Codeで「想定される原因」を自然文で要約させると、経理担当者がレポートを読む負担が下がります。

claude -p "data/output/reconciliation_report.csv を読み、status が
review_required の行について、想定される原因(振込手数料の差引/端数計上/
取引先名の表記ゆれ/入金遅延/その他)を1行ずつ推測してnotes列に追記してください。
断定はせず『〜の可能性があります』という表現にとどめてください。
判定や承認はしないでください。" \
  --allowedTools "Read" "Edit" \
  --model sonnet

似た構造の課題として、仕訳異常検知をClaude Codeで実装した記事でも「差異をどう仕分けるか」の設計を扱っています。消込と仕訳異常検知は、どちらも「機械的に流してよい差異」と「人が見るべき差異」の線引きが実装の核になる点で共通しています。

自動消込してよい差異、人が見るべき差異

すべての差異を人が確認していては仕組み化した意味がありません。逆に、すべてを自動消込すると誤消込のもとになります。線引きの例を示します。

差異の種類 目安 扱い
振込手数料の差引 ±500円以内で、手数料差引と機械的に説明できる場合 自動消込可
まとめ入金 同一取引先の請求書の組み合わせ合計が入金額と一致 自動消込可(組み合わせ探索ロジックで吸収)
取引先名の表記ゆれ 得意先マスタの別名リストと高い一致度 自動消込可(別名リストを継続的にメンテすることが前提)
振込人名義が別法人・個人 得意先マスタに登録のない名義 必ず人が確認(親子会社間の立替・誤入金の可能性)
入金額と請求額の実質的な差 手数料では説明できない金額差 必ず人が確認(値引き・返金・二重請求の可能性)
銀行未達・計上タイミングのズレ 締め日をまたぐ入出金 自動検知してアラート、翌月への繰越として記録

電子取引データの保存にも注意

銀行明細をCSVでダウンロードする、あるいはネットバンキング上のPDF明細を受け取るといった形は、電子帳簿保存法における「電子取引」に該当します。国税庁の電子帳簿等保存制度特設サイトによると、取引に関する書類に通常記載される情報(取引情報)を含む電子データをやり取りした場合、当該データの保存義務や保存方法などが同法により定められており、所得税法・法人税法上の保存義務者は「電子取引」の要件を確認するよう案内されています(国税庁 電子帳簿等保存制度特設サイト)。消込の仕組みを設計する際は、正規化前の元データ(銀行明細CSV・PDF)をどこにどう保存するかも合わせて決めておくと、後から監査対応で困りません。具体的な保存要件は制度改正が続いている領域なので、最新の要件は国税庁の該当ページ(電子取引関係)で確認してください。

AIに任せてはいけないこと

この実装で一番大事なのは、Claude Codeに何をさせないかを決めることです。

  • 最終承認 — 消込結果・差異レポートを人が確認したうえで会計システムに反映する運用にし、AIの出力をそのまま仕訳計上に使わない
  • 仕訳の最終判断 — 消込できない差異をどう処理するか(貸倒処理・雑収入計上・翌月繰越など)の最終判断は、AIの推測ではなく経理責任者・税理士が行う
  • 支払実行 — 買掛金の支払処理など、金銭が動く操作はAIに直接実行させない。人が承認した後に、承認済みのデータだけを次工程に渡す

Claude Codeが担えるのは、あくまで抽出・正規化・消込候補の提示・要約までです。会計処理の正確性に責任を持てるのは人(と専門家)であり、判断に迷う場合は税理士に相談してください。

段階的導入のロードマップ

Phase 1(2〜4週間):取引件数の多い主要取引先数社に絞って正規化と消込ロジックを検証する。許容誤差(手数料差引の範囲など)をチームで合意する。

Phase 2(1〜3ヶ月):対象取引先を広げ、差異レポートを経理の月次締め作業のフローに組み込む。得意先マスタの別名リストを運用しながら育てる。

Phase 3(3ヶ月以降):買掛金側の消込や補助簿と総勘定元帳の突合まで対象を広げ、承認ログの記録や会計システムとの連携まで拡張する。

想定される効果(試算)

以下は特定の実測データではなく、実装を検討する際の試算・想定です。

項目 想定される変化 備考
消込作業の内容 全件を目視・Excelで突合 → 正規化済みデータの「要確認」分だけを人が見る 試算。表記ゆれが多い取引先ほど短縮余地は大きい
見落としリスク 担当者の手作業による見落とし → 機械的な全件消込 全件を対象にできる点が定性的な効果
差異の一次仕分け すべての差異を人が確認 → 自動消込可/要確認に一次仕分け 人が見るのは「要確認」に絞られる

【要注意】よくある失敗パターンと回避策

失敗1:許容誤差を決めずにAIへ「入金と請求書が一致しているか教えて」と丸投げする

❌「この入金、どの請求書に対応するか教えて」
⭕「得意先コードが一致し、金額差が振込手数料相当(500円以内)、日付が請求書期日から5日以内の請求書だけを候補として一覧にしてください。確定はしないでください」

なぜ重要か:「合っている/合っていない」の最終判断を自然言語の出力に依存すると、許容誤差やマッチング順序が実行のたびにぶれます。判定ロジックはコードに固定し、AIには抽出・候補提示・要約だけを任せます。

失敗2:名寄せ候補が複数あるのに、スコアが一番高いものを自動確定させる

❌ candidates列の1番目を無条件に確定として消込を進める
⭕ 候補が2件以上ある行、またはスコアが閾値未満の行は「要確認」として必ず人が最終確認してから確定する

なぜ重要か:似た名称の取引先(例:同じ屋号の別法人、支店違い)は表記だけでは区別できないことがあります。自動確定を急ぐと、誤った取引先に入金を計上してしまいます。

失敗3:まとめ入金の組み合わせ探索を無制限にかけてしまう

❌ 全取引先・全請求書を対象に、あらゆる組み合わせの合計金額を総当たりで探索する
⭕ 同一取引先・直近の請求書に対象を絞り、組み合わせの上限件数(例:3件まで)を明示してから探索する

なぜ重要か:対象を絞らずに探索すると、計算量が跳ね上がるだけでなく、たまたま合計が一致した無関係な請求書を誤って組み合わせにしてしまうリスクが上がります。

失敗4:AIが書いた差異理由をそのまま仕訳の摘要欄に転記して計上する

❌ AIが出力した「振込手数料の差引の可能性があります」という一文をそのまま仕訳の摘要欄に転記して処理を進める
⭕ AIの要約は「確認すべき候補」として扱い、実際の原因(契約書・取引先への確認)は一次資料に当たって担当者が確認してから記録する

なぜ重要か:要約はあくまで推測です。契約内容や取引先への確認を経ずに数字を動かすと、後から監査で説明できなくなります。

よくある質問

Q. 銀行残高照合と売掛金の消込は同じ作業ですか?

いいえ、別の作業です。銀行残高照合は「現金預金の帳簿残高」と「銀行の残高証明書・通帳残高」を照合する作業で、未取立小切手や銀行未記帳といったタイミング差が主な差異要因です。売掛金の消込は「入金明細」と「請求書一覧」を突き合わせ、どの請求に対応する入金かを特定する作業で、この記事で扱っているのは主に後者です。

Q. Claude Codeに消込の最終承認をさせてもいいですか?

させるべきではありません。Claude Codeは抽出・正規化・消込候補の提示・差異の要約までを担い、最終的な消込の確定と仕訳処理は必ず経理担当者(必要に応じて税理士)が確認したうえで行う設計にしてください。

Q. まとめ入金の組み合わせ探索はどこまで自動化していいですか?

組み合わせが一意に定まり、対象を同一取引先・直近の請求書に絞ったうえで金額が過不足なく一致するケースは自動消込の候補にできますが、複数の組み合わせが同時に成立してしまう場合や、対象期間・取引先を広く取りすぎている場合は、誤マッチのリスクが上がるため人が確認する運用にしてください。

Q. 得意先マスタの別名リストはどう整備すればいいですか?

最初から完璧なリストを作ろうとせず、「要確認」として人が確認した振込人名義を、確認のたびに得意先マスタの別名リストへ追記していく運用が現実的です。数ヶ月運用すると主要取引先の表記ゆれはほぼ吸収できるようになります。

Q. 導入にはどれくらいの期間がかかりますか?

現場や取引先数にもよりますが、主要取引先数社に絞って正規化と消込ロジックを検証するまでは数週間、対象取引先を広げて月次の締め作業に組み込むにはさらに数ヶ月かかるのが一般的な目安です(試算であり実測値ではありません)。

まとめ:今日から始める3つのアクション

  1. 今日やること:直近1ヶ月分の銀行明細CSVと売掛金・買掛金の請求一覧を1つのフォルダに集め、取引先名の表記がどれだけ揺れているかを目視で確認する
  2. 今週中:主要取引先数社分だけ正規化スクリプトを書いて、実際にどのパターンで差異が出るかを洗い出す
  3. 今月中:差異を「自動消込してよいもの」と「人が確認するもの」に分類する社内ルールを、経理担当者と一緒に言語化する

関連記事として、経理・購買の請求書突合をClaude Codeで自動化する実装パターンもあわせて参考にしてください。発注書・納品書・請求書の三点照合と、この記事で扱った消込は、どちらも「書類間の差異をどう仕分けるか」という同じ設計思想でつながっています。


著者:佐藤傑(さとう・すぐる)
株式会社Uravation代表取締役。X(@SuguruKun_ai)フォロワー約10万人。100社以上の企業向けAI研修・導入支援。著書『AIエージェント仕事術』(SBクリエイティブ)。

参考・出典

ご質問・ご相談は お問い合わせフォーム からお気軽にどうぞ。

Next Step

この事例を、自社の業務に置き換える。

対象業務、利用データ、評価基準、社内展開の順番まで整理すると、AI開発ツール導入の失敗を減らせます。

導入を相談する