CSV의 빈칸 0 미정 상태 보존 실습

목표
raw_qty / state / numeric_qty / missing_reason / rejection_reason을 따로 저장합니다.
고정 계약 fixed-v1: 정확한 빈문자열, N/A, 미정만 missing으로 허용합니다.
N/A는 이 합성 자료 제공자의 해당 없음 계약이며 모든 CSV에 적용하는 기본값이 아닙니다.
not_applicable과 pending의 뜻은 서로 다릅니다. 수치 없는 행 수에 함께 세어도 원래 사유를 바꾸지 않습니다.
실제 0은 정수 0이며, missing의 numeric_qty는 null입니다.

범위
Python 3.12 이상, 표준 라이브러리만 사용합니다. 추가 설치와 API 키 없음.
실제 시험 환경 Linux CPython 3.12.14. Windows/macOS/Excel 앱 실행은 시험하지 않았습니다.
UTF-8 또는 시작 BOM, 쉼표 CSV, 정확한 id,qty 머리글과 두 열.
ID: ASCII 영문 시작, 영문/숫자/밑줄/하이픈 1~32자, 대소문자 구분, 중복 금지.
수량 정수 문법: 0|[1-9][0-9]{0,5} 전체 일치, 0~999999.
1~10000 데이터 레코드, 1000000바이트 이하, 필드당 131072자 이하.
공백 제거/대소문자 변환/Unicode 정규화/소수 절삭/0 대체 없음.
고정 계약 코드를 사용하며 --missing-token 같은 설정 옵션 없음.

실행
새 빈 일반 로컬 연습 폴더에 ZIP을 풉니다. README.txt가 있는 루트에서 실행합니다.
다른 프로그램이 입력이나 폴더를 수정하지 않게 하고 운영 자료/공유/동기화 폴더는 쓰지 않습니다.
출력 부모 폴더는 미리 있어야 하며 아래 명령은 현재 폴더에 아직 없는 새 JSON을 만듭니다.

python --version
python -m unittest discover -s tests -v
python tests/run_fixture_checks.py
python tests/verify_checksums.py
python code/missing_states.py --input fixtures/main.csv --out main_result.json

기대: 46개 unittest OK; fixture CLI21건/CSV16개, all_passed와 all_source_bytes_unchanged true.
main: missing3/zero1/value1/rejected0, 숫자 있는 두 행 0+5=5, 부분 소계 true, 수치 미확정3, 종료0.
main JSON의 numeric_qty 순서: null,0,null,null,5.
missing_reason 순서: empty,null,not_applicable,pending,null.
수치 미확정은 missing+rejected 중 숫자 수량이 없는 행 수이며 모든 해당없음이 대기라는 뜻은 아닙니다.
all_rows_in_contract true와 종료0은 변환 계약 통과이며 전체 숫자 수량이 확정됐다는 뜻은 아닙니다.

python code/missing_states.py --input fixtures/normal.csv --out normal_result.json
python code/missing_states.py --input fixtures/boundary.csv --out boundary_result.json
python code/missing_states.py --input fixtures/missing_only.csv --out missing_only_result.json
python code/missing_states.py --input fixtures/rejected.csv --out rejected_result.json
python code/missing_states.py --input fixtures/recovery.csv --out recovery_result.json

normal: 0+2+3=5, missing0/zero1/value2/rejected0, 부분false, 미확정0, 종료0.
boundary: 0+1+999999=1000000, 빈 인용필드는 empty, 부분true, 미확정1, 종료0.
missing_only: missing3/zero0/value0, 빈 합의 소계0, 부분true, 미확정3, 종료0. 전체수량0 아님.
rejected: 14행, missing0/zero0/value1/rejected13, 부분 소계5/미확정13, 종료1.
recovery: 실제 상태가 미정이라고 제공자가 확인했다는 가상 전제의 별도 사본. missing1(pending)/value1, 부분소계5/미확정1, 종료0. ?를 임의로 미정이나0으로 고치는 절차가 아님.

실패 시험
python code/missing_states.py --input fixtures/failures/wrong_header.csv --out failed_header.json
python code/missing_states.py --input fixtures/failures/late_unclosed.csv --out failed_quote.json
python code/missing_states.py --input fixtures/failures/invalid_utf8.csv --out failed_encoding.json
세 명령 모두 종료2, 새 최종 JSON 없음. 실패 fixture10개는 전체 중단.
종료1은 승인 밖 업무 표기를 보존한 완성 보고서, 종료2는 완료할 수 없는 구조/옵션/접근/저장 오류.
종료1 명령 뒤 &&로 연결한 명령은 실행되지 않을 수 있으니 하나씩 실행합니다.
구조/인코딩/ID는 원자료를 확인하고 올바른 별도 사본에서 전체 재실행합니다.
unknown은 원자료 뜻을 확인합니다. N/A/n/a/공백을 자동으로 같은 값이라고 정하지 않습니다.

재실행과 보호
같은 main_result.json으로 재실행하면 종료2로 기존 결과를 보존합니다.
python code/missing_states.py --input fixtures/main.csv --out main_second.json
원본 읽기만, 기존 파일/폴더/끊어진 출력 링크 보존, 같은 원본/출력 경로 거부.
심볼릭 링크/검출가능 reparse 경로/하드링크 입력/.. 경로 거부.
출력은 같은 부모 폴더의 .p12- 임시파일에 완성/동기화/재읽기 후 하드링크로 새 이름을 만듭니다.
하드링크 미지원 위치는 중단하며 덮어쓰기로 바꾸지 않습니다.
입력 해시는 읽은 바이트 기록과 저장전 재확인이며 지속감시/잠금/보안격리 아님.
정전/강제종료/공격적 동시경로변경/네트워크 저장소까지 보호한다고 보장하지 않습니다.
최종확정 후 임시파일 정리 실패는 완성 출력 보존+경고. .p12- 잔여파일은 완료 결과로 쓰지 않습니다.

정답과 파일
expected JSON6개는 수동으로 정한 상태/원표기/사유/숫자/손계산입니다.
전체 결과에서 source_sha256만 제외해 expected와 비교하며 해시는 입력 바이트와 별도로 비교합니다.
raw_qty는 CSV에서 해석한 필드 문자열입니다. 구분용 인용부호는 값에 포함되지 않습니다.
source_record는 헤더를1로 센 논리 레코드 번호이며 물리 줄번호와 다를 수 있습니다.
고정 기대값을 입력과 함께 고쳐 결과를 맞추지 마세요.

배포 구성
code/missing_states.py 전체 실행 코드
fixtures/*.csv와 fixtures/failures/*.csv 합성 정상/경계/거부/실패 입력16개
expected/*.json 독립 기대JSON6개, expected/hand_check.txt 손계산 기준
tests/test_missing_states.py 자동시험46개
tests/run_fixture_checks.py CLI기대값/원본보존 대조21건
tests/verify_checksums.py 배포체크섬 대조
README.txt, SHA256SUMS.txt
검수 실제 출력/RED-GREEN 로그는 독자 ZIP에 들어있지 않습니다. 실행 결과는 독자가 새 이름으로 만듭니다.
