Repository navigation
feat: OCR 응답 원본 저장 + 파서 회귀 테스트 기반 #96
Copy link
Copy link
Closed
Labels
✨ Feature기능 개발기능 개발
Description
📌 Description
약봉투·처방전 OCR 추출이 여러 서식에서 깨지는데, 실패를 재현할 수단이 없다.
NaverOcrClient.callOcr()는 응답을NaverOcrApiResponse로 파싱한 뒤 원본을 버린다.
OcrResult에는raw_text(전체 텍스트를 이어붙인 문자열)만 저장되고, 좌표(boundingPoly)가
포함된 원본 응답은 어디에도 남지 않는다.OcrParser단위 테스트가 0개다. 서식 하나가 깨질 때마다 정규식 분기를 추가하는데,
기존 케이스가 회귀했는지 확인할 방법이 없다.raw_text에 환자 이름·주민번호가 그대로 저장되고 있다 (마스킹 없음).
이 이슈는 회귀 테스트 기반을 만든다. 후속 이슈(표 처방전 좌표 파싱, 약봉투 다중 약 분리)는
전부 이 위에서 진행한다.
🎯 목표
- OCR 응답 원본(좌표 포함)을 저장해 실패 케이스를 그대로 재현 가능하게 한다
- 저장 시 주민번호를 마스킹한다 (이름·생년월일은 저장 허용 — 팀 합의)
OcrParser를 fixture로 돌려 약 단위 precision/recall을 뽑는 회귀 테스트를 만든다- 응답 DTO(
OcrResultResponse) 외부 계약은 건드리지 않는다 (프론트 영향 0)
🛠️ 작업 범위
1. Naver 응답 원본 저장
NaverOcrClient.callOcr()—bodyToMono(String.class)로 원문을 받아 주입한ObjectMapper로
파싱. 반환 타입을List<Field>→NaverOcrCallResult(List<Field> fields, String rawResponseJson)
(신규 record)로 변경.extractFields()검증 로직은 유지.OcrResult엔티티 —@Column(name = "raw_response", columnDefinition = "TEXT") String rawResponse
추가 (nullable). 빌더 파라미터 추가.ddl-auto: update라 마이그레이션 파일 불필요.OcrCommandService.processOcr()— 반환 타입 변경 반영,rawResponse저장.
2. 주민번호 마스킹
- 신규 유틸
global/util/PiiMasker.java// 주민번호 형식(6자리-7자리, 뒤 첫자리 1~8)만 마스킹 public static String maskResidentNumber(String text) { return text == null ? null : text.replaceAll("\\d{6}\\s*-\\s*[1-8]\\d{6}", "******-*******"); }
OcrCommandService에서raw_text·raw_response둘 다 저장 직전에 통과.
3. 파서 회귀 테스트 하네스
src/test/resources/ocr/fixtures/*.json— 저장된 Naver 응답(NaverOcrApiResponse형태)- 합성 표 처방전 2~3건: 좌표 포함, 개인정보 없음 (직접 작성)
- 약봉투/영수증: 실제 사진에서 뽑아 이름 마스킹 후 커밋. 확보 전까지는 관찰된
유성온누리약국 케이스 rawText를 좌표 없는 fixture로 우선 포함
src/test/resources/ocr/expected/manifest.json— fixture명 → 기대 약 목록
[{drugName, dosagePerTime, timesPerDay, totalDays}]src/test/java/.../ocrresult/fixture/OcrFixtureLoader.java— JSON →List<Field>역직렬화src/test/java/.../ocrresult/service/OcrParserRegressionTest.javanew OcrParser()(의존성 없음), fixture별parse()실행- 약 단위 precision/recall 계산·콘솔 출력
- 핵심 케이스 개별 단언 (한국어
@DisplayName, Given/When/Then) @Tag("integration")아님 (순수 단위)
✅ To-do
-
NaverOcrCallResultrecord -
NaverOcrClient원문 반환 +ObjectMapper주입 -
PiiMasker유틸 + 단위 테스트 -
OcrResultraw_response컬럼 + 빌더 -
OcrCommandService저장 로직 + 마스킹 적용 -
OcrFixtureLoader+ fixture JSON (합성 처방전 우선) -
manifest.json정답표 -
OcrParserRegressionTest(precision/recall + 핵심 케이스 단언) - 기존 별표 약봉투 케이스로 무회귀 확인
📏 완료 조건 (DoD)
POST /api/ocr호출 후ocr_result.raw_response에 좌표 포함 응답 JSON 저장됨- 주민번호가 있는 처방전으로 테스트 시
raw_text·raw_response모두******-******* ./gradlew test --tests *OcrParser*통과 + precision/recall 출력OcrResultResponse필드 변화 없음
📒 기타
- 커밋 분리 (git.md): entity → record(dto) → client → service → test
- 응답 원본에
inferConfidence는 우리 DTO에 없어 재직렬화 시 빠짐 → 원문 문자열을 그대로 저장하는 이유 - 후속: 이슈 B(표 처방전 좌표 파싱), 이슈 C(약봉투 다중 약)가 이 하네스 위에서 진행
- 로컬 테스트용 컨트롤러
userId하드코딩(현재 워킹트리에 있음)은 이 이슈와 무관 — 커밋에 섞지 말 것
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
✨ Feature기능 개발기능 개발
Activity
Merge pull request #97 from PIUDAProject/feature/96