Turning an Incoming File Away Before You Load It
한국어 원문으로 표시합니다.
목표
상대 기관이 보내온 고정폭·CSV 수신 파일을 적재하기 전에 검사하고, 어긋나면 파일 전체를 거절하는 문지기 filegate.py 를 만든다. 판정은 기계가 읽는 보고서와 종료 코드, 그리고 상대에게 돌려줄 거절 파일로 남긴다.
왜 중요한가
적재한 뒤에 틀린 줄을 찾는 일은 문 앞에서 거절하는 일보다 언제나 비싸다. 이미 들어간 줄은 다른 배치가 물고 갔고, 되돌리려면 정정 전표와 사과 전화가 따라온다. 998건이 멀쩡해도 2건이 규격에서 어긋나면 그 파일은 한 줄도 넣지 않는다. 절반만 넣으면 상대의 트레일러와 우리 원장이 영영 맞지 않는다. 고정폭 파일의 폭은 글자 수가 아니라 바이트다. 한글 한 글자는 CP949 에서 2바이트, UTF-8 에서 3바이트라서, 인코딩이 바뀌면 줄 길이 자체가 달라진다. CSV 는 인용 안의 쉼표와 줄바꿈을 구분자로 세지 않아야 한다. 채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 자기가 만든 수신 파일을 차려 놓고 여러분의 스크립트를 실행해 판정과 오류 코드를 대조한다. 기관 코드와 건수, 금액은 실행마다 바뀐다.
단계
/root/bankfile/gen_inbound.py를 만들어 실행해/root/bankfile/inbound/에 상대 기관 네 곳의 하루치 파일 5개를 만든다./root/bankfile/filegate.py가 CSV 수신 파일의 뼈대와 트레일러 건수·합계를 대조하고 보고서와 종료 코드를 내게 한다.- filegate.py 에 고정폭(.txt) 처리를 넣는다. 한 줄이 정확히 80바이트인지 글자 수가 아니라 바이트로 잰다.
- filegate.py 가 규격과 다른 인코딩으로 온 파일을 ENC 로 거절하게 한다. 파일마다 읽은 인코딩을 보고서에 적는다.
- filegate.py 의 CSV 파싱을 RFC 4180 대로 고친다. 인용된 쉼표와 줄바꿈은 필드 구분자가 아니다.
- filegate.py 에 필드 단위 검증을 넣고, 거절한 파일마다 거절 파일을 남긴다. 오류가 하나라도 있으면 그 파일은 한 줄도 적재하지 않는다.
- 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 입니다. - 고정폭은 한 줄이 정확히 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 <거절디렉터리> - 보고서:
{"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, 하나라도 거절 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 자르기, 오류 난 줄만 빼고 나머지를 적재하기, 문지기가 파일을 고쳐 주기.
하루치 수신함 만들기
/root/bankfile/gen_inbound.py 를 만들어 실행해 /root/bankfile/inbound/ 에 파일 5개를 만드세요. 고정폭(CP949) 120건 파일과 그것을 바이트까지 그대로 다시 보낸 재전송본, CSV 90건(수취인명에 쉼표가 든 줄 3개 이상), 트레일러 건수가 실제보다 1 작은 CSV 60건, 그리고 고정폭인데 UTF-8 로 온 파일입니다.
이름은 IN-<YYYYMMDD>-<기관코드 6자리>-<꼬리>.txt|.csv 입니다. 고정폭의 폭은 바이트라서 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 고정폭 처리를 넣으세요. 한 줄이 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 로 감싸세요. 따옴표가 닫히지 않으면 뒷줄이 통째로 한 필드로 빨려 들어가 필드 수가 맞지 않게 됩니다.
필드 검증과 거절 파일, 그리고 부분 적재 금지
/root/bankfile/filegate.py 에 거래번호·수취인명·계좌·금액 검증을 넣고, 거절한 파일마다 <거절디렉터리>/<파일이름>.reject.csv 를 머리글 line,code,detail 로 남기세요. 오류가 하나라도 있으면 그 파일은 수용 목록에 들어가면 안 됩니다.
거래번호는 파일 안에서 유일해야 하고, 겹쳤다면 뒤에 나온 줄을 가리킵니다. 고정폭에서 잘라 낸 필드는 좌우 공백을 떼고 봅니다. 거절 파일은 상대 기관의 배치가 그대로 읽을 수 있어야 하므로 사람 문장이 아니라 표로 냅니다.
다시 온 파일과 충돌한 파일 가르기
/root/bankfile/filegate.py 가 이미 수용한 기관·일련번호와 같은 파일을 DUP 로 거절하게 하세요. 파일 해시가 같으면 detail 에 resend, 다르면 conflict 를 적고 보고서에 sha256 을 남깁니다.
일련번호는 헤더 레코드에 있습니다. 앞에서 수용된 파일만 기억해야 합니다 - 거절된 파일은 적재되지 않았으니 뒤에 온 같은 번호가 중복이 아닙니다. 해시는 hashlib.sha256 으로 파일 바이트 전체에서 구합니다.
자기 수신함을 처리하고 수신 보고서 내기
자기 수신함을 처리해 /root/bankfile/gate_report.json 과 /root/bankfile/rejected/ 를 남기고, /root/bankfile/receipt.md 에 ## 수신 요약 ## 거절한 파일과 사유 ## 재전송으로 판정한 파일 ## 적재하지 않은 이유 ## 상대 기관에 요청할 것 다섯 절을 쓰세요.
앞 단계에서 만든 /root/bankfile/filegate.py 를 그대로 씁니다. 요약에는 수용 건수·거절 건수와 실제로 적재될 금액 합을 숫자로 적습니다. 거절한 파일은 이름을 그대로 적어야 상대가 찾습니다. 채점기는 /root/bankfile/inbound 를 다시 읽어 여러분의 보고서와 대조하므로, 보고서는 손으로 쓰지 말고 filegate.py 가 낸 것을 쓰세요.