接收理赔文件、核对、只入库一次
目标
通过 SFTP 安全地接收合作保险公司的理赔文件(完成标志、校验和、原子重命名、可安全重新运行),校验头记录和尾记录,按文件入库,然后与我们的受理账本对账。
为什么重要
文件对接的事故出在边界上——正在写的文件、传输中的损坏、因重新运行造成的重复入库、没有核对的数字。这四种都是没有错误、悄悄地过去,到了结账或客户投诉时才暴露。
步骤
- 用
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>,占位符依次为字节数、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 结束,不通过时,第一行输出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 结束),一个文件就是一个事务(全部或全无)。/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 两行,用 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 比较。不要因为一个文件损坏就停下其余的,只要记住退出码就行。
用临时名称接收,并在远端做标记
先用 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 解码。
按文件只放入一次
让 /root/eaimw/batch/load.py --db <路径> <文件> 只把通过校验的文件用一个事务入库,并用 load_log 防止重新入库。
用 isolation_level=None 打开,直接写 BEGIN … COMMIT。如果发生理赔编号主键冲突(IntegrityError),就 ROLLBACK,把前面的行也一并回退。
与我们的账本对账
用 /root/eaimw/batch/recon.py 对 claims 与账本对账,并把以今天文件为基准的结果输出到 /root/eaimw/batch/recon.csv。
对两边理赔编号的并集排序后遍历。只遍历一侧,就会漏掉只存在于另一侧的笔。账本用 partner_drop.py --ledger 生成。