codyssey-b2-1

평가항목 체크리스트 답변 — B2-1 나만의 용돈 기입장

평가항목 문서(B2-1 평가항목.md)의 체크리스트 16개 항목에 대해 충족 여부 → 답변(1~2줄) → 근거 위치(URL) 순서로 정리했습니다. 근거 링크는 GitHub main 브랜치 기준이며, 문서 링크는 ?plain=1#L줄번호 로 해당 줄을 가리킵니다.

저장소: https://github.com/newids/codyssey-b2-1

점검 결과 요약

구분 항목 수 충족 이번에 보완한 것
항목 1 (기능 동작) 7 7 -
항목 2 (구조 설명) 3 3 -
항목 3 (개념 설명) 3 3 -
항목 4 (설계 판단) 3 3 import --strict(롤백 방식) 추가, 10만 건 병목/개선 답변(Q15), 깨진 행 처리 답변(Q16) 추가

이 문서를 바탕으로 받은 AI 사전평가 두 차례(2026-10-17, 모두 16/16 통과)의 항목별 “보완” 제안도 반영했습니다. 제안별 조치는 문서 끝의 1차 반영표와 2차 반영표에 있습니다.

점검 중 모자란다고 본 것은 항목 4의 마지막 두 개였습니다. “병목과 개선”은 한계 목록(Q14)에만 짧게 있었고, “깨진 행 처리”는 부분 성공 한 가지만 구현되어 있어 롤백 전략을 비교해 설명하기 어려웠습니다. 두 가지 모두 코드와 답변을 보강했고, 테스트는 97개로 늘어 모두 통과합니다.


항목 1 — 기능 동작

1-1. add/list/search/summary/export/import/update/delete가 요구사항대로 동작하는가?

충족. 8개 명령 모두 python -m budget_app <command> 로 동작하며, 명령마다 E2E 테스트가 실제 진입점(main)을 호출해 출력과 종료 코드를 검증합니다. add 는 대화형, 나머지는 --옵션 방식이고 update 는 옵션 방식으로 고정했습니다.

1-2. 프로그램 재실행 후에도 거래/카테고리/예산 데이터가 유지되는가? (저장 파일 3개 이상)

충족. ./data 에 transactions / categories / budgets / recurring / meta 5개 JSONL 파일로 저장하며, 첫 실행 때 없는 파일을 자동 생성합니다. 테스트는 create_app 을 다시 호출해 새 프로세스처럼 읽어도 예산이 남아 있는지 확인합니다.

1-3. category add/list/remove가 정상 동작하는가? (삭제 시 사용 중인 카테고리 처리 포함)

충족. 사용 중인 카테고리는 사용 건수를 알려 주며 삭제를 막고, --replace-with 를 주면 거래와 반복 규칙을 대체 카테고리로 옮긴 뒤 삭제합니다. 대체 카테고리도 등록된 것이어야 하고, 삭제 대상과 같으면 거부합니다. 세 파일은 반복 규칙 → 거래 → 카테고리 순서로 처리해, 도중에 실패해도 지워진 카테고리를 가리키는 데이터가 남지 않고 재실행하면 이어서 끝납니다(한 트랜잭션은 아님). 실패하면 완료: ... / 남음: ... 힌트로 어느 단계까지 됐는지 알려 줍니다.

1-4. budget set이 저장되며, summary에서 예산 사용률/초과 여부가 출력되는가?

충족. budget set 은 budgets.jsonl 에 월별로 저장(같은 달은 덮어쓰기)되고, summary 는 예산이 있으면 사용률 N% 를, 80% 이상이면 [주의], 초과하면 [경고] 예산을 N원 초과했습니다! 를 출력합니다. budget remove --month 로 지우면 그 달의 예산 줄과 경고가 요약에서 사라집니다. 변경·삭제가 요약에 미치는 영향은 README §3 표에 있습니다.

1-5. import/export가 명시된 CSV 스키마(UTF-8, 헤더, 컬럼)로 동작하는가?

