取り込んだ後に誤った行を見つけても、もう遅い
一言でいうと
相手機関から受け取った清算・送金指示ファイルは、取り込む前にフォーマット契約で検査し、1行でもずれていればファイル全体を差し戻します。入り口で拒否することは、取り込んだあとで突合することより常に安上がりです。
なぜ必要なのか
銀行の電算の1日は、ファイルで始まり、ファイルで終わります。相手機関が明け方にFTPで置いた1日分の指示ファイルを受け取って取り込み、夕方にこちらの結果を同じ方式で返します。この通り道で事故が起きるパターンは、いつも似ています。ファイルは届き、取り込みバッチは「成功」で終わり、ところが、相手が送ったトレーラーの件数とこちらが入れた件数が違うのです。
そのあとが本当の問題です。すでに入った行は、別のバッチがくわえて持っていきました。残高が変わり、通知が出ました。戻すには訂正伝票を書き、どの行が入ってどの行が抜けたかを数え直さなければなりません。ファイル1つを受け入れなければ5分で済んだことが、1日分の突合とお詫びの電話になります。
そのため、受信経路には必ずゲートキーパーが立ちます。ゲートキーパーの仕事は単純です。このファイルが、合意したフォーマット契約を守っているかだけを見ます。守っていなければ、1行も取り込まず、理由を付けて差し戻します。半分だけ入れるという選択肢はありません。
どう動くのか
フォーマット契約は、たいてい4つの層です。
第一に、ファイルの外見です。名前のルール、エンコーディング、行末。国内の金融機関の対外ファイルには、今もまだ固定長(fixed width)のEUC-KR系が多く残っています。Pythonではcp949コーデックを使いますが、標準エンコーディングの表は、このコーデックの別名を949、ms949、uhcと書き、言語をKoreanに分類しています。ハングル1文字がCP949では2バイト、UTF-8では3バイトだという違いが、次の層にそのままつながります。
第二に、レコードの構造です。固定長なら、1行がちょうど何バイトかを見ます。ここで最もよく起きる間違いが、文字数で埋めることです。受取人名の欄が20なら、それは20文字ではなく20バイトです。ハングル3文字の名前「キム・ソジュン」を3文字と見て、17個分を空白で埋めると、その行は規格の幅からずれ、そのあとのフィールドがまるごとずれます。Unicodeの扱い方ガイドが説明するように、Pythonの文字列の長さはコードポイントの数であり、ファイルに書かれるのはエンコードされたバイトです。切る場所は、strではなくbytesの上になければなりません。
CSVなら、RFC 4180が基準です。この文書はCategoryがInformationalで、自らインターネット標準を何も規定しないと明記していますが、事実上の合意として広く使われています。核心は3つです。改行・二重引用符・カンマを含むフィールドは、二重引用符で囲みます。囲まれたフィールド内の二重引用符は、二重引用符2つで書きます。レコードはCRLFで終わります。そのため、line.split(",")で切った瞬間、受取人名にカンマが含まれる行はフィールドが1つ増え、口座の位置に名前の断片が入ってきます。csvモジュールに任せれば、引用の中のカンマと改行を区切りとして数えません。
第三に、ヘッダーとトレーラーです。ファイル末尾のTレコードに書かれた件数と合計を、実際に数えた値と合わせます。これが、送信中の切れを捕まえる最も安い仕組みです。件数が合っているのに合計が違うなら、切れたのではなく値が変わったのであり、この2つは別の事故なので、エラーコードも別に置きます。
第四に、フィールド単位の規格と重複です。取引番号の形、口座の形式、金額の範囲を見ます。そして、同じファイルが2回来ることは、例外ではなく日常です。相手がこちらのレスポンスを受け取れなければ、同じファイルをもう一度置きます。ファイルのsha256が同じなら単なる再送で、連番は同じなのに内容が違うなら、それは事故です。hashlibでファイルのハッシュを残しておけば、この2つを切り分けられます。
수신함 ──▶ 이름·인코딩 ──▶ 레코드 폭·인용 ──▶ 트레일러 ──▶ 필드 ──▶ 중복
│ │ │ │ │
└──────────── 하나라도 걸리면 파일 전체를 거절 ──────┘
このコードブロックの韓国語コメントは、受信ボックス、名前・エンコーディング、レコード幅・引用、トレーラー、フィールド、重複の順に検査し、1つでも引っかかればファイル全体を拒否する、という意味です。
現場での姿
最初に出会う姿は、静かに壊れるエンコーディングです。規格はCP949なのに、相手機関がシステムを替えるときにUTF-8で送り始めます。運がよければ、デコードが例外で終わってすぐ引っかかります。運が悪いと、一部のバイトが別の文字として解釈され、名前だけが文字化けしたまま入ってしまいます。そのため、エンコーディングだけを見るのではなく、バイト幅まで一緒に見ます。ハングル1文字が2バイトから3バイトに変わると行の長さが変わるので、幅の検査がエンコーディングの事故をもう一度捕まえてくれます。
2つ目は、部分取り込みの誘惑です。「998件は問題ないのに、2件のせいで全部差し戻すのか」という声が、必ず出ます。差し戻さなければなりません。998件だけを入れると、相手のトレーラーの1000件とこちらの元帳の998件がずれたまま残り、翌日に相手が送った訂正ファイルにその2件が再び入ってきて、今度は重複になります。全部か無かで置いておけば、事故は1日で終わります。
3つ目は、理由のない拒否です。「フォーマットエラー」とだけ書いて差し戻すと、相手の担当者は何を直せばよいかわかりません。どの行のどのフィールドが何だったので引っかかったかを、機械が読める拒否ファイルとして一緒に返せば、相手はそのファイルを自分のバッチにそのまま渡せます。
実務で本当に大切なこと
- 拒否はファイル単位です。行単位の拒否を許した瞬間、突合が永遠に終わらなくなります。
- 判定には観測値を付けます。「トレーラー合計000000000050000、実際46000」のように、両側を書きます。
- エラーコードは契約です。バッチの自動化が、コードで次の行動を選びます。トレーラーの不一致は再送の依頼、重複は無視、エンコーディングは相手システムの担当者の呼び出しです。
- 再送は正常な動作です。防がずに区別します。ハッシュが同じなら再送、連番だけが同じなら衝突です。
- ゲートキーパーはファイルを直しません。空白を切り落としてエンコーディングを変えてあげ始めると、相手機関は、自分のファイルが規格に違反していることを永遠に知りません。
次のラボですること
次のラボが使う80バイトのレコード配置とエラーコードの名前、終了コードは、このラボの前提です。機関ごとに規格が異なり、現場で原本になるのは、相手機関との間で交わした合意文書です。変わらないのは、検査する層の順序と、全部か無かという原則です。
相手機関4か所が送ってきた1日分の受信ボックスを自分で作り、その前に立つゲートキーパーを段階的に積み上げます。トレーラーの突合から始めて、固定長のバイト幅、規格と異なるエンコーディング、RFC 4180の引用、フィールドの検証と拒否ファイル、再送の判定まで加えます。採点ツールは、毎回異なる機関コードと件数で自分の受信ファイルを作り、作成したゲートキーパーを実行して判定を突き合わせます。最後に、自分の受信ボックスを処理して、受信レポートを残します。