🧬 연구 허브

Skill

recurrence-detect 바로 실행

기록 타임라인에서 재발 시점·부위를 판정하고, 정답 대비 정확도를 제대로 측정한다.

언제 쓰나

여러 시점의 의무기록을 시간순으로 읽어 사건(재발)이 언제 어디서 일어났는지 판정할 때. 그리고 더 넓게는 — LLM 추출 정확도를 제대로 측정하는 방법이 필요할 때. 평가 설계 부분은 재발이 아닌 어떤 추출 과제에도 그대로 쓴다.

정본 코드

~/Documents/IMOK/LungCancerRecurrence/
  run_batch_analysis.py          배치 실행
  analyze_gt_accuracy.py         ★ GT 대비 정확도 (정본)
  analyze_mismatches.py / analyze_all_mismatches.py   불일치 분석
  analyze_patient_detail.py      개별 환자 추적
  scripts_tmp_eval_full.py, scripts_tmp_modelcompare.py, scripts_tmp_parallel_run.py
  model_comparison_260617.md     ★ 모델 비교 결과 기록
  backup_prompts_260526/, backup_prompts_260611/   프롬프트 BEFORE/AFTER 스냅샷
  docker-compose.yml + app.db(SQLite)

바로 쓰는 코드

python3 scripts/score.py --gt gt.json --pred pred.json --spec spec.json

scripts/score.py — 네 개 파일에 재구현돼 있던 채점 코드를 합친 것. jaccard(둘 다 비면 채점 제외) · date_diff_days · Scorer(지표별 n 분리 누적) · majority(self-consistency 다수결) · report(min_n)(표본 부족 경고). 재발 전용이 아니라 어떤 LLM 추출 채점에도 쓴다.

판정 구조

환자별 기록 타임라인
  → 상위 분석 (기록에서 재발 관련 사실 추출)
  → final_decision  재발 여부 + 재발일 + 부위 코드

상위 분석과 최종 판단을 나눈다. 모델을 바꿔 비교할 때 상위 분석은 재사용하고 최종 판단만 다시 돌린다 — 비교 비용이 크게 준다(model_comparison_260617.md의 "동일 상위분석 재사용").

채점을 제대로 설계해라 — 이 skill의 핵심

정확도를 "맞다/틀리다"로만 재면 개선이 안 보인다. 이 프로젝트가 쓰는 4지표:

지표이유
날짜 정확일치엄격 기준
날짜 ±30일재발일은 확진일·영상일·기록일이 며칠씩 다르다. 완전일치만 재면 실제 성능이 안 보인다
Local 부위 Jaccard부위는 여러 개일 수 있다 → 부분점수
Distant 부위 Jaccard위와 동일

부분점수(Jaccard)와 허용오차(±30일)가 있어야 프롬프트 개선이 지표에 나타난다. 완전일치만 보면 "3개 중 2개 맞춤"이 0점이라 개선 방향을 못 잡는다.

비교 전에 정규화가 필요하다 — normalize_llm_location()이 LLM이 뱉은 부위 표현을 GT 코드 체계로 매핑한다. 정규화 없이 문자열 비교하면 실제보다 훨씬 낮게 나온다.

parse_gt_sites(gt_distant_site, gt_local_site, gt_mixed_site) — GT가 여러 컬럼에 흩어져 있으면 파싱을 한 군데로 모아라.

홀드아웃 + 표본 수를 같이 적어라

홀드아웃 40명, 프로젝트 19
날짜 n=38~39 / Distant n=32~37 / Local n=3~7  ← Local은 신뢰도 낮음

n을 지표 옆에 반드시 적는다. model_comparison_260617.md는 "Local 92.9%"에 n=7을 붙이고 본문에 "신뢰도 낮음"을 명시했다. n을 안 적으면 표본 7건짜리 92.9%가 결론을 뒤집는다.

모델 비교에서 배운 것

방식날짜 정확Distant(J)
gpt-oss:20b 단일31.6%73.8%
gpt-oss:20b 다수결 3회34.2%76.3%
llama3.3:70b 단일41.0%51.9%
  • self-consistency(같은 입력 3회 호출 후 다수결): +2~3%p 일관 개선. 비용 3배지만 저위험.
  • 단일 최강 모델은 없다. 날짜는 llama가, 부위는 gpt-oss가 낫다 → 항목별 하이브리드가 현실적.
  • 실패도 기록해라: qwen3.5:35b-a3b는 서버에서 사실상 무응답(간단 호출도 12초+ 타임아웃), 72분에 10명도 처리 못 해 제외. 왜 제외했는지 남기지 않으면 다음 사람이 또 시도한다.

프롬프트 변경은 스냅샷으로 관리

backup_prompts_YYYYMMDD/ 폴더에 변경 대상 파일의 *_BEFORE.* / *_AFTER.*를 함께 넣고 README에 원본 경로와 롤백 명령을 적는다:

final_decision_BEFORE.py  →  src/final_decision.py
ground_truth_api_BEFORE.py → src/backend/controllers/ground_truth_api.py

프롬프트를 고치면 지표가 오르내린다. 되돌릴 수 있어야 실험을 과감하게 한다. git이 있어도 "이 지표는 어느 프롬프트에서 나온 건지"를 한 폴더로 묶어두는 게 훨씬 빠르다.

불일치를 봐야 개선된다

analyze_mismatches.py가 LLM 예측(extract_llm_prediction)과 분석 근거(extract_analysis_details)를 뽑아 GT와 다른 케이스를 정리한다. analyze_patient_detail.py로 개별 환자를 파고든다.

틀린 케이스를 안 읽고 프롬프트를 고치는 건 추측이다. 지표는 어디가 틀렸는지 안 알려준다.

함정

  • 오래 걸리는 배치는 재시도 스크립트를 따로 둔다(scripts_tmp_rerun_errors.py). 전체 재실행은 최후 수단.
  • 병렬 실행(scripts_tmp_parallel_run.py) 시 온프렘 서버 부하를 확인해라 — 위 qwen 사례가 그 결과다.
  • app.db-shm/app.db-wal이 남아 있다. SQLite WAL 모드이므로 DB를 복사할 때 세 파일을 함께 옮겨야 한다.

연관 skill

tnm-staging-extract·pathology-llm-extract(추출 대상), excel-case-validator, llm-provider-switch