🧬 연구 허브

Skill

medical-code-extract 코드 참조

검사 판독문 결론에서 질병코드를 뽑는다. 규칙으로 힌트를 만들고 LLM이 판단, 실패하면 규칙으로 되돌아간다.

언제 쓰나

건강검진·영상 검사의 결론(conclusion) 텍스트에서 질병코드나 수치 판정을 뽑을 때. 코드 체계가 정해져 있고(유효 코드 집합이 있음) 오답을 내면 안 되는 상황.

pathology-llm-extract가 자유 필드를 뽑는다면, 이쪽은 닫힌 코드 집합으로 분류한다 — 그래서 규칙 기반 폴백이 성립한다.

정본 코드

~/Documents/medical_code_extractor 2/
  keyword_extractor.py   규칙 기반 추출 (문장 단위 정밀 분석)
  llm_extractor.py       LLM + 힌트 주입 + 폴백
  coronary_extractor.py  관상동맥 전용 (Keyword/LLM 쌍 + 팩토리)
  pwv_abi_extractor.py   PWV·ABI 전용
  keywords/              carotid_keywords.py, coronary_keywords.py, pwv_abi_keywords.py
  prompts/               검사별 프롬프트 빌더
  ollama_client.py       온프렘 LLM
  excel_handler.py       입출력
  api_server.py          FastAPI
~/Documents/건강의학과/                검증 케이스 (120/140/199/206건 xlsx)

건강의학과/medical_code_extractor에도 사본이 있다. ~/Documents/medical_code_extractor 2/가 정본이다.

핵심: 규칙이 힌트를 만들고 LLM이 판단한다

1) KeywordExtractor.extract(text)      → KeywordHints
2) format_hints_for_llm(hints)         → 힌트 텍스트
3) prompt_builder(text, hints_text)    → system/user 프롬프트
4) LLM 호출 → JSON 파싱
5) _postprocess_codes(codes, hints)    → 유효 코드 검증
6) JSON 파싱 3회 실패 → KeywordExtractor 결과로 폴백

규칙 결과를 LLM에 넘기되 최종 판단은 LLM이 한다. 규칙만 쓰면 표현 변형에 약하고, LLM만 쓰면 근거 없이 코드를 만든다. 그리고 규칙 결과가 살아 있으므로 LLM이 무너져도 답을 낼 수 있다 — 이게 이 구조의 진짜 가치다.

규칙 추출기가 잡아야 하는 것

VesselFinding이 담는 필드가 곧 규칙 추출기의 요구사항이다:

vessel            어느 혈관/부위
severity          중증도 표현
severity_level    비교 가능한 숫자 등급
stenosis_percent  협착률 수치
context           근거 문장
negated           ★ 부정문 여부

negated가 가장 중요하다. "no significant stenosis"를 협착으로 잡으면 전부 틀린다. 의료 판독문은 정상 소견을 부정문으로 쓴다. 그래서 문서 전체가 아니라 문장 단위로 쪼개 분석한다.

vessel_max_severity: Dict[str, VesselFinding]로 혈관별 최고 중증도만 남긴다. 같은 혈관이 여러 문장에 나오면 가장 심한 것이 판정 기준이다.

폴백 정책

max_json_retries = 3   # JSON 파싱 실패 재시도

3회 실패하면 KeywordExtractor.get_disease_codes(hints)로 폴백하고, reasoning"KeywordExtractor fallback (JSON 파싱 3회 실패)"를 남긴다. 폴백 코드도 없으면 success=False.

폴백으로 나온 결과임을 반드시 표시해라. 나중에 정확도를 볼 때 LLM 결과와 폴백 결과를 섞어 계산하면 안 된다.

json_mode=False로 호출하고 직접 파싱한다(_parse_response_strict). 온프렘 모델은 json_mode를 제대로 지원하지 않는 경우가 많다. max_tokens=8192 — 4096에서 올렸다. reasoning 모델은 thinking에 토큰을 많이 쓴다.

검사 종류 추가하는 법

각 검사가 동일한 3종 세트를 가진다:

keywords/<검사>_keywords.py     위치·중증도 키워드 사전
prompts/<검사>_prompt.py        프롬프트 빌더
<검사>_extractor.py             KeywordExtractor + LLMExtractor + create_<검사>_extractor() 팩토리

create_carotid_extractor(ollama_client) 같은 팩토리로 조립한다. 키워드 사전과 프롬프트를 데이터로 분리했기 때문에 새 검사 추가가 파일 3개 추가로 끝난다.

함정

  • valid_codes: Set[int]로 유효 코드 집합을 강제한다. LLM이 존재하지 않는 코드를 만들어내면 _postprocess_codes에서 걸린다. 닫힌 코드 체계면 반드시 사후 검증해라.
  • 결론 텍스트만 넣는다. 판독문 전체를 넣으면 소견 부분의 언급이 결론과 충돌한다.
  • thinking 필드를 따로 보관한다 — 온프렘 reasoning 모델 대응은 llm-provider-switch 참조.
  • 검증 케이스가 건강의학과/에 xlsx로 있다(경동맥 120·170건, 관상동맥 140건, 199·206건). 규칙을 고치면 여기로 회귀 검증해라.

연관 skill

pathology-llm-extract, llm-provider-switch, excel-case-validator