受け取ったファイルを取り込む前に差し戻す
目標
相手機関が送ってきた固定長・CSVの受信ファイルを取り込む前に検査し、ずれていればファイル全体を拒否するゲートキーパーfilegate.pyを作ります。判定は、機械が読めるレポートと終了コード、そして相手に返す拒否ファイルとして残します。
なぜ重要なのか
取り込んだあとで間違った行を探すことは、入り口で拒否することより常に高くつきます。すでに入った行は別のバッチがくわえて持っていき、戻すには訂正伝票とお詫びの電話が付いてきます。998件に問題がなくても、2件が規格からずれていれば、そのファイルは1行も入れません。半分だけ入れると、相手のトレーラーとこちらの元帳が永遠に合わなくなります。 固定長ファイルの幅は文字数ではなくバイトです。ハングル1文字はCP949では2バイト、UTF-8では3バイトなので、エンコーディングが変わると行の長さ自体が変わります。CSVは、引用の中のカンマと改行を区切りとして数えてはいけません。 採点ツールは、書かれた文言を信用しません。一時ディレクトリに自分で作った受信ファイルを用意し、作成したスクリプトを実行して、判定とエラーコードを突き合わせます。機関コードと件数、金額は、実行のたびに変わります。
ステップ
/root/bankfile/gen_inbound.pyを作成して実行し、/root/bankfile/inbound/に、相手機関4か所の1日分のファイル5個を作ってください。/root/bankfile/filegate.pyが、CSV受信ファイルの骨格とトレーラーの件数・合計を突き合わせ、レポートと終了コードを出すようにしてください。- filegate.pyに固定長(.txt)の処理を入れてください。1行がちょうど80バイトかどうかを、文字数ではなくバイトで測ります。
- filegate.pyが、規格と異なるエンコーディングで来たファイルをENCで拒否するようにしてください。ファイルごとに、読んだエンコーディングをレポートに書きます。
- filegate.pyのCSVパースを、RFC 4180のとおりに直してください。引用されたカンマと改行は、フィールドの区切りではありません。
- filegate.pyにフィールド単位の検証を入れ、拒否したファイルごとに拒否ファイルを残してください。エラーが1つでもあれば、そのファイルは1行も取り込みません。
- filegate.pyが、同じ機関・同じ連番の再送をDUPで拒否するようにしてください。ハッシュが同じならresend、違えばconflictです。
- 自分の受信ボックスを処理して、
/root/bankfile/gate_report.jsonと/root/bankfile/rejected/、/root/bankfile/receipt.mdを残してください。
参考
- 受信規格: 名前は
IN-<YYYYMMDD>-<기관코드 6자리>-<꼬리>.txtまたは.csvです。.txtはCP949固定長、.csvはUTF-8のCSVで、行末はCRLFです(プレースホルダーは、順に機関コード6桁と末尾部分です)。 - 固定長は1行がちょうど80バイトです。H =
H(1) + 機関コード(6) + ファイル日付(8) + 連番(3) + 空白(62)。D =D(1) + 取引番号(12) + 受取人名(20) + 口座(14) + 金額(13、左側を0で埋める) + 空白(20)。T =T(1) + 件数(6) + 合計(15) + 空白(58)。幅はすべてバイトです。 - CSVのレコードは、
H,기관코드,파일일자,일련번호/D,거래번호,수취인명,계좌,금액/T,건수,합계です(プレースホルダーは、順に機関コード・ファイル日付・連番、取引番号・受取人名・口座・金額、件数・合計です)。 - フィールド規格: 取引番号は
TRと数字10桁でファイル内で一意、口座は110-0000-00000、金額は1以上10000000000未満の整数、受取人名は空ではありません。 - 実行契約:
python3 /root/bankfile/filegate.py --in <수신디렉터리> --report <보고서.json> --reject <거절디렉터리>(プレースホルダーは、順に受信ディレクトリ、レポートのJSONファイル、拒否ディレクトリです) - レポート:
{"summary": "accept|reject", "accepted": [이름], "rejected": [이름], "files": [{"name", "verdict", "encoding", "records", "total", "sha256", "errors": [{"code", "line", "detail"}]}]}(プレースホルダーはファイル名です)。recordsはDレコードの数、totalは金額の合計です。 - エラーコード:
ENCWIDTHLAYOUTTRAILER_COUNTTRAILER_TOTALFIELDDUP。ファイル単位のエラーのlineは0です。取引番号が重複した場合は、あとに出てきた行を指します。 - 終了コード: すべて受け入れなら0、1つでも拒否なら2、受信ディレクトリを読めなければレポートなしで3。
- 拒否ファイル: 拒否されたファイルごとに
<거절디렉터리>/<파일이름>.reject.csv、ヘッダー行はline,code,detail(プレースホルダーは、順に拒否ディレクトリとファイル名です)。 - 自分で試す:
python3 /root/bankfile/filegate.py --in /root/bankfile/inbound --report /tmp/r.json --reject /tmp/rej; echo $? - よくある間違い: ハングルの名前を文字数で埋める、
split(",")でCSVを切る、エラーになった行だけを除いて残りを取り込む、ゲートキーパーがファイルを直してしまう。
1日分の受信ボックスを作る
/root/bankfile/gen_inbound.pyを作成して実行し、/root/bankfile/inbound/にファイル5個を作ってください。固定長(CP949)120件のファイルと、それをバイトまでそのまま再送した再送版、CSV 90件(受取人名にカンマを含む行が3つ以上)、トレーラーの件数が実際より1小さいCSV 60件、そして固定長なのにUTF-8で来たファイルです。
名前はIN-<YYYYMMDD>-<기관코드 6자리>-<꼬리>.txt|.csvです(プレースホルダーは、順に機関コード6桁と末尾部分です)。固定長の幅はバイトなので、text.encode('cp949')で測ってから空白を埋める必要があります。再送版は、同じバイトを別の名前でもう一度書けば済みます。CSVのカンマを含む名前は、二重引用符で囲んでください。
トレーラーの件数と合計から突き合わせる
/root/bankfile/filegate.pyが、CSV受信ファイルのH・D・Tの骨格を確認し、トレーラーの件数・合計を実際に数えた値と突き合わせるようにしてください。レポートと終了コード0・2を出します。
件数がずれたことと合計がずれたことは別の事故なので、コードを別に置きます(TRAILER_COUNT・TRAILER_TOTAL)。レポートのrecordsとtotalは、トレーラーに書かれた値ではなく、Dレコードから自分で数えた値です。採点ツールは、毎回異なる機関コードと件数でテストします。
固定長の幅は文字ではなくバイトである
/root/bankfile/filegate.pyに.txtの固定長処理を入れてください。1行がCP949でちょうど80バイトでなければWIDTHで拒否し、ずれた行番号を残します。
len(line)は文字数で、規格が言っているのはlen(line.encode('cp949'))です。フィールドを切る場所も、文字列ではなくバイトの上にあります。ハングルの名前を文字数で埋めた行だけが、幅がずれます。
規格と異なるエンコーディングで来たファイルを止める
/root/bankfile/filegate.pyが、.txtはCP949、.csvはUTF-8で読み、そのエンコーディングでデコードできなければENCで拒否するようにしてください。ファイルごとに、読んだエンコーディングをレポートのencodingに書きます。
bytes.decode()は、失敗するとUnicodeDecodeErrorを出します。例外のstartとreasonをdetailに書いておくと、相手の担当者がどのバイトで壊れたかがわかります。ゲートキーパーがエンコーディングを自動で切り替えて読んではいけません。
引用の中のカンマは区切りではない
/root/bankfile/filegate.pyのCSVパースを、RFC 4180のとおりに直してください。引用されたカンマと改行を含むファイルのrecordsとtotalが合っている必要があり、引用符が閉じていないファイルは拒否しなければなりません。
標準ライブラリのcsvモジュールは、引用のルールをそのまま実装しています。文字列を渡すには、io.StringIOで包んでください。引用符が閉じていないと、後ろの行がまるごと1つのフィールドに吸い込まれ、フィールド数が合わなくなります。
フィールド検証と拒否ファイル、そして部分取り込みの禁止
/root/bankfile/filegate.pyに取引番号・受取人名・口座・金額の検証を入れ、拒否したファイルごとに<거절디렉터리>/<파일이름>.reject.csvをヘッダー行line,code,detailで残してください。エラーが1つでもあれば、そのファイルは受け入れ一覧に入ってはいけません(プレースホルダーは、順に拒否ディレクトリとファイル名です)。
取引番号はファイル内で一意でなければならず、重複した場合は、あとに出てきた行を指します。固定長から切り出したフィールドは、左右の空白を取って見ます。拒否ファイルは、相手機関のバッチがそのまま読めなければならないので、人間向けの文ではなく、表で出します。
再び来たファイルと衝突したファイルを切り分ける
/root/bankfile/filegate.pyが、すでに受け入れた機関・連番と同じファイルをDUPで拒否するようにしてください。ファイルのハッシュが同じならdetailにresend、違えばconflictを書き、レポートにsha256を残します。
連番はヘッダーレコードにあります。前に受け入れられたファイルだけを覚えておく必要があります。拒否されたファイルは取り込まれていないので、あとに来た同じ番号は重複ではありません。ハッシュは、hashlib.sha256でファイルのバイト全体から求めます。
自分の受信ボックスを処理して受信レポートを出す
自分の受信ボックスを処理して/root/bankfile/gate_report.jsonと/root/bankfile/rejected/を残し、/root/bankfile/receipt.mdに## 수신 요약、## 거절한 파일과 사유、## 재전송으로 판정한 파일、## 적재하지 않은 이유、## 상대 기관에 요청할 것の5つの節を書いてください(見出しは韓国語で、順に「受信の要約」「拒否したファイルと理由」「再送と判定したファイル」「取り込まなかった理由」「相手機関に依頼すること」という意味です)。
前のステップで作った/root/bankfile/filegate.pyをそのまま使います。要約には、受け入れ件数・拒否件数と、実際に取り込まれる金額の合計を、数字で書きます。拒否したファイルは、名前をそのまま書かなければ、相手が見つけられません。採点ツールは/root/bankfile/inboundを再び読んで、書かれたレポートと突き合わせるので、レポートは手で書かず、filegate.pyが出したものを使ってください。