고객 데이터 다루기 · 말없이 바뀌는 수신 스키마 · 실습
13주차에 매출이 0 이 됐다 — 수신 계약을 코드로
목표
주마다 오는 파일에서 스키마를 뽑아 지문으로 굳히고, 칼럼 추가·삭제·이름 변경·타입 변경을 분류해 호환 규칙으로 자동 판정하는 도구 schema.py 를 만든다. 마지막에는 이름도 타입도 그대로인 채 뜻만 바뀐 칼럼을 값 분포로 잡아 낸다.
왜 중요한가
남이 주는 파일의 스키마는 통보 없이 바뀐다. 보내는 쪽은 자기 시스템의 필드를 정리한 것이고, 받는 쪽은 그것을 계약으로 쓰고 있었을 뿐이다. 우리가 바꾸는 쪽을 통제할 수 없으므로, 할 수 있는 일은 바뀌었다는 사실을 파이프라인이 먼저 알아채게 만드는 것뿐이다.
알아챈 다음이 더 중요하다. 모르는 칼럼이 하나 늘어난 것 때문에 적재를 멈추면 아무도 그 경고를 신뢰하지 않게 되고, 필수 칼럼이 사라졌는데 그냥 넘어가면 숫자가 조용히 틀린다. 그래서 무엇이 깨지고 무엇이 안 깨지는지를 규칙으로 못박아 둔다. 기본은 두 줄이다 — 모르는 칼럼은 통과, 없어진 필수 칼럼은 중단.
그리고 스키마 검사가 절대 못 보는 것이 하나 있다. 금액의 단위가 원에서 천원으로 바뀌면 칼럼 이름도 타입도 그대로다. 검사는 전부 통과하고 매출만 1000분의 1이 된다. 이런 변화는 값의 분포로만 보이고, 분포는 증거가 아니라 단서다.
채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 주간 파일을 차려 놓고 여러분의 도구를 실제로 실행해 분류와 판정을 대조한다. 상호와 금액은 실행마다 바뀐다.
단계
1. /root/drift/gen_weeks.py 를 만들어 실행해 /root/drift/weeks 아래 w01.csv 부터 w07.csv 까지 만드세요. 주문 30건은 내내 같고 스키마만 달라집니다.
2. /root/drift/schema.py 에 fingerprint 를 만들어 칼럼 이름·타입·값 표본과 지문을 내게 하고, w01.csv 의 지문을 /root/drift/baseline.json 에 저장하세요.
3. diff <기준 JSON> <파일> 을 더해 칼럼 추가와 삭제를 분류하게 하세요.
4. diff 가 값 표본이 크게 겹치는 짝을 찾아 이름 변경으로 분류하게 하세요. 이름 변경으로 잡힌 칼럼은 추가·삭제 목록에서 빠집니다.
5. diff 가 같은 칼럼의 타입 변경을 분류하게 하세요. 이름이 바뀌면서 타입도 바뀐 경우까지 봅니다.
6. /root/drift/contract.json 에 필수·선택 칼럼과 규칙을 선언하고, gate <기준 JSON> <파일> 이 pass·warn·stop 을 판정하게 하세요.
7. watch <기준 CSV> <파일> 을 더해 숫자 칼럼의 중앙값 변화를 재게 하고, 단위가 바뀐 주차를 /root/drift/meaning.json 에 적으세요.
8. 일곱 주차를 한 번에 판정해 /root/drift/drift_report.json 과 /root/drift/drift_report.md 를 만드세요.
참고
- 실행 계약:
python3 /root/drift/schema.py fingerprint <파일>·diff <기준 JSON> <파일>·gate <기준 JSON> <파일>·watch <기준 CSV> <파일>. 성공하면 종료 코드 0, 파일이 없으면 3, 명령이나 인자 수가 틀리면 2 입니다. fingerprint응답:{"rows": 정수, "columns": [{"name": 이름, "type": 타입, "sample": [값...]}], "digest": 12자 16진수}. 표본은 그 칼럼의 서로 다른 값을 정렬해 앞에서 20개까지입니다.- 지문(digest)은
[[이름, 타입], ...]을 공백 없는 JSON 으로 만들어 sha256 을 구하고 앞 12자를 씁니다. 표본은 지문에 넣지 않습니다 — 데이터가 바뀌어도 스키마가 같으면 지문은 같아야 합니다. - 타입 잣대는 이 실습의 가정입니다. 빈 값을 뺀 나머지가 전부 정수 모양이면
int, 소수까지 포함하면float, 전부YYYY-MM-DD면date, 그 밖은str, 값이 하나도 없으면empty입니다. diff응답:{"added": [이름...], "removed": [이름...], "renamed": [[옛이름, 새이름]...], "retyped": [[이름, 옛타입, 새타입]...]}.- 이름 변경은 사라진 칼럼과 생긴 칼럼의 값 표본 자카드 유사도가 0.8 이상일 때로 봅니다. 이 문턱값도 이 실습의 가정입니다.
gate응답:{"verdict": "pass"|"warn"|"stop", "reasons": [문자열...]}. reasons 는 정렬해서 냅니다. 필수·선택 칼럼 목록은/root/drift/contract.json에서 읽습니다.- 판정 규칙: 모르는 칼럼은 통과, 없어진 선택 칼럼은 warn, 없어진 필수 칼럼은 stop, 이름 변경은 warn,
int에서float로 넓어진 것은 warn, 그 밖의 타입 변경은 필수면 stop 선택이면 warn 입니다. watch응답:{"columns": {이름: {"median_before": 수, "median_after": 수, "ratio": 수, "flag": true|false}}}. 중앙값은 소수 셋째 자리, 비율은 넷째 자리에서 반올림합니다. flag 는 비율이 3 이상이거나 3분의 1 이하일 때 true 입니다.- 공식 문서: [python csv](https://docs.python.org/3/library/csv.html) · [python hashlib](https://docs.python.org/3/library/hashlib.html) · [python statistics](https://docs.python.org/3/library/statistics.html) · [python json](https://docs.python.org/3/library/json.html)
- 흔한 실수: 지문에 값 표본까지 넣어 데이터가 바뀔 때마다 지문이 달라지기, 이름 변경을 이름만 보고 판단하기, 모르는 칼럼에 멈추기, 분포 변화를 증거로 다루기.
단계 8개
- 일곱 주치 수신 파일 만들기
- 스키마를 지문으로 굳히기
- 늘어난 칼럼과 없어진 칼럼 가르기
- 이름만 바뀐 칼럼 찾아내기
- 타입이 바뀐 칼럼 가려내기
- 호환 규칙을 코드로 못박기
- 스키마가 못 보는 변화 잡기
- 일곱 주를 한 장으로 보고하기