Building an EAI Middleware Layer
Receive the Claim File, Check It, Load It Once
한국어 원문으로 표시합니다.
목표
제휴 보험사의 청구 파일을 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>)과 받은 파일을 대조한다. 다르면 그 파일을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>: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 로 만듭니다.