🧬 연구 허브

Skill

ctrial-collect 코드 참조

식약처·공공데이터 API로 임상시험을 수집해 DB와 엑셀로 넣고, 회차 간 변경을 뽑는다.

언제 쓰나

식약처 승인 임상시험을 주기적으로 긁어 "지난번 대비 뭐가 새로 생겼고 뭐가 끝났는지"를 내야 할 때. 특정 암종의 현재 모집 중 시험을 찾는 작업의 입력을 만든다.

조합: 이 skill로 수집 → cancer-type-classify로 암종 부착 → 담도암만 필터.

정본 코드

~/Documents/IMOK/ctrial-auto/app/
  services/api_download_service.py    388줄  ★ 공공데이터 API 수집 (정본)
  services/data_download_service.py   242줄  구버전 Playwright 크롤링 (사용 안 함)
  services/excel_compare_service.py   539줄  회차 간 변경 비교
  services/url_update_service.py       83줄  MFDS 상세 URL 역추적
  services/export_excel_service.py     70줄  내보내기
  pipeline/pipeline_manager.py        246줄  단계 진행 상태 관리
  prompt/                                    분류 프롬프트 (cancer-type-classify skill 참조)

ctrial-auto 계열은 6벌(_2월, _4월, _api, CTrial_AUTO, ctrial_auto_module)이 있다. IMOK/ctrial-auto가 최신(2026-07-30)이고 이것이 정본이다.

수집: 목록 + 상세 2단 API

목록 API  getMdcinClincTestInfoList02   → 최근 CLNC_TEST_SN(시험번호) 확보 + 실시기관명
상세 API  getClncExamPlanDtlInq2        → 시험번호별 STATUS·대상질환·영문제목·연구목적·성분명

목록만으로는 분류에 쓸 정보가 부족하다. 상세 API가 주는 대상질환명/카테고리/영문제목/연구목적/성분명 5개 컬럼이 cancer-type-classify의 입력 품질을 결정한다. 목록에서 얻은 실시기관은 _lab_map: {CLNC_TEST_SN: 실시기관}에 담아 상세 결과와 합친다 — 상세 API에는 실시기관이 없다.

호출 사이에 call_delay를 둔다. 공공데이터포털은 연속 호출을 차단한다. max_retries로 재시도.

웹 크롤링에서 API로 갈아탄 이유

원래 Playwright로 긁었다(data_download_service.py). API로 바꾸면서 출력 엑셀 형식은 완전히 동일하게 유지하고 컬럼 5개만 추가했다. 이게 중요하다 — 다운스트림(비교·분류·내보내기)을 하나도 안 고치고 수집 방식만 교체할 수 있었다. 수집기를 바꿀 때는 출력 스키마를 먼저 고정해라.

url_update_service.py의 Playwright는 아직 필요하다. MFDS 상세 페이지 URL은 API로 안 나와서 검색 URL로 역추적한다.

로컬 필터링

API가 아니라 받은 뒤에 나눈다:

모집중  + 승인일 3년 이내   → recruiting_trials.xlsx
승인완료 + 승인일 6개월 이내 → completed_trials.xlsx

기준 기간은 용도마다 다르니 상수로 빼둬라.

변경 비교 (회차 간 증분)

excel_compare_service.py가 핵심이자 가장 큰 파일이다.

excel_old/ 에서 파일명 날짜로 '가장 최근' 1개 선택
  → excel_output/ 신규 파일과 비교
  → step1_output/ 에 비교결과 + updated 파일 생성

파일명에 날짜를 박아 세대를 관리한다(extract_date_from_filename, find_latest_file_by_date). DB에도 20260403.db, 20260618.db, 20260701.db 식으로 스냅샷을 남긴다. 임상시험은 상태가 바뀌므로 "그때 이 시험이 뭐였는지"를 복원할 수 있어야 한다.

비교 전에 양쪽 다 중복 제거를 한다 — 신규는 시험번호 기준(dedup_file2_by_seq), 기존은 라벨을 붙이며(dedup_and_label_file1). 제목은 norm_title()로 정규화해서 비교한다. 공백·괄호 차이로 같은 시험이 신규로 잡히는 사고를 막는다.

find_previous_statuses(db_history)로 이전 상태 이력을 끌어와 "모집중 → 종료" 같은 전이를 잡는다.

파이프라인 진행 상태

PipelineManager는 싱글턴이고 파이프라인 종류별로 상태를 따로 들고 있다:

current_step / total_steps / progress(%) / completed_steps / 실패 시 error
is_pipeline_running()  중복 실행 방지

수집은 수십 분 걸리므로 웹 UI가 진행률을 물어볼 창구가 필요하다. 동기 서비스는 asyncio.to_thread로 감싸 백그라운드에서 돈다(imok_pipeline.py).

함정

  • mfds_updater_service.pyDEFAULT_DB_MODE = "local"⚠️ 테스트용 주석이 달려 있다. 운영에 쓸 때 반드시 확인해라.
  • 외부 Gradle 프로젝트(MfdsUpdater2)를 subprocess로 부른다. 타임아웃 300초. 이 의존성이 있는지 먼저 확인.
  • Elasticsearch 색인(elasticsearch_service.py)은 선택 단계다. ES 없이도 수집·비교는 돈다.

유사 프로젝트

IMOK/iqvia가 같은 구조(excel_input/excel_old/excel_output/db)로 IQVIA 데이터를 처리한다. IMOK/ctrial-mapper는 수집물을 PI(연구자) 중심으로 재구성한다.

연관 skill

cancer-type-classify, excel-case-validator, regimen-extract