# PMPLUS20 테이블 매핑 가이드 (pharmacy-stats-api v2) ## 1. DB 연결 정보 | 구분 | 서버 | DB | SA 비밀번호 | |------|------|-----|------------| | v1 PharmIT3000 | `192.168.0.201\PM2014` | PM_PRES | `tmddls214!%(` | | v2 PMPLUS20 | `192.168.0.201\PMPLUS20` | PM_MAIN | `newpro2020!@` | > **IP는 반드시 192.168.0.201 고정** (두 인스턴스 SA 비밀번호 다름) --- ## 2. 테이블 매핑 (확정) ### 처방 조제 통계 (pharmacy-stats-api용) | 역할 | v1 (PharmIT3000) | v2 (PMPLUS20) | 검증 | |------|-----------------|---------------|------| | 처방 헤더 | `PM_PRES..PS_MAIN` | `PM_MAIN..TBSID040_03` | ✅ 확정 | | 수납/결제 | `PM_PRES..CD_SUNAB` | `PM_MAIN..TBSIR000_01` | ✅ 확정 | ### JOIN 관계 ```sql -- v1 FROM PM_PRES..PS_MAIN m LEFT JOIN PM_PRES..CD_SUNAB n ON n.PRESERIAL = m.PreSerial -- v2 FROM PM_MAIN..TBSID040_03 m LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.DRUG_SEQ = m.DRUG_SEQ ``` ### 참고: OTC 판매 매핑 (person-lookup-web-local용, 별도) | 역할 | v1 | v2 | |------|-----|-----| | OTC 판매 헤더 | SALE_MAIN | TBSIM040_28 | | OTC 판매 상세 | SALE_SUB | TBSIM040_29 | | OTC 결제 수납 | CD_SUNAB | TBSIR000_01 | --- ## 3. 컬럼 매핑 (확정) ### 3-1. 처방 헤더 (PS_MAIN → TBSID040_03) | v1 컬럼 | v2 컬럼 | 역할 | 검증 금액 (20260324) | 상태 | |---------|---------|------|---------------------|------| | `PreSerial` | `DRUG_SEQ` | 처방 일련번호 | 20260324000004 = 동일 | ✅ | | `INDATE` | `SUNAB_DT` | 조제일자 (YYYYMMDD) | 동일 | ✅ | | `PRICE_T` | `TOT_PRICE` | 보험 약제비 총액 | 9,476,000 = 동일 | ✅ | | `PRICE_C` | `INS_PRICE` | 청구금액 | 7,068,800 = 동일 | ✅ | | `PRICE_P` | `EXE_PRICE` | 본인부담금 | 2,407,200 = 동일 | ✅ | | `PRICE_N` | `REAL_PRICE` | 수납금액 | 2,838,420 (차이 23,420원 — 아래 참고) | ✅ | | `S_Prep` | `INS_PREP_PRICE` | 조제료 | 2,277,850 = 동일 | ✅ | | `Drug_T4` | `NON_DRUG_PRICE + EXP_EXE_PRICE` | 비급여 약제비 | 292,140+120,420=412,560 ✅ | ✅ | | `PaNum` | `PAT_JUMIN_NO` | 주민번호 (연령 계산) | 동일 | ✅ | | `PaName` | `PAT_NM` | 환자명 | 동일 | ✅ | | `OrderName` | `YOYANG_NM` | 처방 병원명 | 동일 | ✅ | ### 3-2. 보험구분 (PreGubun → MPRE_TYPE + MPRE_TYPE_GUBUN) v2에서 `MPRE_TYPE='4'`는 특수보험 그룹이며, `MPRE_TYPE_GUBUN`으로 세부 구분함. | v1 `PreGubun` | v2 `MPRE_TYPE` | v2 `MPRE_TYPE_GUBUN` | 의미 | 건수 (202603) | |---------------|---------------|---------------------|------|----------------| | `'0'` | `'0'` | `'0'` | 건강보험 | 3,325건 ✅ | | `'1'` | `'1'` | `'0'` | 의료급여1종 | 259건 ✅ | | `'2'` | `'2'` | `'0'` | 산재 | 17건 ✅ | | `'3'` | `'3'` | `'0'` | 의료급여2종 | 6건 ✅ | | `'7'` | `'4'` | `'C'` | **차상위1** | 7건 ✅ | | `'E'` | `'4'` | `'E'` | **보훈** | 32건 ✅ | | `'F'` | `'4'` | `'F'` | **차상위2** | 11건 ✅ | | `'9'` | `'9'` | `'0'` | 비급여 | 34건 ✅ | **v2에서 PreGubun 변환 SQL**: ```sql CASE WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'E' THEN 'E' -- 보훈 WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'F' THEN 'F' -- 차상위2 WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'C' THEN '7' -- 차상위1 ELSE ISNULL(m.MPRE_TYPE, '0') END AS PreGubun ``` > **주의**: `MPRE_TYPE`이 기본 보험구분. `PRES_GUBUN`은 다른 용도 (A/E 값) > **v2 전용 필터**: `PRES_GUBUN = 'E'`인 "재고보정" 레코드는 제외해야 함 > (DRUG_SEQ='20260324099999' 형태, v1에는 없음) ### 3-3. 시간가산 (Holiday → HD_ADD + PRES_TIME_GUBUN) v1의 `Holiday`는 단일 컬럼, v2는 두 컬럼 조합: | v2 `HD_ADD` | v2 `PRES_TIME_GUBUN` | v1 `Holiday` | 의미 | |------------|---------------------|-------------|------| | `'1'` | `'0'` | `'1'` | 일반 | ✅ | | `'2'` | `'0'` | `'2'` | 공휴일 가산 | ✅ | | `'1'` | `'1'` | `'3'` | 시간외 가산 | ✅ | | `'2'` | `'1'` | `'4'` | 공휴+시간외 | 추정 (데이터 미확인) | **v2에서 Holiday 계산 공식**: ```python # PMPLUS20 → PharmIT3000 Holiday 변환 if hd_add == '2' and time_gubun == '1': holiday = '4' # 공휴+시간외 elif hd_add == '2': holiday = '2' # 공휴 elif time_gubun == '1': holiday = '3' # 시간외 else: holiday = '1' # 일반 ``` ### 3-4. 수납/결제 (CD_SUNAB → TBSIR000_01) | v1 컬럼 | v2 컬럼 | 역할 | 상태 | |---------|---------|------|------| | `CD_SUNAB.PRESERIAL` | `TBSIR000_01.DRUG_SEQ` | JOIN 키 | ✅ | | `CD_SUNAB.Appr_Gubun` | `TBSIR000_01.APPR_GUBUN` | 결제수단 구분 | ✅ 값 동일 | | `CD_SUNAB.nAPPROVAL_NUM` | `TBSIR000_01.CARD_ADM_NO` | 승인번호 | ✅ | 결제 구분 값 분포 (20260324): - `'9'`: 179건 (카드) - `'A'`: 24건 - `'0'`: 5건 - `'2'`: 3건 (현금) --- ## 4. v2 쿼리 (변환 완료) ```sql SELECT m.DRUG_SEQ AS PreSerial, -- 보험구분 변환: MPRE_TYPE + MPRE_TYPE_GUBUN → PreGubun CASE WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'E' THEN 'E' -- 보훈 WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'F' THEN 'F' -- 차상위2 WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'C' THEN '7' -- 차상위1 ELSE ISNULL(m.MPRE_TYPE, '0') END AS PreGubun, m.PAT_JUMIN_NO AS PaNum, m.YOYANG_NM AS OrderName, -- Holiday 변환: HD_ADD + PRES_TIME_GUBUN → Holiday CASE WHEN m.HD_ADD = '2' AND m.PRES_TIME_GUBUN = '1' THEN '4' WHEN m.HD_ADD = '2' THEN '2' WHEN m.PRES_TIME_GUBUN = '1' THEN '3' ELSE '1' END AS Holiday, m.TOT_PRICE AS PRICE_T, (ISNULL(m.NON_DRUG_PRICE, 0) + ISNULL(m.EXP_EXE_PRICE, 0)) AS Drug_T4, m.INS_PREP_PRICE AS S_Prep, m.INS_PRICE AS PRICE_C, m.EXE_PRICE AS PRICE_P, m.REAL_PRICE AS PRICE_N, ISNULL(n.APPR_GUBUN, '') AS Appr_Gubun, ISNULL(n.CARD_ADM_NO, '') AS nAPPROVAL_NUM FROM PM_MAIN..TBSID040_03 m LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.DRUG_SEQ = m.DRUG_SEQ WHERE m.SUNAB_DT BETWEEN ? AND ? AND m.PRES_GUBUN != 'E' -- 재고보정 레코드 제외 ``` --- ## 5. 데이터 검증 결과 (20260324) ### 건수 | 항목 | v1 | v2 | 비고 | |------|-----|-----|------| | 전체 건수 | 211건 | 212건 | v2에 "재고보정" 1건 추가 (PRES_GUBUN='E' 필터로 해결) | | 필터 후 | 211건 | 211건 | ✅ 일치 | ### 금액 합계 | 항목 | v1 | v2 | 일치 | |------|-----|-----|------| | PRICE_T (보험약제비) | 9,476,000 | 9,476,000 | ✅ | | PRICE_C (청구금액) | 7,068,800 | 7,068,800 | ✅ | | PRICE_P (본인부담금) | 2,407,200 | 2,407,200 | ✅ | | S_Prep (조제료) | 2,277,850 | 2,277,850 | ✅ | | Drug_T4 (비급여약제비) | 412,560 | 412,560 | ✅ (NON_DRUG+EXP_EXE) | | PRICE_N (수납금액) | 2,815,000 | 2,838,420 | ⚠️ 차이 23,420원 (아래 설명) | ### PRICE_N vs REAL_PRICE 매핑 상세 분석 #### 결론: `REAL_PRICE = 진짜 수납금액` ✅ PMPLUS20의 `REAL_PRICE`는 수납테이블(TBSIR000_01)의 `RECP_AMT`, `SUNAB_PRICE`와 정확히 일치함. #### 검증 케이스 (20260324) | 환자 | 보험구분 | v1 PRICE_N | v2 REAL_PRICE | v2 수납(RECP_AMT) | 설명 | |------|---------|-----------|--------------|-----------------|------| | 박명순 | 건강보험+비급여 | 89,600 | 89,600 | 89,600 | 일반 케이스 ✅ | | 김상훈 | 건강보험+비급여 | 70,900 | 70,900 | 70,900 | 일반 케이스 ✅ | | 지성윤 | 차상위2(F) | 500 | 11,920 | 11,920 | 특수 케이스 ⚠️ | | 임해자 | 건강보험+선별급여 | 19,700 | 31,700 | 31,700 | 특수 케이스 ⚠️ | #### 일반 환자 (건강보험 + 비급여) ``` REAL_PRICE = EXE_PRICE(본인부담) + NON_DRUG_PRICE(비급여약) + EXP_EXE_PRICE(비급여조제) = PRICE_N (정확히 일치) ``` #### 특수 보험 환자 (차상위/선별급여) **지성윤 (차상위2)**: ``` EXE_PRICE = 500 (기본 본인부담) EXP_UNDER_EXE_PRICE = 11,420 (상한제초과 본인부담) REAL_PRICE = 500 + 11,420 = 11,920 (진짜 수납액) v1 PRICE_N = 500 ← 상한제초과분 누락! ``` **임해자 (건강보험 + 선별급여 80%)**: ``` EXE_PRICE = 19,700 (기본 본인부담) SE_PRICE_P_80 = 12,000 (선별급여 80% 본인부담) REAL_PRICE = 19,700 + 12,000 = 31,700 (진짜 수납액) v1 PRICE_N = 19,700 ← 선별급여분 누락! ``` #### v1 스키마 분석: 선별급여/상한제 데이터는 어디에? **V1 PharmIT3000에도 데이터는 있다!** `PS_Main_Sub` 테이블에 저장됨: ```sql -- V1 PharmIT3000에서 선별급여/상한제 확인 SELECT m.PreSerial, m.PRICE_N, s.SE_PRICE_P_80, -- 선별급여 80% 본인부담 s.SE_PRICE_P, -- 선별급여 본인부담 합계 c.ETC_CARD -- 실제 수납액 FROM PS_Main m JOIN PS_Main_Sub s ON s.PreSerial = m.PreSerial JOIN CD_SUNAB c ON c.PRESERIAL = m.PreSerial WHERE s.SE_PRICE_P > 0 ``` **V1 테이블 구조:** | 테이블 | 역할 | 선별급여/상한제 | |--------|-----|-------------------| | `PS_Main` | 처방 헤더 | ❌ `PRICE_N`만 (합산 안됨) | | `PS_Main_Sub` | 처방 부가정보 | ✅ `SE_PRICE_P_80`, `SE_PRICE_P` 등 | | `CD_SUNAB` | 수납 | ✅ `ETC_CARD`에 합산 결과 | **검증 케이스 (V1):** | 환자 | PS_Main.PRICE_N | PS_Main_Sub.SE_PRICE_P_80 | CD_SUNAB.ETC_CARD | |------|----------------|--------------------------|------------------| | 지성윤(차상위2) | 500 | 11,420 | 11,920 ✅ | | 임해자(선별급여) | 19,700 | 12,000 | 31,700 ✅ | **문제점**: `PS_Main.PRICE_N`이 `PS_Main_Sub`의 선별급여/상한제 금액을 **합산하지 않음**. #### 선별급여 컨럼 추가 이력 - `PS_Main_Sub` 테이블: **2010년 초기 스키마**에 생성 - `SE_PRICE_P_*` 커럼들: **나중에 추가됨** (선별급여 제도 시행 후) - 실제 데이터: 2026년부터 6건 존재 (최근 사용 시작) #### V1 수납 프로세스 (추정) 1. 수납 화면에서 `PS_Main` + `PS_Main_Sub` JOIN해서 총액 계산 2. 계산된 금액을 `CD_SUNAB.ETC_CARD`에 저장 3. **하지만** `PS_Main.PRICE_N`은 업데이트 안 함 (버그) #### V2 PMPLUS20 개선 사항 PMPLUS20은 이 문제를 해결: - `TBSID040_03.REAL_PRICE`에 **모든 항목 합산해서 저장** - `EXP_UNDER_EXE_PRICE`, `SE_PRICE_P_80` 등 별도 컨럼도 유지 - 수납테이블(TBSIR000_01)의 `RECP_AMT`와 일치 보장 **결론**: `REAL_PRICE` 매핑 유지가 맞음. V1의 `PRICE_N` 버그를 V2에서 수정한 것. ### 보험구분별 (2026년 3월 전체 검증) | 보험구분 | v1 건수 | v2 건수 | 일치 | |---------|--------|--------|------| | 0 (건강보험) | 3,359 | 3,359 | ✅ | | 1 (의료급여1종) | 271 | 271 | ✅ | | 2 (산재) | 17 | 17 | ✅ | | 3 (의료급여2종) | 6 | 6 | ✅ | | 7 (차상위1) | 8 | 8 | ✅ | | 9 (비급여) | 35 | 35 | ✅ | | E (보훈) | 32 | 32 | ✅ | | F (차상위2) | 11 | 11 | ✅ | ### 결제수단별 | 구분 | v1 건수 | v2 건수 | 일치 | |------|--------|--------|------| | 9 (카드) | 179 | 179 | ✅ | | A | 24 | 24 | ✅ | | 0 | 5 | 5 | ✅ | | 2 (현금) | 3 | 3 | ✅ | --- ## 6. 작업 이력 - [x] 테이블 매핑 확정 (PS_MAIN → TBSID040_03) - [x] 컬럼 매핑 확정 (13개 컬럼 전체) - [x] 수납 테이블 매핑 확정 (CD_SUNAB → TBSIR000_01) - [x] 변환 쿼리 작성 - [x] `queries/v2_pmplus20.py` 코드 수정 (2026-04-01) - [x] 보험구분 매핑 완료 - 보훈/차상위1/차상위2 정확히 변환 (2026-04-01) - [x] 2026년 3월 전체 기간 보험구분별 건수 검증 완료 (V1=V2 일치) - [ ] `/api/compare`로 UI 수치 검증 - [ ] 시간별/결제별/병원별 세부 수치 검증 --- ## 7. 참고: PMPLUS20 주요 테이블 구조 (210개 중 데이터 있는 것) | 테이블 | 행수 | 추정 역할 | |--------|------|----------| | TBSWH040_02 | 6,533,993 | 입고 이력 상세 | | TBSID040_05 | 1,543,564 | 조제 상세 (처방전 약품) | | TBSID040_04 | 1,542,882 | 조제 상세 (원본) | | TBSIR000_01 | 426,751 | **수납/결제 정보** | | TBSID040_03 | 394,802 | **처방 헤더 (= PS_MAIN)** | | TBSIM040_01 | 188,852 | 약품 마스터 (= CD_GOODS) | | TBSIM040_29 | 49,161 | OTC 판매 상세 (= SALE_SUB) | | TBSIM040_28 | 28,813 | OTC 판매 헤더 (= SALE_MAIN) |