소수 금액 CSV를 Decimal로 계산하고 반올림 기준 남기기
P07 실습 자료 / 버전 1.0.0 / 검수일 2026-10-05

1. 준비
새 빈 연습 폴더에 ZIP을 풀고 터미널을 그 폴더로 옮깁니다.
Python 3.10 이상 표준 라이브러리만 사용합니다. 별도 설치 패키지는 없습니다.
실제 실행 검수는 Linux의 Python 3.12.14에서 했습니다.
Windows와 Python 3.10·3.11은 실행하지 않았습니다.

python --version

위 명령의 python이 없으면 설치된 Python의 명령 이름을 확인하고 아래 명령의
python도 같은 이름으로 바꿉니다. 업무 자료 대신 포함한 가상 CSV부터 사용합니다.
가상 금액과 USD 표시는 이 연습의 계약이며 실제 청구·세금·회계 규정이 아닙니다.

2. 첫 실행
input/amounts.csv 내용:
record_id,currency,amount
0001,USD,0.10
0002,USD,0.20
0003,USD,1.005
0004,USD,1.005

python code/decimal_csv.py --input input/amounts.csv --report out/amounts.json

정상 종료코드: 0
화면 기대 문구:
원합계 2.310 / 합계 후 2.31 / 행별 후 합계 2.32 / 차이 0.01

out 폴더는 필요하면 자동 생성합니다. input 원본은 쓰지 않습니다.
보고서 금액은 문자열입니다. ID 0001~0004, 원금액 원문, 행 순서를 보존합니다.
원합계 raw_total=2.310
주 결과 total_after_rounding=2.31
비교 결과 sum_after_row_rounding=2.32
차이 row_rounding_delta=0.01 (행별 후 합계 - 합계 후 반올림)
행별 rounded_amount=0.10,0.20,1.01,1.01
policy.quantum=0.01, rounding=ROUND_HALF_UP, primary_rounding_stage=after_total
전체 보고서는 expected/amounts.json과 비교합니다.

3. 입력 계약
UTF-8 또는 UTF-8 BOM, 쉼표 구분, 2,000,000바이트 이하
머리글 record_id,currency,amount 순서, 각 레코드 정확히 3열
ID는 ASCII 영문·숫자·밑줄·하이픈 1~32자. 대소문자 구분, 중복 거부
통화는 정확히 USD만 허용. 환산하지 않으며 KRW·EUR·usd·USD/kg는 거부
금액은 부호 없는 ASCII 정수 또는 일반 소수, 불필요한 정수부 앞자리 0 거부
소수부가 있으면 1~4자리. 금액 범위 0~999999.9999, 데이터 1~10000행
음수·+부호·-0·공백·지수·NaN·Infinity·단위기호·빈 문자열은 거부
공백을 자동 제거하거나 잘못된 값을 0으로 대체하지 않습니다.
CSV 구문·열 수·ID·통화·금액에 오류가 있으면 전체 보고서를 보류합니다.

금액 문자열은 float를 거치지 않고 Decimal(amount_text)로 직접 읽습니다.
계산 precision 28은 유효숫자 정밀도이며 출력 소수자리 수가 아닙니다.
원합계 상한은 999999.9999 x 10000 = 9999999999.0000입니다.
정수부 10자리+소수부 4자리=최대 14자리이고, 행별 반올림 후 합계 상한
10000000000.00도 13자리이므로 28자리에 충분히 들어갑니다.
비음수 입력만 허용하므로 중간 합계가 최대 전체 합계를 넘지 않습니다.
합산 단계는 Inexact trap을 켜고, 의도된 quantize 반올림에서는 끕니다.
출력은 quantum 0.01과 ROUND_HALF_UP을 명시해 소수 둘째 자리로 반올림합니다.

4. 경계 연습
python code/decimal_csv.py --input input/zero.csv --report out/zero.json
python code/decimal_csv.py --input input/boundary.csv --report out/boundary.json
python code/decimal_csv.py --input input/precision4.csv --report out/precision4.json

zero.csv: 원합계 0.0000, 합계 후 0.00, 행별 후 합계 0.00, 차이 0.00
boundary.csv: 원합계 999999.9999, 합계 후 1000000.00, 행별 후 합계 1000000.00, 차이 0.00
precision4.csv: 원합계 1.0005, 합계 후 1.00, 행별 후 합계 1.00, 차이 0.00
input/bom_crlf.csv는 UTF-8 BOM·CRLF·인용 필드의 정상 예시입니다.
정상 5개 입력의 고정 기대 합계는 expected/summaries.json에 있습니다.
입력 범위 검사는 반올림 전 금액에 적용되므로 입력 1000000은 거부하지만
999999.9999를 반올림한 출력 1000000.00은 정상입니다.

