새 폴더에서 재현
90분 안팎
학습 목표
새 로컬 임시 폴더에 표본·설정·코드를 복사하여 수집부터 보고서까지 실행합니다.
개념
재현은 숨은 의존성을 찾는 검사입니다
작성자의 컴퓨터에서는 실행되는데 동료의 폴더에서는 실패하는 일이 생깁니다. 기존 DB가 이미 채워져 있거나 이전 실행의 clean.csv가 남아 있으면 누락된 단계가 드러나지 않습니다. 결과물 인계에서는 파일을 열 수 있는지보다 원본에서 같은 결과를 다시 만들 수 있는지를 확인합니다. 새 폴더는 기존 상태의 도움을 끊어 코드·입력·설정의 관계를 드러내는 실험입니다. 성공 화면을 복사하는 대신 동료가 수행할 명령과 비교 기준을 함께 전달합니다.
이 모듈은 Python 3.9 이상과 표준 라이브러리만으로 검증합니다. ZIP 루트에서 bash check.sh를 실행하며 네트워크나 외부 계정이 필요하지 않습니다. 기존 requirements.txt는 앞 단계의 선택 확장 안내이고 현재 SVG 생성에는 설치하지 않습니다. 모든 원본은 교육용 합성 교통·날씨 표본입니다. 실습에는 앞 모듈 solution 전체가 들어 있으며 기존 테스트와 공개 경로를 유지합니다. starter의 delivery.py에서 compare만 완성하고 테스트의 기대값을 바꾸지 않습니다.
복사 목록을 입력과 출력으로 나눕니다
delivery.py의 FILES와 DIRS는 새 공간에 전달할 허용 목록입니다. raw, queries, 설정 JSON과 실행 모듈을 복사하되 etl.sqlite, collected, out, metrics-out, visual-out은 가져가지 않습니다. 생성된 joined.csv도 앞 단계 실행으로 다시 만듭니다. audit.csv는 계약 검사에 필요한 기존 검수 입력이므로 함께 전달합니다. 이름에 audit가 있다는 이유로 모두 출력이라고 분류하면 FileNotFoundError가 납니다. 각 파일의 소비자를 확인하고 입력인지 생성물인지 결정합니다.
copy_inputs는 목적 폴더를 만들고 입력을 복사합니다. TemporaryDirectory는 작업이 끝나거나 예외로 빠져나오면 자신이 만든 임시 공간을 정리합니다. 기존 프로젝트를 삭제하는 명령은 없습니다. 이 방식은 내 원본을 보존하면서 빈 상태를 얻습니다. 테스트가 생성한 공간과 사용자가 내려받은 ZIP 루트를 혼동하지 않습니다. 배포자가 잘못된 입력을 넣었을 때도 임시 경로를 공개 로그에 통째로 남기기보다 파일 이름과 오류 코드로 진단 범위를 좁힙니다.
수집부터 공개까지 순서를 고정합니다
build는 pipeline.run으로 일별 표를 만들고 join_pipeline.run으로 날씨를 결합합니다. cleaning.run은 자료형과 제외 기록을 만들며 metrics.run은 정의 파일에 따라 지표를 계산합니다. visual.generate는 그 지표와 그래프 수치를 대조합니다. 마지막 quality.run은 원본 보존·배치 품질 검사·증분 저장·공개 보고서를 수행합니다. 이전 run.sh는 증분 저장 학습 경로이므로 새 공개 경로의 별칭처럼 쓰지 않습니다. 각 호출의 입력이 이전 호출의 출력인지 읽어 보며 단계 누락을 확인합니다.
configuration은 pipeline.json의 source, snapshot, transformed, database 경로를 해석합니다. 이 표본의 source는 joined.csv이며 저장 키는 지역과 날짜입니다. 폴더 두 곳에서 build를 호출하면 서로 다른 DB를 처음부터 만듭니다. 첫 폴더를 다시 실행하면 중복 재처리도 확인됩니다. 이 세 실행을 비교하되 오래된 정정본을 승인 없이 덮어쓰거나 실제 운영 스케줄러를 등록하지 않습니다. 실습은 단일 실행의 재현성과 키 보존을 검사하며 동시 실행 안전성을 새로 제공하지 않습니다.
바이트 일치와 의미 일치를 구분합니다
report.json의 lineage에는 실행 ID와 시각이 들어갑니다. 같은 원본을 처리해도 이 값은 달라지므로 두 정상 실행의 파일 해시가 다르다는 이유만으로 수치 오류라고 판단하지 않습니다. compare는 unit, rows, sum_vehicles, dates를 추려 비교합니다. SQLite도 파일 전체 바이트 대신 ORDER BY region,date로 정렬한 논리 행을 읽습니다. 키만 비교하면 값 변경을 놓치고 합계만 비교하면 서로 상쇄되는 행 변경을 놓칩니다. DB 전체 논리 스냅샷을 비교하는 이유입니다.
지표 CSV와 chart-data의 각 행도 비교합니다. 같은 합계라도 n이나 지역 그룹이 달라지면 평균의 의미가 달라집니다. 현재 출력은 정의된 순서로 생성되어 CSV 문자열 비교가 가능합니다. 다른 도구로 확장해 행 순서나 숫자 표현을 바꾸면 파싱 후 키별 비교와 허용 오차를 별도로 설계합니다. 여기서는 오차를 임의로 넓혀 통과시키지 않습니다. 재현 검사는 정의된 계약의 일치를 확인하며 표본이 현실을 대표한다는 판단은 하지 않습니다.
서로 다른 세 종류의 합계를 읽습니다
실제 실행에서 raw_rows는 7, raw_total은 470, unique_total은 390입니다. 같은 관측의 동일 중복을 제거한 뒤 일별 표는 6행입니다. 비교 보고서는 통행량과 강수량이 모두 있는 4행만 남겨 합계 300을 사용합니다. raw_total과 report 합계가 다르다는 사실은 이 표본에서는 제외 규칙으로 설명됩니다. 정제 전후 건수와 지표 분모를 확인하지 않고 최종 숫자를 원본 합계로 교체하면 결측 제외의 의미와 원본 추적이 깨집니다.
question.json은 초기 질문에서 rain_mm > 0을 사용했지만 최종 metrics.json은 1mm 이상을 강수 그룹으로 정합니다. 이 차이를 README에 적고 최종 보고서에는 확정된 정의를 연결합니다. 현재 표본에서는 두 경계가 같은 그룹을 만들더라도 다음 입력에 0.5mm가 있으면 결과가 달라집니다. 표본에서 차이가 없다는 이유로 두 정의를 같은 것으로 취급하지 않습니다. 기준을 바꾸면 해당 입력으로 지표와 그래프를 다시 생성하고 변경 이유를 기록합니다.
오류는 처음 어긋난 계약에서 읽습니다
FileNotFoundError는 필요한 파일이 전달되었는지와 실행 위치를 먼저 확인하게 합니다. HASH: traffic은 원본 바이트가 출처 기록과 달라졌다는 뜻입니다. MISMATCH: database는 저장 키나 값 차이, MISMATCH: chart는 그래프 근거의 차이입니다. 모든 예외를 PASS로 바꾸거나 출력 폴더를 통째로 복사하면 빈 공간 검사의 목적을 잃습니다. 오류 필드에 해당하는 입력과 정책을 비교하고 원인이 확인된 뒤 다시 실행합니다. 테스트는 실패 메시지의 필드도 확인합니다.
solution 검사는 앞 모듈 테스트에 새 재현 검사 11개를 추가합니다. 정상 입력, 동일 상태, 키 변경, 공개 합계 변경, 지표 변경, 그래프 n 변경, 같은 폴더 재실행, 두 새 폴더, 출력 제외 복사, 면접 근거 존재, 선택 문서를 확인합니다. starter는 네 가지 변경 상태를 거절하지 못해 실패합니다. 검증을 마치면 README의 실행 안내를 따라 다른 위치에서 다시 확인하고 정상 수치와 한계를 함께 적습니다. 내려받은 결과를 수정할 때 기존 품질 실패 검사를 유지하는 것이 완료 조건입니다.
따라하기
ZIP의 새 공간 재현 명령 실행
실습 starter의 compare를 완성한 뒤 ZIP 루트에서 실행합니다. 참고 출력은 solution을 실행해 확인한 것입니다. 임시 폴더 두 곳과 첫 폴더 재실행 결과를 비교하며 원본 프로젝트의 DB는 사용하지 않습니다.
python3 delivery.py실행 결과
raw_rows=7 raw_total=470 unique_total=390 daily_rows=6
{"after_rows": 6, "after_total": 390, "before_rows": 6, "before_total": 390, "mapping_missing": 0, "rain_missing": 1, "weather_missing": 1}
raw_rows=7 raw_total=470 unique_total=390 daily_rows=6
{"after_rows": 6, "after_total": 390, "before_rows": 6, "before_total": 390, "mapping_missing": 0, "rain_missing": 1, "weather_missing": 1}
raw_rows=7 raw_total=470 unique_total=390 daily_rows=6
{"after_rows": 6, "after_total": 390, "before_rows": 6, "before_total": 390, "mapping_missing": 0, "rain_missing": 1, "weather_missing": 1}
reproduce=PASS rows=4 sum=300
정렬한 키 비교
다음 독립 예제를 Python으로 실행하여 판단 기준을 확인합니다.
keys=[('B','2026-09-01'),('A','2026-09-01')]
print(sorted(keys))실행 결과
[('A', '2026-09-01'), ('B', '2026-09-01')]
안정된 공개 필드 추출
다음 독립 예제를 Python으로 실행하여 판단 기준을 확인합니다.
report={'rows':4,'sum_vehicles':300,'run_id':'new'}
print({k:report[k] for k in ('rows','sum_vehicles')})실행 결과
{'rows': 4, 'sum_vehicles': 300}
필드별 불일치 진단
다음 독립 예제를 Python으로 실행하여 판단 기준을 확인합니다.
a={'chart':[{'n':'1'}]}; b={'chart':[{'n':'2'}]}
print('MISMATCH: chart' if a['chart']!=b['chart'] else 'PASS')실행 결과
MISMATCH: chart
확인 문제
실습
delivery.py의 compare를 완성합니다. database, report, metrics, chart를 모두 비교하여 첫 불일치에 ValueError("MISMATCH: 필드명")를 발생시키고 같으면 True를 반환합니다. 네 변경 상태가 starter에서 실패해야 하며 solution은 전 검사 통과해야 합니다. bash check.sh는 기존 품질·중복·복구, 새 폴더·재실행, 키·공개 합계·지표·차트 변경 거절과 근거 파일 존재를 검사합니다. 원본과 테스트는 바꾸지 않습니다.
실행 명령
bash check.sh
기대 결과
기존 검사와 새 재현 검사 통과, 선택 HTTP 9개 skip, reproduce=PASS rows=4 sum=300
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 매일 같은 파일을 읽는 처리에서 중복 적재를 막는 방법을 설명해 주시면 됩니다.
- 보고서의 수치와 원본 데이터가 다를 때 확인할 순서를 설명해 주시면 됩니다.