충족. 스키마 date,type,category,amount,memo,tags 를 상수로 고정해 export 헤더와 import 검증에 같이 쓰며, UTF-8(BOM 포함)로 읽고 씁니다. export 한 파일을 그대로 import 해도 메모의 쉼표·따옴표·여러 태그가 보존됩니다. 엑셀의 cp949 저장·세미콜론 구분자·BOM 에 대한 주의는 README §5 와 import --help 에 적었습니다.

1-6. 잘못된 입력/파일 오류에서 스택트레이스 없이 오류 메시지와 해결 힌트를 출력하는가?

충족. 모든 의도된 오류는 BudgetAppError(message, hint) 로 던지고, handle_errors 데코레이터가 [오류] 원인 / [힌트] 해결 방법 두 줄로 바꿔 표준 오류에 출력합니다. 깨진 저장 파일은 몇 번째 줄인지, 잘못된 CSV 는 몇 행인지 알려 주며, 예기치 못한 예외도 스택트레이스 없이 처리됩니다.

1-7. 오류 상황에서 종료 코드가 0이 아님을 확인할 수 있는가?

충족. 정상 0, 입력·데이터 오류 1, 명령 형식 오류 2, 예기치 못한 오류 3, Ctrl+C 130 으로 구분하고 sys.exit(main()) 으로 전달합니다. 실제 python -m budget_app 프로세스를 띄워 반환 코드를 확인하는 테스트가 있습니다.


항목 2 — 구조 설명

2-1. 코드가 3개 이상 모듈로 분리되어 있고, 각 모듈의 책임을 “어떻게” 나눴는지 설명할 수 있는가?

충족. 18개 모듈을 CLI(입력 해석·출력) → 서비스(업무 규칙) → 저장소(파일 입출력) → 모델(데이터 구조·검증) 네 계층으로 나누고, 의존은 위에서 아래로만 흐르게 했습니다. 나누는 기준은 “무엇이 바뀔 때 어디만 고치면 되는가”입니다. 저장 포맷이 바뀌면 저장소만, 출력 모양이 바뀌면 CLI 만 고칩니다.

2-2. 최소 2개 이상의 클래스에 부여한 책임 경계를 “어떻게” 정했는지 설명할 수 있는가?

충족. TransactionRepository 는 “파일을 어떻게 읽고 쓰는가”만, BudgetService 는 “어떤 거래가 유효하고 어떻게 집계하는가”만 맡습니다. 경계 기준은 파일 형식을 아는가 / 업무 규칙을 아는가입니다. Transaction 모델은 자기 검증만, CategoryStore·BudgetStore 는 각자 파일 하나만 담당하며, 서비스는 저장소를 생성자로 받아 테스트에서 바꿔 끼울 수 있습니다.

2-3. 파일 기반 update/delete를 “어떻게” 안전하게 처리했는지 설명할 수 있는가?

충족. 원본을 직접 고치지 않고, 같은 폴더의 임시 파일에 전체를 다시 쓴 뒤 fsync → os.replace 로 한 번에 교체합니다. 도중에 죽어도 원본은 그대로이고 임시 파일만 남습니다. 수정 전에 id 존재와 값 유효성을 먼저 검사하고, 삭제한 id 는 meta.jsonl 의 일련번호 덕분에 재사용되지 않습니다.


항목 3 — 개념 설명

3-1. list/search를 제너레이터로 스트리밍 처리한 방식을 “어떻게” 구현했고, “왜” 유리한지 설명할 수 있는가?

충족. read_jsonl 이 파일을 한 줄씩 yield 하고, 그 위에 필터(제너레이터 식)와 heapq.nlargest(limit, …) 를 이어 붙여 “파일 → 객체 → 조건 → 최신 N건”이 한 건씩 흐르게 했습니다. 30만 건(40MB) 실측에서 최대 메모리가 147KB 로, 전부 읽어 정렬한 182MB 의 약 1/1200 이었습니다. 메모리가 파일 크기와 무관해지는 것이 이점입니다.