5. 실패와 재실행
python code/decimal_csv.py --input input/failures/nan.csv --report out/rejected_nan.json
python code/decimal_csv.py --input input/failures/unapproved_currency.csv --report out/rejected_currency.json
python code/decimal_csv.py --input input/failures/precision5.csv --report out/rejected_precision.json
python code/decimal_csv.py --input input/failures/duplicate_id.csv --report out/rejected_id.json

네 명령은 종료코드 2, 지정한 새 보고서 부재가 기대값입니다.
NaN을 0으로 바꾸거나 KRW라는 이름만 USD로 바꾸지 않습니다.
오류가 난 원문·통화·자릿수·중복의 원인을 공급 자료와 확인하세요.
원본을 덮어쓰지 말고 작업용 사본에서 수정 후 새 이름의 보고서로 다시 실행합니다.
input/failures에는 부호, 공백, 지수, 비유한값, 통화, 범위, 자릿수, 중복,
머리글, 열 수, UTF-8, 따옴표, 빈 행, 데이터 없는 파일의 총 17개 반례가 있습니다.
invalid_utf8.csv는 의도적으로 올바르지 않은 UTF-8 바이트를 포함합니다.

처음 만든 out/amounts.json을 같은 이름으로 재실행하면 종료코드 2로 멈추며
기존 보고서 바이트를 보존합니다. 새 보고서는 다음 명령으로 만듭니다.
python code/decimal_csv.py --input input/amounts.csv --report out/amounts_again.json

입력과 출력은 다른 경로여야 하고 보고서는 .json으로 지정합니다.
기존 파일과 링크를 덮어쓰지 않도록 배타적 생성 모드 x를 사용합니다.
source_record는 머리글을 1로 센 논리 레코드 번호이며 물리 줄 번호와 다를 수 있습니다.
source_sha256은 읽어들인 바이트의 기록이지 원본 변경을 막는 잠금이 아닙니다.

6. 재검산
python -m unittest discover -s code -p "test_decimal_csv.py" -v
python code/verify_examples.py

단위시험 32개와 일부 반복 하위시험이 모두 통과했습니다.
verify_examples.py는 매번 새 임시 폴더에 입력을 복사해 CLI 25건을 실행하고
정상 기대값·전체 보고서·실패 출력 부재·기존 출력 보존·재실행을 검사합니다.
제공 입력 22개 CSV의 실행 전후 SHA-256이 같았습니다.
시험은 사용자 out 폴더를 지우거나 기존 보고서를 덮어쓰지 않습니다.

logs/tdd_red.txt: 기능 구현 전 placeholder에 대한 실패 실행(32개 시험, 하위시험 포함 59실패)
logs/tdd_green.txt: 최종 코드에서 32개 시험 통과
logs/cli_verification.json: 실제 격리 CLI 25건의 종료코드·출력·입력 해시
logs/runtime.json: 실제 Python·OS 버전
SHA256SUMS.txt 및 manifest.json: 각 포함 파일의 바이트 수와 SHA-256
manifest.json과 SHA256SUMS.txt 자체는 자기 해시 목록에서 제외합니다.

처리된 일부 쓰기 오류에서는 새 출력 삭제를 시도합니다. 한 시험은 실제 임시
파일에 일부 내용을 쓴 후 OSError를 주입하여 불완전한 새 출력이 제거되는지 검사합니다.
실제 저장장치 고장 재현, 강제 종료, 정전, 동시 실행, 파일 삭제 실패는 검증하지 않았습니다.
중단 후 남은 파일을 완료 보고서로 사용하지 마세요.

7. 공식 자료
https://docs.python.org/3.12/library/decimal.html
https://docs.python.org/3.12/library/csv.html
https://docs.python.org/3.12/library/functions.html#open
https://docs.python.org/3.12/library/unittest.html#test-discovery
자료 확인일 2026-10-05. Python 3.12 문서와 실제 실행 3.12.14를 구분합니다.

업무 적용 전에는 통화, 입력 부호의 의미, 정밀도, 범위, 반올림 모드와 시점을
해당 자료의 계약에 맞게 먼저 확정하세요. 고정 기대값을 결과에 맞춰 고치지 않습니다.
