보험 도메인 심화 · 서류가 진짜 그 서류인가 · 이론
영수증이 두 번 쓰였는데 파일 이름은 달랐다
한 줄 요약
청구 증빙 점검은 파일이 다 왔는지 세는 일이 아니라, 그 파일이 정말 그 서류인지를 이름이 아닌 내용으로 판정하는 일이다. 형식은 매직 바이트로, 동일성은 내용 해시로, 기록과 실물의 어긋남은 양방향 대조로 본다.
왜 이게 필요했나
보험 청구에서 서류는 지급 판단의 근거다. 그런데 접수 창구가 여럿이고 채널이 여럿이면, 같은 시스템에 들어오는 파일의 상태가 제각각이 된다. 스캐너가 만든 PNG 에 담당자가 .pdf 를 붙여 올리고, 파일 이름에 피보험자 이름을 넣어 보내고, 같은 영수증을 이름만 바꿔 두 청구에 낸다. 심사자는 화면에 열리는 것만 보기 때문에 이 셋 다 눈으로는 잘 안 잡힌다.
점검을 사람의 눈에 맡기면 세 가지가 동시에 무너진다. 서류가 덜 왔는데 왔다고 처리되고(지급 근거가 비고), 같은 서류가 두 번 지급 근거로 쓰이고(중복 지급), 파일 이름에 실린 개인정보가 목록·백업·로그로 퍼진다. 셋 다 나중에 발견되면 되돌리기가 매우 비싸다. 반대로 접수 시점의 자동 점검은 값싸다 — 필요한 것은 규칙표 하나와 파일의 앞 몇 바이트, 그리고 해시뿐이다.
어떻게 동작하나
체크리스트는 코드가 아니라 데이터로 둔다. 청구 유형마다 필요한 서류가 다르고 그 목록은 상품과 규정이 바뀔 때마다 움직인다. 유형과 서류 종류를 두 컬럼으로 가진 표에 넣어 두면 점검기는 표만 읽으면 되고, 규정이 바뀌어도 코드를 고치지 않는다. SQLite 처럼 컬럼 타입이 느슨한 저장소에서는 값의 저장 클래스가 무엇인지도 함께 알아 두는 편이 좋다([SQLite 데이터 타입](https://www.sqlite.org/datatype3.html)).
형식은 앞부분 바이트로 가른다. 확장자는 사람이 붙인 이름표라 아무 보장이 없다. PDF 는 시작이 %PDF- 라고 [RFC 8118](https://www.rfc-editor.org/rfc/rfc8118.html)의 미디어 타입 등록 항목이 못박고 있고, PNG 는 첫 여덟 바이트가 십진수로 137 80 78 71 13 10 26 10 이라고 [RFC 2083](https://www.rfc-editor.org/rfc/rfc2083.html) 3.1 절이 정의한다. JPEG 는 FF D8 FF 로 시작하고 ZIP 은 PK 뒤에 03 04 가 온다. 리눅스의 file 명령이 하는 일도 결국 이 표를 보는 것이다.
여기서 반드시 지킬 것이 하나 있다. 이 파일들에는 NUL 바이트가 섞여 있으므로 grep 을 물리면 안 된다. GNU grep 은 NUL 이 섞인 입력을 이진 파일로 보고 "Binary file matches" 만 내거나 조용히 아무것도 못 찾는다. 앞이 NUL 로 시작하는 형식일수록 진짜 파일에서 확실히 떨어진다. od -c 로 보거나, 파이썬에서 open(path, "rb").read(16) 으로 읽어 비교한다.
동일성은 내용 해시로 본다. 같은 서류인지 파일 이름으로는 절대 말할 수 없다. [hashlib](https://docs.python.org/3/library/hashlib.html)로 파일 내용의 sha256 을 구해 같은 값끼리 묶으면, 이름을 바꿔 다시 낸 서류가 한 묶음으로 드러난다. 해시를 사람이 읽는 목록에 실을 때는 16진수로 적는 것이 보통이고, 바이트를 그대로 텍스트에 담아야 할 때 base16·base32·base64 의 자리를 정리해 둔 문서가 [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648.html)이다. 경로를 다루는 코드는 문자열 연산 대신 [pathlib](https://docs.python.org/3/library/pathlib.html)을 쓰면 확장자와 이름을 가르는 실수가 줄어든다.
기록과 실물은 양방향으로 맞댄다. 접수 기록에는 있는데 파일이 없는 것과, 파일은 있는데 기록이 없는 것은 서로 다른 사고다. 앞쪽은 지급 근거가 비어 있는 것이고, 뒤쪽은 누구의 것인지 모르는 개인정보가 남아 있는 것이다. 한 방향만 보는 점검기는 늘 절반을 놓친다.
doc_rule(규칙표) ─┐ ├─▶ 빠진 서류 ─┐submission(기록) ─┤ ├─▶ 청구별 보완 요청 목록 ├─▶ 고아 / 없는 파일 ┤inbox(실물) ──────┴─▶ 형식 · 해시 · 이름 ┘현장에서 만나는 모습
기한 계산의 기산일을 잘못 잡는 일이 아주 흔하다. 접수일을 기준으로 세면 늦게 접수한 건일수록 기한이 늘어난다. 기산일은 사고일이어야 하고, 그 규칙은 문서 한 줄로 적어 점검기와 함께 둔다. 이 실습에서는 사고일부터 30일을 기한으로 잡는데, 이것은 실제 법정 기한이 아니라 이 실습의 가정이다. 상품과 회사마다 다르므로 현장에서는 반드시 그 회사의 규정을 확인해야 한다.
파일 이름의 개인정보도 자주 그대로 남는다. 내용은 비식별 처리를 하면서 이름은 손대지 않는 경우가 많은데, 목록 화면, 백업 색인, 에러 로그, 심지어 보고서 파일명까지 그 이름을 그대로 실어 나른다. 점검 보고서 자체가 같은 값을 한 번 더 퍼뜨리지 않도록, 보고서에는 건수와 규칙만 적고 값은 싣지 않는다.
실무에서 진짜 중요한 것
- 형식 판정은 이름이 아니라 내용으로. 그리고 판정 결과를 표로 남겨 나중에 다시 셀 수 있게 한다.
- 같은 서류의 재사용은 해시로만 잡힌다. 사람이 볼 목록에는 근거(어느 청구와 묶였는지)를 함께 낸다.
- 빠진 서류와 늦은 서류는 처리가 다르다. 보완 요청 목록은 사유별로 갈라 적는다.
- 점검기는 접수함을 고치지 않는다. 파일을 옮기거나 이름을 바꾸는 것은 합의된 절차가 따로 있다.
다음 실습에서 할 것
다온손해보험(가상)의 청구 60건과 접수함 파일 189개를 직접 만들고, 규칙표로 빠진 서류를 찾습니다. 확장자를 믿지 않는 형식 판정기를 만들어 접수함 전체의 판정표를 내고, 내용 해시로 다시 쓰인 서류를 묶고, 파일 이름에 실려 온 개인정보를 셉니다. 기한을 사고일에서 계산하고, 기록과 실물을 양방향으로 맞댄 뒤, 청구별 보완 요청 목록과 보고서로 마무리합니다.