docs/superpowers/plans/2026-08-21-hancom-page-chunking-tasks.md
브랜치 feat/hancom-page-chunking
계획서 2026-08-21-hancom-page-chunking.md
방식 TDD (red → green → 리팩터). 선택 지점은 권장안 채택.
각 테스크는 완료조건(구현이 무엇을 만족해야 하는가)과 검증방법(그것을 어떻게 증명하는가 — 실행 가능한 명령/단정)을 분리해 정의한다.
상태: 서버 도달 불가로 차단(미완). 기본값 20으로 진행, 실측은 별건.
layoutPageChunk 기본 20, 설정으로 변경 가능하게 만들어
실측 후 코드 변경 없이 조정. 계획서에 "미실측" 명기 완료.extractPages(byte[] pdf, List<Integer> absolutePages0Based) → byte[]
PDDocument 누수 없음.mvn -pl opendataloader-pdf-core test -Dtest=HancomPdfPageSlicerTest[30..49] 추출 → 페이지 수 20, 각 페이지 미디어박스가
원본 30..49와 일치, 원본 바이트 배열 해시 불변.[5,9,40], 범위 초과 [80,999].page_number: k → slice.get(k) 절대값으로 매핑.k < 0 || k >= slice.size() 레코드는 폐기 + WARNING.page_number도 동일 규칙 적용.object_id는 페이지 지역 식별자이므로 변경하지 않음.mvn ... -Dtest=HancomPageRenumberTest[30,31,32], 비연속 [5,9,40] 매핑 정확.page_number: 99 + slice 크기 3 → 결과에서 제외, 예외 없음.JsonNode를 재번호 후 재검사해 원본 page_number 유지 확인.convert()가 request.getPageNumbers()를 존중 (빈 값 → 전체 페이지).pdfBytes + 절대 페이지로 기존 로직 유지.mvn ... -Dtest=HancomAIPageChunkingTest (MockWebServer)page_number 오름차순.failedPages로 보고.IOException (문서 단위 Java 폴백).getFailedPages()에 포함, 예외 없이 완주.IOException.BACKEND_CHUNK_SIZE(50)와 클라이언트 분할(20)의 이중 분할 정리.lastHybridRawJson 마지막-청크-승 손실 없음).getLastHybridRawJson()의 DLA 페이지 수 == 60.상태: 서버 도달 불가 → 차단. 목 서버/단위 검증으로 대체하고 별건으로 남긴다.
odl-pdfua로 태그 PDF까지 완주.main과 대조해 신규 실패 0건.| 선택 | 채택안 | 근거 |
|---|---|---|
| 이미지 분할 vs PDF 분할 | PDF 분할 | 텍스트 레이어 보존 (계획서 1.4) |
| 슬라이스 크기 | 20 | 30 한계에 안전 여유, 왕복 지연 억제 |
| 순차 vs 병렬 전송 | 순차 | 서버 동시성 미확인, 정확성 우선 |
| 부분 실패 | 페이지 단위 격리 | 250p를 한 슬라이스 때문에 버리지 않음 |
| CLI 노출 | 비노출(내부 설정) | 사용자 조정 대상 아님 |
| 테스크 | 상태 | 검증 결과 |
|---|---|---|
| T0 서버 한계 실측 | 차단 | 실서버·스테이징 모두 도달 불가. 30은 미실측 전제. layoutPageChunk로 코드 변경 없이 조정 가능하게 처리 |
| T1 페이지 추출 | 완료 | 단위 8개. 실물 3종(82p 스캔/32p 매거진/34p 수식) 텍스트 대조 0 불일치 |
| T2 재번호 매기기 | 완료 | 단위 13개. 뮤테이션 검증: 매핑 무력화 시 13개 중 10개 실패 |
| T3 분할 배선 | 완료 | 단위 11개. 프로브 서버로 1/20/21/30/31/82/255p 전부 PASS, 요청당 20p 초과 없음 |
| T4 부분 실패 격리 | 완료 | 중간 슬라이스 500 → 나머지 페이지 보존 + failedPages 보고 |
| T5 이중 청킹 정리 | 완료 | hancom-ai는 처리기 청킹 우회. 60p 문서 증거 유실 해소. 오버플로 클램프 테스트 포함 |
| T6 다중 페이지 자산 | 완료 | 32p 생성 문서 + 픽스처, 목 서버 슬라이스 replay, smoke-mock.sh 배치 검증 추가 (목 테스트 34→45) |
| T7 실서버 종단 | 차단 | 서버 불가. 목 서버로 대체 검증(32p 전 페이지 정위치, veraPDF 1/1 통과) |
| T8 코드리뷰·보안 | 완료 | 서브에이전트 2건 + CodeRabbit 3회. 결함 6건 적발(전부 조용한 페이지 손실), 각각 red 재현 후 수정. PR #693 리뷰 스레드 9건 전부 응답·resolve |
분할 전 상태: HancomAIClient가 getPageNumbers()를 무시해 처리기의 50페이지
청킹이 무효였다. 100페이지 문서는 전체를 두 번 업로드하고 매번 전체 결과를 받았다.
재번호 매기기가 급소라는 증거: 재번호를 무력화한 뒤 32페이지 문서를 목 서버로 돌리면 결과가 20페이지(0..19)로 축소된다. 두 번째 슬라이스의 12페이지가 1~12페이지를 조용히 덮어쓴다. 예외도 경고도 없다.
검증 공백이 있었다: 200개 코퍼스가 전부 1페이지여서 기존 하이브리드 검증 전체가 다중 페이지 경로를 한 번도 실행하지 않았다. T6에서 32페이지 자산을 만들어 각 페이지에 자기 페이지 번호를 새겨, 오배치가 그럴듯해 보이지 않고 실패로 드러나게 했다.
리뷰가 잡은 결함 6건은 모두 같은 모양이었다: 출력이 정상처럼 보이면서 페이지가 조용히 사라진다. 예외도 경고도 없다. 이 기능이 노출된 실패 유형이 그것 하나라는 뜻이고, 페이지 번호 변환과 누락 페이지 회계가 그걸 막는 유일한 장치다. 그래서 두 지점 모두 뮤테이션으로 "테스트가 실제로 그 버그를 잡는지" 확인했다(각각 10/13, 5개 실패).
내가 쓴 테스트 2개가 정작 대상 버그를 못 잡았다: (1) TreeSet이 잘못된 페이지를
무해한 뒤쪽으로 정렬해버려 통과 — 앞쪽에 오는 page 0 케이스로 교체.
(2) 클램프 테스트가 production 식을 테스트 본문에 복사해 계산 — effectiveChunkSize를
추출해 실제 호출로 변경. 초록 막대는 검증이 아니다.