📌 Description
OCR 파싱 개선 1단계의 파서 수정. #96(회귀 테스트 기반) 위에서 진행한다.
OcrParser.java 한 파일만 건드리며, 스키마·응답 DTO·보안 설정 변화 없음.
두 서식이 다중 약 추출에 실패한다:
B. 병원 표 처방전 — 좌표 파싱 경로에 못 들어감
parse()는 isTablePrescription()이 true여야 parseByCoordinates()로 간다. 그런데
isTablePrescription()은 LABEL_DOSAGE_PATTERN(1회\s*투약량\s*\d 등)이 매치되면 즉시 false를
반환한다. 이 \s*가 줄바꿈을 삼켜서, 표 헤더 1회투약량\n1일투여횟수의 다음 줄 숫자에
매치된다 → 표 처방전을 "약봉투 라벨"로 오판 → 좌표 파싱이 한 번도 실행된 적 없음.
좌표 경로에 억지로 넣어도, detectColumnIndex()가 헤더 셀의 순서 인덱스로 컬럼을 잡아서
헤더 처방 의약품의 명칭이 처방의약품의+명칭 두 필드로 쪼개지면 인덱스가 밀려 전 행이 skip된다.
현재 baseline (#96 하네스): table_prescription_synth.json R=0.00 (5약 중 1약).
C. 약봉투 압축형 — 첫 약만 추출
약이름 + 1정씩1회5일분이 N번 반복되는 약봉투. 별표도 1회투약량 N 라벨 텍스트도 없어
isTextSequentialMulti(= LABEL_DOSAGE_PATTERN 2회+)에 안 걸리고 parseByRegex 단일 폴백 →
COMPACT_DOSAGE_PATTERN이 첫 매치만.
🎯 목표
- 표 처방전이 좌표 파싱 경로로 진입하고, 헤더 셀이 쪼개져도 컬럼을 올바르게 배정한다
- 압축형 약봉투의 약을 전부 분리 추출한다
- 두 서식의 합성 fixture를 guard로 승격 (R=1.00)
🛠️ 작업 범위
OcrParser.java 단일 파일.
B-1. 게이트 정규식
LABEL_DOSAGE_PATTERN의 \s* → [ \t]* (같은 줄의 숫자 = 진짜 라벨만 인정).
isTextSequentialMulti, isTablePrescription 둘 다 이 패턴을 공유 → 표 헤더 오인 함께 해소.
B-2. x좌표 앵커 기반 컬럼 배정 (parseByCoordinates)
detectColumnIndex / ColumnIndex(순서 인덱스) 제거하고:
- 헤더 행에서 키워드가 든 셀마다
(x중심, 컬럼종류)를 앵커로 기록 (0=name 1=dosage 2=times 3=days)
- 헤더 판정: name 앵커 존재 + 앵커 2개 이상
- 데이터 행의 각 셀을 x가 가장 가까운 앵커에 배정, 컬럼당 첫 값 채택
- 헤더 못 찾거나 결과 비면
parseByRegex 폴백 (기존 유지)
- 미사용이 되는
getCol 정리
B-3. 폴백 조건 완화 (parseByPharmacyReceipt)
allMatch(전부 null) → 절반 이상 null이면 parseGroupedTrailingNumbers 시도.
C-1. 압축형 게이트 확장 (isTextSequentialMulti)
LABEL_DOSAGE_PATTERN 카운트에 더해, COMPACT_DOSAGE_PATTERN 매치가 2회 이상이어도 true.
→ parseByTextSequential이 이미 이름 줄로 블록 나누고 tryCompactDosage 시도하므로 로직 재사용.
테스트 (#96 하네스)
table_prescription_synth.json — guard 승격
drug_bag_compact_synth.json (신규 합성) — 압축형 약봉투, guard
- 실제 사진 fixture는 팀이 이름 마스킹 후 추후 추가
✅ To-do
📏 완료 조건 (DoD)
table_prescription_synth.json: 약 5건, 필드 채움, guard 통과
drug_bag_compact_synth.json: 약 N건 전부, guard 통과
pharmacy_receipt_starred.json(기존 guard) 결과 변화 없음
./gradlew test --tests "*OcrParser*" 통과
📒 기타
📌 Description
OCR 파싱 개선 1단계의 파서 수정. #96(회귀 테스트 기반) 위에서 진행한다.
OcrParser.java한 파일만 건드리며, 스키마·응답 DTO·보안 설정 변화 없음.두 서식이 다중 약 추출에 실패한다:
B. 병원 표 처방전 — 좌표 파싱 경로에 못 들어감
parse()는isTablePrescription()이 true여야parseByCoordinates()로 간다. 그런데isTablePrescription()은LABEL_DOSAGE_PATTERN(1회\s*투약량\s*\d등)이 매치되면 즉시 false를반환한다. 이
\s*가 줄바꿈을 삼켜서, 표 헤더1회투약량\n1일투여횟수의 다음 줄 숫자에매치된다 → 표 처방전을 "약봉투 라벨"로 오판 → 좌표 파싱이 한 번도 실행된 적 없음.
좌표 경로에 억지로 넣어도,
detectColumnIndex()가 헤더 셀의 순서 인덱스로 컬럼을 잡아서헤더
처방 의약품의 명칭이처방의약품의+명칭두 필드로 쪼개지면 인덱스가 밀려 전 행이 skip된다.현재 baseline (#96 하네스):
table_prescription_synth.jsonR=0.00 (5약 중 1약).C. 약봉투 압축형 — 첫 약만 추출
약이름+1정씩1회5일분이 N번 반복되는 약봉투. 별표도1회투약량 N라벨 텍스트도 없어isTextSequentialMulti(=LABEL_DOSAGE_PATTERN2회+)에 안 걸리고parseByRegex단일 폴백 →COMPACT_DOSAGE_PATTERN이 첫 매치만.🎯 목표
🛠️ 작업 범위
OcrParser.java단일 파일.B-1. 게이트 정규식
LABEL_DOSAGE_PATTERN의\s*→[ \t]*(같은 줄의 숫자 = 진짜 라벨만 인정).isTextSequentialMulti,isTablePrescription둘 다 이 패턴을 공유 → 표 헤더 오인 함께 해소.B-2. x좌표 앵커 기반 컬럼 배정 (
parseByCoordinates)detectColumnIndex/ColumnIndex(순서 인덱스) 제거하고:(x중심, 컬럼종류)를 앵커로 기록 (0=name 1=dosage 2=times 3=days)parseByRegex폴백 (기존 유지)getCol정리B-3. 폴백 조건 완화 (
parseByPharmacyReceipt)allMatch(전부 null)→절반 이상 null이면parseGroupedTrailingNumbers시도.C-1. 압축형 게이트 확장 (
isTextSequentialMulti)LABEL_DOSAGE_PATTERN카운트에 더해,COMPACT_DOSAGE_PATTERN매치가 2회 이상이어도 true.→
parseByTextSequential이 이미 이름 줄로 블록 나누고tryCompactDosage시도하므로 로직 재사용.테스트 (#96 하네스)
table_prescription_synth.json— guard 승격drug_bag_compact_synth.json(신규 합성) — 압축형 약봉투, guard✅ To-do
LABEL_DOSAGE_PATTERN\s*→[ \t]*parseByCoordinatesx좌표 앵커 방식으로 교체,detectColumnIndex/ColumnIndex/getCol정리parseByPharmacyReceipt폴백 조건 완화isTextSequentialMulti—COMPACT_DOSAGE_PATTERN2회+ 조건 추가table_prescription_synth.jsonguard 승격📏 완료 조건 (DoD)
table_prescription_synth.json: 약 5건, 필드 채움, guard 통과drug_bag_compact_synth.json: 약 N건 전부, guard 통과pharmacy_receipt_starred.json(기존 guard) 결과 변화 없음./gradlew test --tests "*OcrParser*"통과📒 기타
fix: 표 처방전 좌표 파싱/fix: 약봉투 압축형 다중 약/test: 합성 fixture(
.dev/learnings/ocr_table_prescription_parsing.md)