全銀フォーマットの総合振込データをClaude Codeで作り検算する
支払一覧CSVから120桁固定長の総合振込ファイルを作り、桁数・半角カナ・合計件数と合計金額を検算する手順。生成と検証のスクリプトをClaude Codeに書かせ、Stopフックで検算が通るまで作業を続けさせます。
総合振込の全銀フォーマットは、1行120バイトの固定長レコードを、ヘッダ、データ、トレーラ、エンドの順に並べたテキストファイルです。支払一覧のCSVから組み立てる処理自体は単純ですが、桁が1つずれただけで銀行の取込がエラーになります。ここでは変換スクリプトと検算スクリプトをClaude Codeに書かせ、検算が通るまで作業を終わらせない構成を作ります。
例のCSVは手順確認用の合成データで、口座も金融機関の組み合わせも実在のものではありません。
総合振込ファイルは4種類の120バイトレコードでできている
きらぼし銀行の振込依頼ファイル仕様は、全銀協規定形式を「各120桁(バイト)の4種類(ヘッダ、データ、トレーラ、エンド)の固定長レコード」と説明しています。三菱UFJ銀行のBizSTATIONの仕様書も、同じ4種類の構成です。全銀ネットの解説によると、振込は全銀システムで銀行間の資金清算に回ります。この記事のファイルは、その手前で依頼人が銀行に渡す入口にあたります。
並びはヘッダ1行、データ(振込先ごとに1行)、トレーラ1行、エンド1行です。データ区分は先頭1桁で、ヘッダが1、データが2、トレーラが8、エンドが9になります。
| レコード | 区分 | 主な項目(桁数) |
|---|---|---|
| ヘッダ | 区分1 | 主な項目(桁数)種別コード21(総合振込)、委託者コード(10)、委託者名(40)、取組日MMDD(4)、仕向銀行番号(4)、仕向支店番号(3)、預金種目(1)、口座番号(7) |
| データ | 区分2 | 主な項目(桁数)被仕向銀行番号(4)、被仕向支店番号(3)、預金種目(1)、口座番号(7)、受取人名(30)、振込金額(10)、顧客コード(10+10) |
| トレーラ | 区分8 | 主な項目(桁数)合計件数(6)、合計金額(12) |
| エンド | 区分9 | 主な項目(桁数)残りはすべて空白 |
文字種は2つです。数字項目(N)は半角数字を右詰めで、足りない桁を0で埋めます。文字項目(C)は半角のカナ・英大文字・数字を左詰めで、余りを半角スペースで埋めます。たとえば5桁の数字項目に13を入れるなら00013、5桁の文字項目にABCを入れるならABCの後ろにスペース2つです。
銀行ごとに違う項目は先に確かめておく
全銀協規定形式に準拠していても、細部は銀行によって違います。きらぼし銀行と三菱UFJ銀行の仕様書を並べるだけでも、次の差があります。
| 項目 | きらぼし銀行の仕様書 | 三菱UFJ銀行(BizSTATION)の仕様書 |
|---|---|---|
| コード区分(ヘッダ) | きらぼし銀行の仕様書0=JISまたは1=EBCDICを必須で設定 | 三菱UFJ銀行(BizSTATION)の仕様書取り込まない(スペースで可) |
| 仕向銀行名 | きらぼし銀行の仕様書設定不要(初期値) | 三菱UFJ銀行(BizSTATION)の仕様書「ミツビシユ-エフジエイ」をセット |
| 被仕向銀行名・支店名 | きらぼし銀行の仕様書規定形式では設定不要、CSV形式では条件次第 | 三菱UFJ銀行(BizSTATION)の仕様書スペースなら番号から補完して取り込む |
| 文字コード | きらぼし銀行の仕様書規定形式ではコード区分で指定 | 三菱UFJ銀行(BizSTATION)の仕様書シフトJISまたはEBCDIC |
| 1回の最大件数 | きらぼし銀行の仕様書記載なし | 三菱UFJ銀行(BizSTATION)の仕様書データレコード200,000件 |
三菱UFJ銀行の仕様書には許容記号の一覧もあります。受取人名では、許容記号以外の記号がスペースに変換され、小文字は大文字に、長音は「-」に変換されると書かれています。変換されるとはいえ、取り込み後に受取人名が変わるのは避けたいところです。生成側であらかじめ許容文字に寄せ、変換が起きない状態でファイルを渡すほうが確実です。
この差があるため、スクリプトに銀行固有の値を埋め込まず、設定に出しておくのが安全です。自社が使う銀行の仕様書を最初にClaude Codeへ読ませ、上の表のような差分を列挙させてから書かせると、後の手戻りが減ります。
項目の値は何を入れるか迷いやすい
仕様書の表を読むだけでは、どの項目に何を入れるかで迷います。2行の仕様書に共通して書かれている値を整理しました。
| 項目 | 入れる値 | 注意 |
|---|---|---|
| 種別コード | 入れる値21(総合振込) | 注意総合振込のファイルであることを示す業務種類の区分 |
| 取組日 | 入れる値MMDD(月日) | 注意年は入らない。振込指定日を4桁で入れる |
| 委託者コード | 入れる値銀行が採番した10桁 | 注意きらぼし銀行は当行が採番した値、三菱UFJ銀行は契約者番号でスペース不可 |
| 預金種目(ヘッダ) | 入れる値1=普通、2=当座 | 注意三菱UFJ銀行は9(その他)も許容 |
| 預金種目(データ) | 入れる値1=普通、2=当座、4=貯蓄、9=その他 | 注意三菱UFJ銀行は科目が不正なとき0(不明)を補って取り込む |
| 手形交換所番号 | 入れる値使わない | 注意4桁のスペース(初期値)のままにする |
| 新規コード | 入れる値使わない | 注意三菱UFJ銀行は「0」固定 |
| 振込区分 | 入れる値7(電信振込)またはスペース | 注意三菱UFJ銀行はスペースでも可 |
顧客コードの2項目と、その後ろの識別表示は特に取り違えやすい箇所です。きらぼし銀行の仕様書では、識別表示にYを入れると顧客コード1・2の20桁がEDI情報として扱われ、受取人に通知されます。Yを入れなければ、顧客コードは依頼人が付ける受取人の識別用です。三菱UFJ銀行はさらにXを許容し、顧客コード2の先頭に振込依頼人番号を付加します。この記事のコードは識別表示を空白にしており、顧客コード1に請求書番号を入れるだけの最小構成です。
登録用の「振込先ファイル」は別の形式
きらぼし銀行の仕様書には、振込依頼ファイルのほかに、振込先を一括登録する「振込先ファイル」の形式も載っています。同じ120桁の全銀協規定形式にも、可変長のCSV形式にも対応していますが、中身は別物です。
- 規定形式の振込先ファイルは、ヘッダの委託者コードや取組日が設定不要(初期値)で、トレーラの合計件数・合計金額も設定不要になります。依頼ファイルのヘッダ・トレーラ用のコードをそのまま流用すると、設定不要の項目に値が入るので注意が必要です
- 規定形式の振込先ファイルでは、被仕向銀行名と支店名(カナ)が必須になります。依頼ファイルでは設定不要だった項目です
- CSV形式の振込先ファイルは、振込種類、金融機関コード、支店コード、科目、口座番号、取引先登録名、漢字の金融機関名・支店名などを持つ38項目で、漢字は全角で1文字2バイトと数えます
支払の都度つくるのは依頼ファイルで、取引先を登録するときにだけ使うのが振込先ファイルです。Claude Codeに「全銀フォーマットで出して」とだけ頼むと、どちらの形式を指すのかが曖昧になります。CLAUDE.mdで「振込依頼ファイル(総合振込)」と書いておくと、取り違えを避けられます。
CLAUDE.mdに桁と文字のルールを書く
生成も検算も、同じルールを読んで動く必要があります。プロジェクトのCLAUDE.mdには、次のように書いておきます。
## 総合振込ファイル(全銀フォーマット)
- 1レコード=120バイト。ヘッダ→データ→トレーラ→エンドの順
- 数字項目は右詰め0埋め、文字項目は左詰めスペース埋め
- 文字は半角カナ・英大文字・数字・許容記号のみ。小文字カナは大文字へ
- 出力はCP932でCRLF区切り。桁は文字数でなくバイト数で数える
- 銀行固有の値(委託者コード・銀行名)は config に置き、コードに直書きしない
- 変更したら `python3 check.py` を実行し、終了コード0を確認する
- 実在の口座情報を含むCSVは読み込まない。サンプルは合成データのみ最後の1行は運用の線引きです。口座番号と受取人名の組は個人情報になる可能性があるため、本番の支払一覧をそのままセッションに読ませない運用にしておくと、事故が起きる余地を減らせます。
支払一覧CSVから120桁の行を組み立てる
入力は次の形のCSVです。合成データで3件だけ用意しました。
bank_code,branch_code,account_type,account_number,payee_kana,amount,customer_code
0005,123,1,1234567,カ)サンプルシヨウジ,150000,INV-0001
0009,001,1,7654321,ヤマダ タロウ,48000,INV-0002
0137,456,2,0012345,カブシキガイシヤ テスト,1200000,INV-0003Claude Codeへの指示は、たとえば次のように出します。ルールの出どころと完了条件を一緒に渡すのがコツです。
claude -p "payments.csv から総合振込の全銀ファイルを out.txt に作る zengin.py を書いて。\
仕様は CLAUDE.md に従い、桁と文字種の違反は ValueError で止めること。\
最後に python3 check.py が終了コード0になるのを確認して" \
--allowedTools "Read,Edit,Write,Bash(python3 *)"上の指示で書かれる実装の一例が次のコードです。Python 3.12.2で動作を確かめたコードで、build が組み立て、verify が検算を担い、末尾の main がCSVの読み込みとファイル出力をつなぎます。銀行固有の値は config.json から読みます。値は合成です。
{
"client_code": "1234567890",
"client_name": "カ)テストイライニン",
"mmdd": "1031",
"bank": "0005",
"branch": "123",
"acct_type": "1",
"acct": "1234567"
}import csv, json, re
# 小文字カナは大文字へ、長音は「-」へ寄せる
HW_SMALL = str.maketrans("ァィゥェォッャュョー", "アイウエオツヤユヨ-")
# 許容記号は銀行ごとに違う
ALLOWED = re.compile(r"[ヲ-ン゙゚0-9A-Z \-().,/「」]*")
def n(value, width):
s = str(value)
if not s.isdigit() or len(s) > width:
raise ValueError(f"数字項目が不正: {s!r} (最大{width}桁)")
return s.zfill(width)
def c(value, width):
s = value.translate(HW_SMALL)
if not ALLOWED.fullmatch(s) or len(s) > width:
raise ValueError(f"文字項目が不正: {s!r} (最大{width}桁)")
return s.ljust(width)
def build(rows, client_code, client_name, mmdd, bank, branch, acct_type, acct):
lines = ["1" + "21" + "0" + n(client_code, 10) + c(client_name, 40) + n(mmdd, 4)
+ n(bank, 4) + c("", 15) + n(branch, 3) + c("", 15)
+ n(acct_type, 1) + n(acct, 7) + c("", 17)]
total = 0
for r in rows:
amount = int(r["amount"])
total += amount
lines.append("2" + n(r["bank_code"], 4) + c("", 15) + n(r["branch_code"], 3)
+ c("", 15) + c("", 4) + n(r["account_type"], 1)
+ n(r["account_number"], 7) + c(r["payee_kana"], 30)
+ n(amount, 10) + "0" + c(r["customer_code"], 10) + c("", 10)
+ "7" + c("", 1) + c("", 7))
lines.append("8" + n(len(rows), 6) + n(total, 12) + c("", 101))
lines.append("9" + c("", 119))
assert all(len(l) == 120 for l in lines)
return "\r\n".join(lines) + "\r\n"
def verify(path, expected_count, expected_total):
errors = []
raw = open(path, "rb").read()
records = [r for r in raw.replace(b"\r\n", b"\n").split(b"\n") if r]
if not records:
return ["ファイルが空です"]
for i, r in enumerate(records, 1):
if len(r) != 120:
errors.append(f"{i}行目: {len(r)}バイト (120必須)")
kinds = [r[:1] for r in records]
if kinds[0] != b"1" or kinds[-1] != b"9" or kinds[-2] != b"8":
errors.append("レコード順が 1 → 2... → 8 → 9 でない")
data = [r for r in records if r[:1] == b"2"]
amounts = sum(int(r[80:90]) for r in data) # 振込金額: 先頭から81〜90バイト目
trailer = next(r for r in records if r[:1] == b"8")
if int(trailer[1:7]) != len(data) or int(trailer[1:7]) != expected_count:
errors.append(f"件数不一致: トレーラ{int(trailer[1:7])} / データ{len(data)} / 期待{expected_count}")
if int(trailer[7:19]) != amounts or amounts != expected_total:
errors.append(
f"金額不一致: トレーラ{int(trailer[7:19])}"
f" / データ{amounts} / 期待{expected_total}")
return errors
if __name__ == "__main__":
cfg = json.load(open("config.json", encoding="utf-8"))
with open("payments.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
text = build(rows, **cfg)
# 半角カナはCP932で1バイト。改行はCRLFのまま書く
with open("out.txt", "w", encoding="cp932", newline="") as f:
f.write(text)
total = sum(int(r["amount"]) for r in rows)
errors = verify("out.txt", len(rows), total)
print("\n".join(errors) if errors else "OK")組み立ての要点は3つです。n は数字を0埋めし、桁あふれと数字以外を例外にします。c は小文字カナを大文字へ寄せ、許容外の文字と桁あふれを例外にします。最後の assert で全レコードが120桁であることを保証します。例外にしているのは、銀行側の取込エラーで気づくより、手元で止めるほうが原因を追いやすいからです。
CSVに入った名義が全角カナだったときの変換は、このコードに含めていません。全角から半角への変換表を足すか、入力側を半角カナに揃えるかを、CLAUDE.mdで決めておく必要があります。
合計件数と合計金額を2通りで検算する
生成側がトレーラに入れた合計は、生成側の計算結果にすぎません。そこで検算側は、ファイルを読み直してバイト列から件数と金額を数え直し、トレーラの値、CSVの件数と合計の三者を突き合わせます。
verify が見るのは次の4点です。
- 全行が120バイトか
- 先頭のデータ区分が1、2...、8、9の順に並んでいるか
- データレコードの件数が、トレーラの合計件数とCSVの件数に一致するか
- データレコードの振込金額の合計が、トレーラの合計金額とCSVの合計に一致するか
手元のPython 3.12.2で、3件のCSVから作ったファイルを検算した結果は次のとおりです。
python3 -I zengin.py出力は OK の1行で、トレーラは 8 + 件数 000003 + 合計金額 000001398000 でした(150,000+48,000+1,200,000=1,398,000)。
検算が本当に誤りを捕まえるかも確かめました。出力ファイルの2件目の金額を48,000から49,000に書き換えると、次のエラーが返ります。
金額不一致: トレーラ1398000 / データ1399000 / 期待1398000顧客コードの後ろにスペースを1つ足して121バイトにした行は、「2行目: 121バイト(120必須)」と報告されます。逆に、ヤマダ(カ) のように全角カッコが混じった名義や、漢字の「山田」を c に渡すと、文字項目が不正として例外になります。
Stopフックで検算が通るまで作業を続けさせる
Claude Codeに「確認してから終わって」と頼むだけでは、確認が省かれることがあります。検算をフックに置けば、確認が手順の外に出ます。公式のフックの資料には、Stop などのフックが終了コード2を返すと、Claudeが作業を続けると書かれています。
検算の入口は、次の check.py です。
import csv, json, sys
import zengin
# フックの入力(JSON)。続行中の再実行なら、ここで抜けて無限ループを防ぐ
hook = json.loads(sys.stdin.read() or "{}")
if hook.get("stop_hook_active"):
sys.exit(0)
with open("payments.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
total = sum(int(r["amount"]) for r in rows)
errors = zengin.verify("out.txt", len(rows), total)
if errors:
print("\n".join(errors), file=sys.stderr)
sys.exit(2) # Stop フックでは Claude に作業の続行を促す.claude/settings.json に次を置くと、Claudeが応答を終えようとするたびに検算が走ります。
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/check.py" }
]
}
]
}
}注意点が2つあります。1つ目は無限継続です。検算が通らないまま終了コード2を返し続けると、Claudeは止まれません。フックの入力JSONには、フックによる続行中かどうかを示す stop_hook_active が入るので、check.py の冒頭でtrueなら終了コード0で抜けています。この場合、続行の1回目で直らなかった誤りは、そのまま応答が終わる点に注意してください。2つ目はファイルが無いときです。out.txt や payments.csv が無いと check.py は FileNotFoundError で終了コード1になり、フックは続行を促さずに素通りします。生成前に応答が終わる使い方では、検算が走らない前提で運用します。
金額を改ざんした out.txt に対して check.py を実行すると、終了コードは2で、標準エラーに不一致の内容が出ました。正常なファイルでは終了コード0です。フックの書き方と「テストが通るまで止めない」運用の全体像は、Claude Codeのagent hookでテスト通過まで止めない設定で詳しく扱っています。
つまずきやすい4か所
- 文字数でなくバイト数で数える: 半角カナはCP932で1文字1バイトなので、
lenで数えても120になります。全角が1文字混ざると2バイトになり、行の長さがずれます。検算を「バイト列」に対して行うのはこのためです - 濁点は別の1文字: 半角カナの「ジ」は2文字(2バイト)です。30桁の受取人名に収まるかは、全角換算でなく半角で数えます
- 小文字カナ: 「シヨウ」と「ショウ」は別の文字ですが、三菱UFJ銀行の仕様書は小文字カナを大文字に変換して取り込むと書いています。名義の突合に使う側が混乱しないよう、生成時点で寄せておきます
- 改行コード: きらぼし銀行の仕様書は、レコードごとの改行の有無は任意で、CR+LF、CR、LFのいずれも可としています。ただし他行では扱いが違う可能性があるため、銀行の仕様書で確認した値をCLAUDE.mdに書きます
最終的な合否は、銀行のインターネットバンキングが取込時に出すチェック結果です。手元の検算は、取込エラーの手前で桁と合計の誤りを潰す役割にとどまります。
支払側のファイルを作る流れとは逆に、入ってきた入金を帳簿と突き合わせる作業は、入金消込をClaude Codeで自動化する手順にまとめています。CSVを決まった形式に整えて検査する考え方は、自治体オープンデータを標準データセットの形式に整える記事と共通です。