P51 두 CSV와 외래키·트랜잭션 경계 실습
모든 자료는 합성입니다. Python 3.12 이상 표준 sqlite3 모듈을 사용합니다.
기존 DB 경로를 받지 않고 매 실행에 새 폴더/새 practice.sqlite3를 만듭니다.
운영 DB 수정 예제가 아닙니다. 외부 서버/계정/패키지 설치는 필요 없습니다.

압축을 푼 폴더에서:
python3 atomic_import.py fixtures/departments.csv fixtures/transactions.csv out-normal
종료0, 새 부서2+거래3행 커밋. 기준행을 포함한 총 부서3+거래4행.
기준행은 D0/기존부서, T0/D0/7입니다. expected/normal_rows.json과 대조하세요.

python3 atomic_import.py fixtures/departments.csv fixtures/unknown_department.csv out-unknown
종료1, 추가0+0. 기준 부서1+거래1행은 남습니다.
report.json과 practice.sqlite3를 보존합니다. expected/rollback_rows.json과 대조하세요.
같은 명령에서 duplicate_transaction.csv, bad_quantity.csv도 각각 새로운 폴더명으로 실행하세요.
같은 출력폴더 재실행은 종료2이며 기존 결과를 덮어쓰지 않습니다.

python3 test_import.py : 11개 단위 검산. logs/cli_actual.json : 실제 7개 CLI 검산.
source_fixtures_preserved=true는 해당 검산에서 원본 바이트 보존을 확인했다는 뜻입니다.
출력 DB의 파일 SHA가 롤백 전후 같다고 주장하지 않습니다. 행 내용을 대조합니다.

PRAGMA foreign_keys=ON은 BEGIN 이전에 연결별로 설정하고 실제 값1을 확인합니다.
한 BEGIN 안에 부서/거래 INSERT와 기대 행 대조를 둡니다.
autocommit=True에서는 SQL COMMIT/ROLLBACK을 명시합니다.
BEGIN 이전의 기준행은 별도로 커밋되어 있어 실패 후에도 남습니다.
SQLite 트랜잭션은 DB 내부 변경 범위이며 CSV/JSON/폴더를 한 번에 되돌리지 않습니다.
종료2는 저장·입력·예상 밖 실행 오류입니다. DB가 이미 커밋됐을 수도 있으므로 완료로 사용하지 마세요.
원본을 수정하지 말고 사본에서 원인 확인 후 새로운 출력폴더로 재실행하세요.
Windows/macOS, 동시 쓰기, 전원 장애, 네트워크 파일 시스템은 검증하지 않았습니다.

python3 test_cli.py : 추가 CLI 반례를 새 임시 폴더에서 재실행합니다. 제공 로그·기대값·원본은 덮어쓰지 않습니다.
