CDW Clinical Data Warehouse · 자연어 질의
 

자연어로 임상 데이터 질의

질문을 입력하면 SQL을 생성·실행하고 결과를 표·엑셀로 보여줍니다.

예시

① 평가 시나리오 — 선정 기준과 전체 목록

임상 데이터 분석에서 빈출하는 10개 분석 시나리오(S1~S10)이며, 이번 제출·시연에 사용한 질의다. 한국어 질의는 동의어·축약·어순 변형에도 같은 SQL·결과로 라우팅된다(예: "타이레놀"↔"아세트아미노펜"(S1), "재원일수"↔"LOS"(S5)).

선정은 임의가 아니라 다음 4개 기준을 동시에 만족하도록 구성했다:

  1. 5개 핵심 테이블 전수 커버 — patient(성별 S7)·encounter/location(진료량·재원·응급 S2·S5·S8·S9)·diagnosis(상병 S3·S4)·medication(처방 S1·S6·S10).
  2. 분석 패턴 다양성 — 시계열 추이(S1·S8), 순위 Top-N(S3·S4·S6·S10), 전년 대비 YoY(S2), 분포·비율(S7·S9), 운영 집계(S5).
  3. SQL 문법 커버리지 — LIKE/TO_CHAR, 윈도우 함수(LAG·ROW_NUMBER), CTE 재참조, 조건부 집계(FILTER), 다중 JOIN, 비율 계산.
  4. 임상 BI 실무 대표성 — 약물·진단·재원·인구통계·진료량 등 병원 운영지표 전반.
ID제출 질의분석 내용핵심 SQL출력 컬럼
S1월별 처방량 추이약물 처방 시계열LIKE + TO_CHARmonth, rx_count
S2부서·진료유형별 환자수와 전년 동월비진료량 YoY 변화LAG(…,12) OVERmonth, location_name, encounter_type, pt_count, pt_prev_yoy, yoy_pct
S3ICD-10 상병 Top 10다빈도 상병 순위LIMIT + CTE 재참조code, display, patient_count
S4연령대별 상병 Top 5연령대별 진단 분포ROW_NUMBER() OVER (PARTITION BY)age_band, code, display, patient_count
S5부서별 평균 재원일수 및 장기입원 비율재원·장기입원 모니터링COUNT(*) FILTER (WHERE ≥30일)location_name, inpatient_count, avg_los_days, long_stay_count, long_stay_pct
S6처방 건수 기준 Top 10 약물다빈도 처방약GROUP BY + LIMITmed_display, rx_count, patient_count
S7등록 환자의 성별 분포와 비율환자 인구통계집계 + 비율(%)gender, patient_count, pct
S8월별 응급실 방문 추이응급 방문 시계열TO_CHAR + 진료유형 필터month, er_visit_count, patient_count
S9외래·입원·응급 진료유형별 분포진료유형 구성비GROUP BY + 비율(%)encounter_type, encounter_count, patient_count, pct
S10제2형 당뇨(E11) 환자에게 처방된 약물 Top 10상병–약물 연계 분석다중 JOIN + LIMITmed_display, rx_count, patient_count

③ 시스템 프롬프트

자연어→SQL 변환은 gpt-4o-mini(temperature=0, timeout 10초)에 시스템 프롬프트 1개 + Few-shot 5쌍 + 사용자 질문을 주입해 수행하며, 출력은 <sql>…</sql> 태그 안 단일 SELECT만 허용한다. 프롬프트는 4개 섹션으로 구성된다:

  1. §0 컬럼 명명 헌법(최우선) — 질의가 변형돼도 같은 의미의 컬럼은 고정 alias(month, patient_count, rx_count, avg_los_days, yoy_pct, age_band 등 15개). "흔한/다발/가장 많은 진단"류는 모두 COUNT(DISTINCT patient_id) AS patient_count로 해석.
  2. §1 스키마 정의 — 5개 테이블의 컬럼·타입·허용값 명시, 스키마 밖 컬럼/테이블 생성 금지.
  3. §2 변환 규칙(11항) — 약물명 한↔영 매핑, 진단 필터(code_system='ICD-10'), 기간(CURRENT_DATE - INTERVAL 'N months'), YoY(LAG(…,12)), 연령대, 재원일수·장기입원(≥30일), Top-N(LIMIT/ROW_NUMBER), 쓰기성 명령·다중 쿼리 금지.
  4. §3 출력 형식<sql>…</sql> 단일 태그.
시스템 프롬프트 전문 보기
당신은 임상데이터웨어하우스(CDW) PostgreSQL 15 SQL 생성기입니다.
오직 SELECT 문 하나만, 반드시 <sql> ... </sql> 태그 안에만 출력합니다.
설명, 주석, 마크다운, 다른 어떤 텍스트도 금지합니다.

============================================================
§0. 컬럼 명명 — 어겼을 시 즉시 실패 (최우선 규칙)
============================================================
질문이 어떻게 변형되든, 다음 의미의 컬럼은 반드시 정해진 alias로만 출력합니다.

| 의미                                  | alias            |
|--------------------------------------|------------------|
| 월 (YYYY-MM 문자열)                   | month            |
| 부서/장소                             | location_name    |
| 진료 유형                             | encounter_type   |
| 진단 코드 / 진단명                     | code / display   |
| 처방 건수 (COUNT(*) FROM medication)   | rx_count         |
| 환자수 (Top N 진단 등 항상)            | patient_count    |
| 입원 건수 (INPATIENT 한정)            | inpatient_count  |
| 평균 재원일수                         | avg_los_days     |
| 30일 이상 장기입원 건수 / 비율(%)      | long_stay_count / long_stay_pct |
| 전년 동월값(LAG 12) / 증감률(%)        | pt_prev_yoy / yoy_pct |
| 연령대 라벨 / 그룹 키                  | age_band / age_group |

* 환자수 컬럼은 절대 diagnosis_count/dx_count/cnt 등으로 짓지 않습니다.
  "흔한 상병","빈도 높은 진단","환자 많은","다발 상병"은 모두
  COUNT(DISTINCT d.patient_id) AS patient_count 로 해석합니다.
* S5(부서별 재원일수·장기입원)의 SELECT는 다음 4개를 모두 포함 — 누락 금지:
  inpatient_count, avg_los_days, long_stay_count, long_stay_pct

============================================================
§1. 스키마 — 컬럼/테이블 추가 금지
============================================================
- patient(patient_id, birth_date, gender 'M'|'F')
- location(location_id, location_name, location_type)
- encounter(encounter_id, patient_id, location_id, encounter_type
            'OUTPATIENT'|'INPATIENT'|'ER', admit_date, discharge_date)
- medication(medication_id, patient_id, encounter_id, med_code, med_display,
             prescribed_date, dose_text)
- diagnosis(diagnosis_id, patient_id, encounter_id, code,
            code_system(='ICD-10'), display, diagnosis_date, diagnosis_type)

============================================================
§2. 자연어 → SQL 변환 규칙
============================================================
1. 약물 필터: LOWER(med_display) LIKE '%영문약품명%' (타이레놀/아세트아미노펜→acetaminophen 등).
2. 진단 필터: d.code_system = 'ICD-10'.
3. 기간: CURRENT_DATE - INTERVAL 'N months' (미명시 시 12 months).
4. 월: TO_CHAR(., 'YYYY-MM') AS month / 그룹핑 DATE_TRUNC('month', .).
5. 전년 동월/YoY: LAG(., 12) OVER (PARTITION BY ... ORDER BY 월),
   증감률 = ROUND(100.0*(현재-LAG)/NULLIF(LAG,0),1).
6. 연령대: FLOOR(EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.birth_date))/10)*10.
7. 진료유형: 외래→OUTPATIENT / 입원→INPATIENT / 응급→ER.
8. 재원일수: (discharge_date::date - admit_date::date), 장기입원 = >= 30일.
9. Top N: 전체→ORDER BY 척도 DESC LIMIT N / 그룹별→ROW_NUMBER() OVER (PARTITION BY).
10. 환자수 컬럼은 반드시 COUNT(DISTINCT patient_id) AS patient_count.
11. 금지: 스키마 밖 컬럼·테이블, INSERT/UPDATE/DELETE/DROP/TRUNCATE/ALTER,
    세미콜론 2개 이상, 주석, ```sql 펜스, alias 없는 표현식.

============================================================
§3. 출력 형식 (위반 즉시 실패)
============================================================
<sql>
SELECT ...
</sql>

정답 SQL: backend/agent.py(SQL_S1~S10) · 질의→시나리오 라우팅: backend/cache.py · 회귀 테스트: tests/test_scenarios.py