3-2. 데코레이터로 분리한 공통 기능이 무엇이며, “왜” 분리가 필요했는지 설명할 수 있는가?

충족. handle_errors(예외 → 메시지·종료 코드), log_call(시작/완료/실패 로그), timed(소요 시간) 세 개를 만들어 main 과 서비스·저장소의 주요 메서드에 붙였습니다. 이 셋은 모든 명령에 똑같이 필요하지만 가계부 로직과 무관합니다. 명령마다 try/except 를 반복하면 한 곳만 빠져도 스택트레이스가 노출되므로 한 곳에 모았습니다.

3-3. 타입 힌트를 적용해 얻는 이점을 실제 코드 예로 “어떻게” 확인했고 “왜” 도움이 되는지 설명할 수 있는가?

충족. iter_all() -> Iterator[Transaction] 과 list_recent() -> list[Transaction] 의 차이가 “한 번만 순회 가능 / 여러 번 사용 가능”을, find_by_id() -> Transaction | None 이 “없는 경우를 처리하라”를 시그니처만으로 알려 줍니다. rewrite(transform: Callable[[Transaction], Transaction | None]) 처럼 넘길 함수의 모양도 계약으로 고정되어, 함수 본문을 열지 않고 계층 간 경계를 지킬 수 있습니다. 검증은 mypy --strict budget_app 으로 하며 19개 모듈 오류 0건입니다(실행에는 불필요, 개발용 설치).


항목 4 — 설계 판단

4-1. JSONL과 CSV 중 선택한 저장 포맷의 장단점을 비교하고, “왜” 그 포맷을 택했는지 근거를 말할 수 있는가?

충족. JSONL 은 태그 같은 목록 값과 숫자/문자열 타입을 그대로 담고 한 줄이 한 레코드라 스트리밍·덧붙이기가 쉽지만 엑셀로 열기 불편합니다. CSV 는 그 반대입니다. 그래서 내부 저장은 JSONL, 사람과 주고받는 용도는 import/export 의 CSV 로 역할을 나눴습니다.

4-2. 거래가 10만 건으로 늘어난다면, 현재 구조에서 병목이 어디이며 “어떻게” 개선할지 설명할 수 있는가?

충족(이번에 보강). 메모리는 스트리밍으로 일정하지만 시간이 문제입니다. 조회마다 전체 파일을 읽고 JSON 을 해석하며, update/delete 는 파일 전체를 다시 쓰므로 10만 건이면 명령당 1~2초입니다. 개선은 월별 파일 분할 → 역순 읽기로 list 조기 종료 → id 오프셋 인덱스 → sqlite3 저장소 교체 순서이며, 서비스가 저장소 인터페이스만 쓰므로 저장소 계층만 바꾸면 됩니다. 예상 효과(추정)는 월별 분할만으로 월 단위 명령이 약 1/24(0.07초), 역순 읽기로 list 가 수 ms, SQLite 로 조회·수정 모두 수 ms 입니다.

4-3. import CSV에 일부 깨진 행이 섞이면, “어떻게” 처리해 사용자 신뢰를 지킬지 (부분 성공/롤백/리포트) 설명할 수 있는가?

충족(이번에 보강). 기본은 부분 성공: 올바른 행만 등록하고 imported=N, skipped=M 과 건너뛴 행의 번호·사유를 리포트합니다. --strict 를 주면 롤백: 잘못된 행이 하나라도 있으면 아무것도 등록하지 않고 종료 코드 1 로 끝납니다. 어느 방식이든 모든 행을 검증한 뒤에 한 번만 쓰므로 절반만 들어가는 일이 없고, 헤더가 틀리면 읽기 전에 중단합니다. 같은 CSV 재입력은 --skip-duplicates(날짜·타입·카테고리·금액·메모가 같은 행을 저장된 개수만큼 이미 있는 거래 로 건너뜀) 로 막습니다. 같은 파일을 다시 넣으면 멱등이고 실제 반복 거래는 남으며, --strict 와 함께 써도 중복은 실패 사유가 아닙니다.


AI 사전평가 보완 제안 반영 (2026-10-17)

