Skip to content

[Feat] OCR 약 추출 LLM 하이브리드 — 처방전=파서, 약봉투/영수증=gpt-4o #100

Description

@kangcheolung

📌 Description

OCR로 rawText/좌표는 잘 가져오는데, 이를 {약이름, 1회량, 1일횟수, 총일수} 구조로 바꾸는
파싱이 서식마다 깨진다. OcrParser(정규식+좌표)는 알려진 서식만 커버해 새 서식마다 코드를
추가해야 하고, 뭉개진 약 이름(지스로먹스장지스로맥스정)은 교정할 수 없다.

파싱 단계를 LLM 추출로 검증하고, 측정 결과에 맞는 도입 방식을 정한다.

전제: #96(회귀 하네스), #99(파서 고도화) 머지. fixture 9종이 "정답"을 정의.

🎯 목표

  • fixture 9종으로 파서 vs LLM(gpt-4o) 약 단위 정확도 측정
  • 측정 결과에 맞는 도입 방식 결정 및 구현
  • 응답 스키마(OcrResultResponse.parsedDrugs) 유지 → 프론트 영향 0
  • 키 미설정 시 LLM 자동 비활성 → 기존(파서 전용)과 동일 동작

🧪 측정 결과 (gpt-4o + 좌표 + 프롬프트 개선)

프롬프트 1차 버그: "제형어 빼라"→아모잘탄정아모잘탄으로 잘림.
→ "제형 접미사는 이름의 일부, 절대 떼지 마라"로 수정.

서식 파서 gpt-4o
병원 처방전 (표 서식) 4/4 0/4 (좌표 텍스트로 표 숫자 매핑 실패)
약봉투 (별표/압축) 4/4 · 9/9 3~4/4 · 9/9
grid 영수증 4/5 5/5 (파서가 놓친 약을 잡음)
scattered 영수증 3/8 이름 8/8

처방전은 파서(좌표 알고리즘), 약봉투·영수증은 LLM이 유리. 하이브리드로 결정.

🛠️ 작업 범위

하이브리드 라우팅

OCR → 병원 처방전인가? (isPrescription)
  ├─ YES → 파서 (좌표 알고리즘)
  └─ NO  → 약봉투/영수증/그 외 → gpt-4o
                                  ↓ LLM 실패·약 0개
                                파서 폴백

isPrescription — 서식별 고유 문구로 구분 (발행처가 약국/병원/약국으로 달라 겹치지 않음):

서식 고유 문구
처방전 "처방전" 제목 / "처방 의약품" / "교부번호"·"교부일" / 보험코드 줄(\d{8,10}\s+약이름)
약봉투 "복약안내" / *약이름 별표 / "N정씩N회N일분"
영수증 "약제비" / "계산서" / "본인부담금"
isPrescription = 처방전 신호 있음  AND  약봉투 신호 없음  AND  영수증 신호 없음

보험코드 줄만 보면 코드 미인쇄 EMR에서 처방전을 놓쳐서, 여러 신호를 OR로 묶고 약봉투·영수증 신호로 배제.

1. DrugExtractor + LlmDrugExtractor

  • List<ParsedOcrData> extract(fields) — 실패 시 예외 대신 빈 리스트
  • 키 미설정·API 오류 → 빈 리스트 (폴백 유도)
  • sanity check: timesPerDay 16, totalDays 190 벗어나면 null

2. OpenAiClient

  • fields → "텍스트 @(x,y)" 목록(y→x) → gpt-4o temperature 0 json_object 25s
  • 프롬프트: 행 해석, 제형 접미사 유지, 용량 표기·성분명·제형어 제외, "교부일로부터 N일" 제외
  • 새 의존성 없음 (WebClient)

3. OcrParser.isPrescription(fields) public

4. OcrCommandService 라우팅 + method 로깅

5. application.ymlopenai.api-key: ${OPENAI_API_KEY:}

6. 테스트

  • OcrCommandServiceTest (키 불필요) — 처방전→파서(LLM 미호출), 약봉투→LLM, LLM 실패→파서 폴백
  • LlmDrugExtractorComparisonTest@Tag("integration") + OPENAI_API_KEY 있을 때만. 파서 vs LLM vs 하이브리드 리포트

✅ To-do

  • fixture 9종 측정 (gpt-4o-mini rawText/좌표 → gpt-4o 좌표+프롬프트 개선)
  • DrugExtractor + LlmDrugExtractor (sanity check)
  • OpenAiClient (좌표 텍스트, 프롬프트, gpt-4o)
  • OcrParser.isPrescription (서식별 고유 문구)
  • OcrCommandService 하이브리드 라우팅
  • application.yml openai 설정
  • OcrCommandServiceTest, LlmDrugExtractorComparisonTest
  • 설계 문서 docs/kangcheolung/issue-100-ocr-llm-hybrid.md

📏 완료 조건 (DoD)

  • 키 있는 로컬: 약봉투 → LLM 경로, 처방전 → 파서 경로로 parsedDrugs 채워짐
  • 키 없음/LLM 실패 → 파서로 기존과 동일 동작
  • OcrResultResponse 필드 변화 없음
  • ./gradlew test 통과

📒 기타 · 알려진 한계

  • LLM 비결정성: temperature 0이어도 gpt-4o가 run마다 이름 형식 미세하게 다름. 사용자 확인 UI가 최종 방어선.
  • 비용: gpt-4o 호출당 1520원. 볼륨 커지면 gpt-4o-mini + 프롬프트 강화 또는 캐싱.
  • 이름 정규화(지스로먹스장지스로맥스정)는 gpt-4o도 못 함 → ES 대조 = 다음 이슈 (druginfo 4,745건).
  • 이미지→비전 LLM은 별도 검토.
  • 로컬 테스트용 컨트롤러 userId 하드코딩은 커밋 금지.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions