請求ファイルを受け取り、照合し、一度だけ入れる
目標
提携保険会社の請求ファイルをSFTPで安全に受け取り(完了フラグ・チェックサム・原子的な名前の変更・再実行しても安全)、ヘッダー・トレーラーを検証し、ファイル単位で取り込んだうえで、自社の受付元帳と突合します。
なぜ重要なのか
ファイル連携の事故は境界で起きます。書き込み中のファイル、転送中の破損、再実行による二重の取り込み、突き合わせていない数字です。4つとも、エラーなく黙って通り過ぎ、決算や顧客からのクレームで初めて表に出ます。
ステップ
python3 /opt/lab/fixtures/eaimw/batch/partner_drop.py --out /root/eaimw/batch/remote --day 20260923でパートナーのリモートディレクトリを作成し、sftp -D /usr/lib/openssh/sftp-server -b <배치파일>(プレースホルダーはバッチファイルです)でそのディレクトリにcdしてからls -lした出力を、そのまま/root/eaimw/batch/remote-ls.txtに保存してください。/root/eaimw/batch/fetch.shを書いてください。環境変数REMOTE_DIR(既定は/root/eaimw/batch/remote)・LOCAL_DIR(既定は/root/eaimw/batch/inbox)・QUAR_DIR(既定は/root/eaimw/batch/quarantine)を読み、SFTPでリモートの一覧を取得し、<파일>.done(プレースホルダーはファイル名です)があるもののうち、.datだけをLOCAL_DIRに取得します。- フラグの内容(
<바이트수> <sha256>、プレースホルダーはバイト数です)と、取得したファイルを照合してください。違っていればそのファイルをQUAR_DIRに移し(LOCAL_DIRには置きません)、残りは処理を続けて、最後に0以外のコードで終了します。 - 受信中は
LOCAL_DIRの中に<파일>.part(プレースホルダーはファイル名です)として受け取り、検証が終わったら最終的な名前に変更してください。受信後は、SFTPでリモートのファイルとフラグの名前の末尾に.fetchedを付けます。再実行しても何も取得せず、0で終了します。 /root/eaimw/batch/check.py <파일>(プレースホルダーはファイルです)を作成してください。行ごとに60バイト(EUC-KR、行末はLF)、最初の行はH・最後の行はT・その間はD、Hの日付=ファイル名の日付、Tの件数・合計=Dの件数・合計です。合っていればOK <건수> <합계>(プレースホルダーは件数と合計です)と終了コード0、違っていれば1行目にE <사유>(プレースホルダーは理由です)を出力し、終了コード2にします。/root/eaimw/batch/load.py --db <경로> <파일>(プレースホルダーはパスとファイルです)を作成してください。検証を通ったファイルだけを、SQLiteのテーブルclaims(claim_no 기본키, policy_no, amount, claim_date, insured, src_file)(プレースホルダーは主キーです)に取り込み、load_log(file 기본키, records, total, loaded_at)(プレースホルダーは主キーです)に記録します。同じファイルは再び入れず(0で終了)、ファイル1つは1トランザクション(すべてか、何もなしか)にします。/root/eaimw/batch/recon.py --db <경로> --ledger <원장CSV> --out <결과CSV>(プレースホルダーはパス、元帳CSV、結果CSVです)を作成してください。claimsと元帳(claim_no,amount)を請求番号で突き合わせ、claim_no,status,partner_amount,our_amountを請求番号順に書きます。statusはMATCHED・ONLY_PARTNER・ONLY_OURS・AMOUNT_MISMATCH、ない側の金額は空欄です。最後に、当日のファイル(CLM_401_20260923.dat)を取得して取り込み、partner_drop.py --ledger /root/eaimw/batch/ledger.csv --day 20260923で作った元帳と突合した結果を、/root/eaimw/batch/recon.csvに出力してください。
参考
- インターフェース仕様書:
/opt/lab/fixtures/eaimw/batch/SPEC.md。D行の位置(0から): 請求番号1–15、証券番号15–27、金額27–40、請求日40–48、名前48–60。 - SFTPバッチの例:
printf 'cd %s\nls -1\n' "$REMOTE_DIR" > /tmp/b; sftp -q -D /usr/lib/openssh/sftp-server -b /tmp/b。出力にsftp>で始まるコマンド行が混ざります。get 원격 로컬(プレースホルダーはリモートとローカルです)、rename 옛이름 새이름(プレースホルダーは旧名と新名です)も同じ方式です。 - チェックサム:
sha256sum 파일 | cut -d' ' -f1(プレースホルダーはファイルです)、サイズ:stat -c %s 파일(プレースホルダーはファイルです)。 - よくある間違い: 一時ファイルを
/tmpに受け取ってmvすること(別のファイルシステムだと原子的ではありません)、取り込みを行ごとにコミットすること、突合を片側だけを基準に回すこと(反対側にだけある件を見逃します)。
SFTPでリモートを見る
partner_drop.pyでリモートディレクトリを作成し、sftp -Dバッチで一覧した出力を/root/eaimw/batch/remote-ls.txtに保存してください。
バッチファイルにcdとls -lの2行を書き、sftp -D /usr/lib/openssh/sftp-server -bでそのバッチファイルを指定して実行します。出力をそのままリダイレクトしてください。
フラグのあるファイルだけを取得する
/root/eaimw/batch/fetch.shが、REMOTE_DIRから.doneのある.datだけをLOCAL_DIRに取得するようにしてください。
まずls -1で名前の一覧を取得し、各.datについて、同じ名前+.doneが一覧にあるか確認します(grep -qx)。取得はgetコマンドを使います(リモートのパス、ローカルのパスの順)。
チェックサムが違えば隔離する
フラグのバイト数・sha256と取得したファイルを照合し、違っていればQUAR_DIRに移し、最後に0以外のコードで終了してください。
フラグも取得してread -r size shaで読み、stat -c %s、sha256sumと比較します。1つのファイルが壊れていても残りを止めず、終了コードだけを覚えておいてください。
一時的な名前で受け取り、リモートにマークする
LOCAL_DIRの中の.partに受け取って検証後に名前を変更し、リモートのファイルとフラグに.fetchedを付けて、再実行を安全にしてください。
一時ファイルは最終的なディレクトリと同じ場所に置かないと、renameが原子的になりません。リモート側のマークはSFTPのrenameコマンドです。
ヘッダーとトレーラーを突き合わせる
/root/eaimw/batch/check.py <ファイル>が、行の長さ(バイト)・H/D/T構造・日付・件数・合計を検証するようにしてください(OK <件数> <合計> / E <理由>と終了コード2)。
ファイルをrbで読んで行に分け、len(行)==60を確認します。Tの件数(1–8)・合計(8–23)を、Dから自分で数えた値と比較します。名前はeuc_krでデコードします。
ファイル単位で1回だけ入れる
/root/eaimw/batch/load.py --db <パス> <ファイル>が、検証済みのファイルだけを1トランザクションで取り込み、load_logで再取り込みを防ぐようにしてください。
isolation_level=Noneで開き、BEGIN … COMMITを自分で書きます。請求番号の主キー衝突(IntegrityError)が起きたらROLLBACKして、前の行まで巻き戻します。
自社の元帳と突合する
/root/eaimw/batch/recon.pyでclaimsと元帳を突合し、当日のファイルを基準にした結果を/root/eaimw/batch/recon.csvに出力してください。
両側の請求番号の和集合を並べ替えて回します。片側だけで回すと、反対側にだけある件を見逃します。元帳はpartner_drop.py --ledgerで作ります。