사전평가는 16개 항목 모두 PASS 였고, 항목마다 “보완” 제안이 하나씩 있었습니다. 전부 반영했으며 코드가 바뀐 것은 #3, #5, #11, #13 네 개입니다(테스트 97 → 101개, mypy --strict 0건).

# 제안 조치 위치
1 핵심 명령의 실패/경계 E2E 사례를 docs 에 추가 22개 경계·실패 사례를 명령·입력·결과·종료 코드·테스트 이름으로 표로 정리 02-result-report.md §3.10
2 백업 → 복원 절차 추가 backup 뒤 cp data/backups/<시각>/*.jsonl data/ 로 되돌리는 예시와 주의점(5개 파일 함께, 실행 중 아님) README.md §4 백업과 복구
3 카테고리 대체 중 실패 시 롤백 보증 설명 점검 중 실제 문제 발견: 카테고리를 지운 뒤에 규칙을 옮겨 중간 실패 시 규칙이 지워진 카테고리를 가리켰음. 규칙 → 거래 → 카테고리 순서로 고치고 중단 테스트 추가, 보증 범위를 문서화 recurring.py#L62-L86, 테스트, 03 Q12
4 예산 경고 임계값 변경 방법 안내 config.BUDGET_WARNING_RATE 위치와 “옵션으로 두지 않은 이유”를 README 에 명시, 상수에 주석 README.md §3, config.py#L11
5 대용량 CSV 성능·중단 테스트 추가 1만 행 import(약 1초, 상한 10초 검증)와 “2행 쓴 뒤 디스크 가득 참” 시나리오(파일 읽기 가능, id 재사용 없음, 재실행 정상) 테스트 대용량, 중단
6 내부 오류의 구체적 힌트 보강 자주 보는 오류 10가지의 메시지·원인·해결 표(권한, 디스크 공간, 깨진 줄, 코드 2/3 포함) README.md §6 자주 보는 오류와 해결
7 종료 코드 확인 셸 예시 ; echo "exit=$?" 와 && ... \|\| ... 예시 README.md §6
8 모듈 의존 다이어그램 두 줄 텍스트 다이어그램(cli → commands → services → repository → storage) 추가 README.md §7
9 주요 클래스의 책임 한 문장 9개 클래스에 docstring 추가, 답변서에 클래스별 책임표와 일련번호 책임 분리(보관/예약/무관) 설명 03 Q2 클래스별 책임, services.py#L26, repository.py#L18
10 권한 유지 정책 한 줄 원본 권한 상속, 임시 파일은 같은 폴더, 폴더 쓰기 권한만 필요함을 README 에 명시 README.md §4
11 search --limit 미지정 시 경고/권장 결과가 1,000건을 넘으면 표준 오류에 --limit 권장 안내, 도움말 문구 보강, 테스트 추가 commands.py#L58-L82, 테스트
12 --verbose 로그 예시 실제 출력 예시와 어느 데코레이터가 어떤 줄을 남기는지, 로그 수준 설명 README.md §6 실행 로그
13 타입 검사 실행법 mypy --strict budget_app 안내. 실행해 보니 6곳 지적 → 모두 수정해 0건 README.md §8, cli.py#L17-L20
14 JSONL 단점(엑셀 친화성) 명시 비교표 아래에 JSONL 단점 3가지와 보완 수단(export CSV, 줄 번호 오류, backup) 추가 03 Q6
15 개선안별 예상 효과 수치 실측(6만 건/초) 기반 추정표: 월별 분할 1/24, 역순 읽기 수 ms, SQLite 수 ms, 구현 비용 03 Q15 예상 효과
16 import 중복 감지 옵션 향후 방안 --skip-duplicates 설계(기준 키, 동작, 메모리, 기본값을 끄는 이유) 문서화 03 Q16 향후 보완, README.md §6.1

설계 규칙으로도 남겼습니다: 01-mission-guide.md §3.3 규칙 7.


2차 AI 사전평가 보완 제안 반영 (2026-10-17)

1차 반영 뒤의 재평가(16/16 PASS)에서 나온 제안입니다. 코드가 바뀐 것은 #3, #4, #6, #7, #11, #12, #16 입니다(테스트 101 → 110개, mypy --strict 0건).

# 제안 조치 위치
1 핵심 명령의 E2E 실패 시나리오 추가 경계·실패 사례표에 6행 추가(중복 import, 예산 삭제, 마지막 단계 실패, 기본 상한, 종료 코드 29가지) + 실패 입력 29가지의 종료 코드를 한 표로 고정한 테스트 02 §3.10, test_exit_codes_per_failure_case
2 백업 복구 후 무결성 검증 절차 건수 비교 → 세 파일 읽기 → meta 값과 최대 id 비교 → 요약 확인 4단계와, meta 가 작을 때의 처리(삭제하면 최대 번호에서 이어 감) README.md §4 복구 뒤 확인
3 대체 실패 시 완료/남은 단계 출력 StepFailedError 추가. 검증 뒤 어느 단계에서 실패해도 실제 상태로 만든 완료: ... / 남음: ... 힌트. 테스트 3개 errors.py#L39-L59, recurring.py#L89-L108, 테스트, 03 Q12
4 예산 변경/삭제의 영향 문서화 budget remove --month 명령 추가(없던 기능) + 변경·삭제·미설정이 요약에 미치는 영향 표 README.md §3, commands.py#L122-L125, 테스트
5 엑셀/지역별 인코딩 주의 cp949 저장 문제와 “CSV UTF-8” 선택, 세미콜론 미지원, export 가 BOM 없는 UTF-8 인 이유와 엑셀에서 여는 법. import --help 에도 한 줄 README.md §5, cli.py#L136-L142
6 예기치 못한 오류 힌트에 수집 항목 힌트에 “명령 전체 + --verbose 출력(마지막 [log] 줄)” 명시, README 표에 수집 항목 4가지 decorators.py#L76-L85, README.md §6 표
7 오류별 기대 종료 코드 테스트 29가지 실패 입력에 기대 코드(0/1/2)를 명시한 subTest 표 test_exit_codes_per_failure_case
8 저장소 교체 체크리스트 SQLite 전환 시 모듈별 변경 범위, 6단계 체크리스트, 4단계 마이그레이션 절차 03 Q18
9 README 에 클래스별 책임 표 구조 섹션에 9개 클래스 책임 표와 “검증은 모델·서비스, 저장은 저장소” 한 줄 README.md §7
10 디스크 가득 참/권한 오류 복구 순서 원인 제거 → 임시 파일 정리 → 읽기 확인 → 재실행(--skip-duplicates) → 백업 복구 5단계 README.md §6 복구 순서
11 대량 결과 기본 상한 옵션 config.SEARCH_DEFAULT_LIMIT(기본 None). 설정하면 --limit 없는 search 가 그 건수만 보여 주고 안내. 옵션이 아닌 상수로 둔 이유를 문서화 config.py#L13-L14, commands.py#L83-L94, 테스트
12 데코레이터 사용 예·예외 매핑 주석 handle_errors docstring 에 예외 → 종료 코드 표와 사용 예 decorators.py#L32-L50
13 경계에서의 타입 불일치 처리 세 경계(옵션/대화형, CSV, 저장 파일)별 검사와 실패 처리 표 README.md §6 경계 검증
14 포맷 전환 비용·마이그레이션 Q6 에 Q18 참조, Q18 에 모듈별 변경 규모(약 200줄)와 절차 03 Q18
15 개선안별 개발 비용·리스크 Q15 표에 예상 개발 시간(반나절~3일)과 운영 리스크 열 추가 03 Q15 표
16 --skip-duplicates 구현 계획대로 구현. 저장된 개수만큼 상쇄하는 방식(같은 파일 재입력은 멱등, 실제 반복 거래는 보존), 사유 보고, --strict 와 조합 규칙 정의. 테스트 3개 csv_io.py#L34-L100, 테스트, 03 Q16