Commit Graph
151 Commits
Author SHA1 Message Date
MsystechandClaude Fable 5 55c1a7fcf1 재원일수 파생 2종·병동 수간호사 — 254종 → 257종
OCM_재원일수_단입법(bzDataInterface.vb:7947 — 재원일수와 같은 분기
구조인데 +1 이 없다(끝날 미포함). MsgBox 갈래·개원일 분기 처리 동일),
OCM_재원일수_낮병동(:8083 — 낮병동 신청 구간 일수 +1, 입원일자_낮병동
과 같은 조건·결정 규칙), ETC_로그인사용자_병동_수간호사(:15507 —
직급 코드표(PSTCOD)의 '수간호사/간호수선생' 코드로 로그인 부서
재직자를 찾는다. 원문 두 쿼리 모두 정렬 없는 Rows(0) — 코드·사용자
코드 순으로 고정하고 한 왕복으로 접음. 원문의 부서 코드 문자열
연결은 바인드로 정상화).

- dotnet test 342/342 · --edit-smoke 실패 0 (이름 검사 257종)
- --db-patient ①~㊱ 전건 통과 (낮병동구간·수간호사 실행 확인 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 09:03:10 +09:00
MsystechandClaude Fable 5 edb9cc65cf 비급여처방·전문그룹과목·청구진료과목·임신주기 4종 — 250종 → 254종
OCM_비급여처방(bzDataInterface.vb:10964, Case Else + SRCH 조건 갈래 —
고지 대상(SgaNtiNon='Y')·수가 그룹 존재만. TYRCH UNION·BSGH 치료실·
GNRH·BSQ·SUSS 분기 미이식). 보험 코드로 금액 컬럼을 골라 0 초과만
"이름(##,###)원" CRLF 결합. 특수 계산코드(SGACFG Use_SgaCA1/2)
환자군은 레거시가 SELECT 에 없는 컬럼을 읽어 항상 빈 값(무증상
실패) — 설정을 읽어 그 환자군이면 사유로 말한다(이 DB 에 설정이
실제로 1건 있다 — ㉟ 확인).

OCM_진단서전문그룹과목(:11245 — DepBilCod='01'+세부과목이면 MEDDTL
이름, 아니면 GDPTBL 그룹과목. 원문의 0행 Rows(0) 크래시는 사유로).
OCM_청구진료과목(:11398 — 문맥 진료과 행의 DepBilCod 를 BDPTBL 로.
부서 시점과 유효기간 시점이 어긋나는 원문 특이점 그대로).
OCM_임신주기(:11845 — 사용자 설정(EDD/LMP) 조회가 코드 컬럼에
사용자 이름을 비교하는 결함으로 항상 0행 → 기본값 LMP 고정.
LMP 산식만 옮기고 도달 불가한 EDD 산식은 미이식. 주수는 총일수/7·
나머지, 월경일이 미래면 음수 일수만 — 원문 그대로).

- dotnet test 342/342 · --edit-smoke 실패 0 (이름 검사 254종)
- --db-patient ①~㊱ 전건 통과 (그룹과목 실값 3컬럼)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 08:59:49 +09:00
MsystechandClaude Fable 5 199a4142ee 퇴원약 8종 — 242종 → 250종
공통 조회(퇴원약DT, bzDataInterface.vb:10482 — 퇴원의약 오더를
일시·코드·이름 순, 1회 투여량은 SQL 계산) 하나에 파생 6종
(코드/명칭/용량/횟수/일수/용법 — 열 하나를 줄단위로, 마지막 뒤에도
개행, 용법은 LEFT JOIN 미매칭이면 빈 줄) + 고정폭 조립 2종:

OCM_퇴원약(:10617) — PadRight 를 "채울 개수"로 오용한 원문 결함
그대로(실제 의미는 총폭이라 정렬이 어긋난다), 코드가 20바이트를
넘으면 음수 인자 예외 → 사유(레거시는 오류창+빈 값). SATCH 비고
괄호 분기 미이식. OCM_퇴원약_New(:10733) — 바이트 고정폭(코드 10·
명칭 37), 용량·횟수·일수 패딩이 잘라낸 값이 아닌 원본 전체 바이트로
계산되는 결함 보존(5자 이상이면 음수 예외 → 사유).

행이 없으면 SRCH 만 "해당없음." 을 찍는다(:10704/:10864) — 우리
병원 분기라 그대로 값이 된다. CP949 바이트 길이는 ASCII 1·그 외 2
근사(레거시 LenK 대응), 바이트 절단은 문자 경계라 레거시의 반각
깨짐("?") 재절단 분기가 필요 없다(폭 최대 1바이트 차이 — 주석 기록).

- dotnet test 342/342 · --edit-smoke 실패 0 (이름 검사 250종)
- --db-patient ①~㊱ 전건 통과 (㉟ 에 퇴원약·수술집도의과·수술명칭 실행 확인 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 08:55:08 +09:00
MsystechandClaude Fable 5 03fff0a3a4 수술 파생 6종 — 236종 → 242종, ㉘ 수술 검증이 SKIP 에서 실검증으로
OCM_수술주상병(bzDataInterface.vb:13148 + GetOprInfDT_OKDMain :13364 —
기존 수술 조회와의 실질 차이가 상병 집계의 주상병 한정(OPRVAR1='0')
한 줄이라 SurgeryStore 에 mainDiagnosisOnly 를 얹었다. 치환이
빗나가면 필터 없는 값이 조용히 나가므로 가드로 던진다),
OCM_수술집도의_진료과/_그룹진료과(:3157/:3194 — 집도의(DTRUIDCOD)를
오늘 기준 마스터로 푸는 원문 특이점(OprStt 무필터·SYSDATE 유효기간)
그대로, 최신 수술 첫 행 고정), OCM_수술명칭(:3520 Case Else —
OPRCOD 를 수술일 순으로, 각 행 뒤 공백 1칸 + CRLF 결합. GNBEDRO·
HIMCHAN_BP 의 OPNAME 포함 분기 미이식).

사유 완결 2종: OCM_수술진단명(:3066 — PURME 전용, 그 외 병원은
Case 미매칭으로 항상 빈 값), OCM_수술처치(:3474 — SUSS·YJRCH 전용).

㉘ 정정: 수술 검증이 늘 SKIP 이던 원인은 소유자 탐색을 O_OprInf 로
한 것 — 실제 조회(GetOprInfDT)의 축은 S_OprInf(수술 신청)다.
표를 바로잡자 이 시험 DB 에서 수술 행 1건·OPNAME 실값이 검증됐다
(SurgeryStore 의 낡은 주석도 함께 정정).

- dotnet test 342/342 · --edit-smoke 실패 0 (이름 검사 242종)
- --db-patient ①~㊱ 전건 통과 — ㉘ 이 처음으로 실값 PASS
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 08:51:30 +09:00
MsystechandClaude Fable 5 02657b3893 상병 조합 11종 — 225종 → 236종
전부 기존 상병 조회(Okd 공용 쿼리, Scope All/Main/Sub) 재사용 —
왕복이 늘지 않고 결합·표기만 다르다(bzDataInterface.vb:8825-10481,
Case Else 갈래. SHCTR 무하이픈·BSGH·JEGG 배제·SPHH 본문 분기 미이식):

OCM_상병코드_상병명("코드-명(확진/의증)" 줄단위),
OCM_진단구분_상병코드_상병명("[주진단명] : "/"[의증]     : "(공백 5)/
"[부진단명] : "), OCM_진단구분_상병명("[주] /[부] "+명+확의),
OCM_상병명_상병코드(확진 접미가 " - 확증" — 레거시 오타로 보이지만
그대로), OCM_주상병일자(무구분 이어붙임 — 복수 주상병이 한 줄로
붙는 원문 그대로. 등록일시 12자 미만이면 레거시 예외→빈 값 재현),
OCM_상병코드_상병명_주상병("1. " 고정 번호 + PadRight 15),
OCM_상병코드_상병명_부상병(번호가 0부터 시작하는 결함 보존),
OCM_부상병명, OCM_주상병명_영어, OCM_주상병명_주상병코드.

OCM_주상병특정기호는 사유 완결 — 심사 상병 공용 쿼리(Bkd)의 SELECT
목록에 특정기호(OkdVeeCod)가 없어 행이 있으면 컬럼 예외→빈, 없어도
빈. 항상 빈 값인 확정 결함이다.

이름 게이트가 OCM_주상병명_주상병코드를 카탈로그 미등재로 잡았다 —
레거시 원문(:10429) 실존을 확인하고 등재(전수 추출 이후 추가된 함수).

- dotnet test 342/342 · --edit-smoke 실패 0 (이름 검사 236종)
- --db-patient ①~㊱ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 08:46:26 +09:00
MsystechandClaude Fable 5 8256b79800 나이 계열 7종·주민번호 마스킹 3종 — 215종 → 225종 (조사 라운드 7 시작)
PAT_나이(bzDataInterface.vb:373 Case Else — 통합 대표행의 AgeMonth
파생 컬럼과 같은 식을 SQL 로: UDF_GETAGE_FROMRESNUM(복호화, 적용일시),
dtCommonLib.vb:3725 확인. CGCH 분기·암호화 N 갈래 미이식),
PAT_나이_세(:417 — 산정코드 24* 는 UDF_GETAGE(PatBthDay), 그 밖은
AgeCheck), PAT_성별_나이(:359 — "성별/나이". HIMCHAN_BP·BSYD 분기
미이식), PAT_나이_개월수(:430 — 월 경계 -1, 기존 MonthsSince 재사용),
개월·일 4종(:436-508 — 30일 나눗셈, 새 DaysSince 로 분해).

AgeCheck(clsCommonLib.vb:665-974)를 ResidentNumber.AgeOf 로 이식 —
세기 접두 규칙(1·2→19, 3·4→20, 5·6→출생2자리<20 이면 20xx 아니면
19xx+월일 0101 강제, 7·8→서버 연도 2자리 비교, 그 외→18xx. REDCROSS·
BSYD·BSGH 특례 미이식), 만나이(생일 당일 차감 없음 — 2024-09-25 이후
전 병원 규칙), 1세 이하는 고정 365일. 테스트 5건 — 기대값 하나가
틀려 있었다(출생 2자리 "20"은 <20 이 아니라 1920년으로 강제 — 구현이
맞고 판정을 고침). 개월·일 4종의 기준일은 레거시가 PC 시계(Now)라
서버 오늘로 바꿈(의도적 이탈 — 단말마다 값이 갈리면 안 된다).

PAT_주민번호_Blind/SexTyp_Blind/SexTyp_BlindStar(:105-186 Case Else —
앞6 " - " 뒤 XXXXXXX / 성별1+XXXXXX / 성별1+******. SYBS 의 J-계열
서식 전체 노출 분기 미이식).

대조군 교체: PAT_나이가 이식되면서 ⑬·edit-smoke 의 "안 옮긴 태그"
대조군이 깨졌다(의도된 동작) — ETC_수술실간호사로 교체.
㊱ 신설: 나이 UDF 2종이 실값을 돌려준다(접두 없이 동작 확인).

- dotnet test 342/342 (AgeOf 5건 추가) · --edit-smoke 실패 0
- --db-patient ①~㊱ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 08:41:22 +09:00
MsystechandClaude Fable 5 abb08d0c6b 태그 분류 정정 — 이름 규칙과 실제 반환형이 어긋나는 10종
OCM_알러지_List·OCM_당뇨식칼로리_List 는 이름만 List 로 끝나고 값
한 줄을 돌려준다 — "List 끝 = 표" 규칙보다 먼저 PatientContext 로
잡아 환자 해석기의 실값이 틀린 사유("표를 돌려주는 태그")에 가려지지
않게 했다. 반대로 ETC_전체진료과_한글명/영문명·ETC_의사List_이름순·
ETC_진료과별의사List_이름순 4종은 이름이 규칙에 안 걸리는데
DataTable 을 돌려준다(bzDataInterface.vb:16797-16890) — Table 로
명시(콤보 채움용, 미리보기 한 줄 값이 아니라는 사유가 정확해진다).
GetNurseShtCod_GCRCH/GNBEDRO/SRH 3종은 내부 헬퍼 — NotATag.

참고: ETC_진료과별의사List_이름순은 원문에 :psDepCod 바인드 미등록
결함이 있다(파라미터 사전에 sWrkDte 만 추가 — 실행 시 바인드 불일치).

- dotnet test 338/338 · --edit-smoke 실패 0
- --db-patient ①~㉟ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 07:41:51 +09:00
MsystechandClaude Fable 5 d988a347f4 영상촬영이력 — 213종 → 214종
OCM_영상촬영이력(bzDataInterface.vb:13968) — 영상의학(XRAY) 접수 완료
오더의 일자·이름(30자 절단). 원문 결합 그대로: 한 줄에 2건, 줄 안은
"     /     "(공백 5), 줄 사이 CRLF, 각 건은 날짜+공백7+이름.
원문 정렬이 8자 절단 별칭이라 같은 날 안 순서가 임의였다 — 원본
일시(OdrDtm)로 정렬해 고정(상위 순서 동일).

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 214종)
- --db-patient ①~㉟ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:31:49 +09:00
MsystechandClaude Fable 5 f06740ae51 적용진료과·전과·간호정보 과거력 4종 — 209종 → 213종
OCM_적용진료과(bzDataInterface.vb:5659) — 통합 대표행의
AdpDepCod/AdpDtm 은 진료행 CodDepCod 와 적용일시의 별칭임을 확인
(dtHISOperatingInfo.vb:3699 — CodDepCod AdpDepCod, CodDtrCod
AdpDtrCod). 원문이 적용일시 12자리 전체를 8자리 유효기간과 비교하는
특이점 그대로. OCM_적용진료의사(:3939 — AdpDtrCod + 적용일 8자 →
UidNam, 기존 UserNameAt 재사용).

OCM_전과(:12924 + GetCodInfDT With_Join, dtCommonLib.vb:2777) —
진료행 전부(CodMtiSeq='0', 이름 조인은 오늘 기준·DepWrkTyp IN
('D','A'), SYBS·NYHY 의 'B' 추가 미이식)를 과가 바뀔 때만 "과명 /
시작 ~ 끝" 으로 AppendLine. 원문의 DefaultView 필터·정렬은 순회에
반영 안 되지만 SQL 이 같은 조건·순서(ORDER BY CodStrDtm)라 실질
무해였고, 무기한(2999-12-31) 판정은 Substring(1,8) 결함으로 절대
참이 안 되어 실제 값이 찍힌다 — 판정 없이 그대로.
새 사실: GetCodInfDT 는 항상 ORDER BY CodStrDtm(오름차순)이 있다 —
문맥 로드의 "최신순 1행" 결정과 다른 방향이지만 적용일시 구간에
행이 여럿 겹치는 비정상 데이터에서만 갈린다(기존 결정 유지, 기록만).

OCM_간호정보조사지_과거력(:13030 + GetNurseEmrDT :13523 +
GetNurseShtCod :13770) — 서식 우선순위(S113→S202→S163, SRCH 갈래)를
CASE 정렬로 접어 레거시 4왕복을 1왕복으로. 병력 결합 " ,"(공백+콤마)
와 무병력 시에도 붙는 개행까지 원문 그대로. GCRCH·SRH·GNBEDRO 의
서식 코드 분기는 미이식.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 213종)
- --db-patient ①~㉟ 전건 통과 (적용진료과 실값 5자)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:29:33 +09:00
MsystechandClaude Fable 5 7dcc4c2e48 진단서 의사·수술일자·외래실진료일 16종 — 193종 → 209종 (조사 라운드 6)
진단서 계열 8종(bzDataInterface.vb:4380-4845): 로그인 사용자가
의사(UidDtrYon='Y')면 로그인 사용자의 값, 아니면 담당의의 값 —
시점·기준일(입원 CodEndDtm·외래 접수일)이 담당의 프리페치와 같은
판정이라 그 행을 재사용한다. 의사명(UidNam)·명_외래(입원이면 조회
없이 빈)·영문(UidEngNam)·면허(UidLicNum)·전문의(UidSpcLic)·
세부전문의(UidDtlSpcLic)·전문과목(개원일 2022-07-01 분기, TYRCH
미이식)·청구진료과목_로그인(DepBilCod→BDPTBL, 원문의 같은 쿼리 2회
실행은 1회로). 로그인 행에 영문명·세부전문의번호 컬럼 2개 추가.

값 5종: OCM_당뇨식칼로리_List(:3569 — 원문부터 결정적),
OCM_수술일자(:2977 Case Else — 첫 수술 OprKey ASC; PURME·BSGH·
HIMCHAN·GJHNSS 분기 미이식), OCM_수술일자_마지막수술(:2999),
OCM_외래실진료일/_과목별(:1217/:1503 — 신환 첫 접수(폴백 전체 첫
접수)~마지막 접수 구간의 과 매칭 접수일 전부 "YYYY/MM/DD" ", " 결합.
같은 날 중복 출력도 원문 그대로. 본체 종료일 쿼리의 문자열 연결은
같은 값이라 바인드로 정상화).

사유 완결 3종: OCM_외래실진료일수(:1645 — 차트번호 자리에 시작일
바인드 + 없는 컬럼(OcmAcpDtm) 정렬, 상시 ORA-00904→빈 값인 레거시
결함), ETC_승인의사(:3892 — EmrKey 의존, 미리보기 부재),
OCM_수술소독간호사(:3028 — PURME 전용, 그 외 병원은 빈 SQL 실행).

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 209종)
- --db-patient ①~㉟ 전건 통과 (㉟ 4종 추가 — 청구과목 실값 4자)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:23:25 +09:00
MsystechandClaude Fable 5 4e6e086ece 유형·수납·진료기간·응급증상·전과내역 8종 — 185종 → 193종
OCM_계산유형(bzDataInterface.vb:5133 — 문맥 자격의 보험·계산 코드로
M_CalMst.CalNam), OCM_보험유형(:5186 — M_InsMst.InsNam),
OCM_수납일시(:5287 — 자격의 수납청구→수납 갱신일시. 원문 두 쿼리 다
비결정 Rows(0) — 최신 청구의 최신 수납으로 고정, 한 왕복으로 접음),
OCM_진료기간(:2236 — SRCH 갈래: 퇴원이면 접수~퇴원, 재원이면 최신
퇴원약 오더→퇴원예정→오늘 순. 외래: 최초 접수~최신 접수/오늘.
HANKOOK·SRH·GUPOSS 분기 미이식), OCM_진료기간_진료과(:2575 — 입원만
값. 외래는 두 번째 쿼리의 진료과 바인드 미등록(레거시 결함, :2644)으로
상시 ORA-01008→빈 값 — 사유로 완결), OCM_응급증상여부_YES/NO
(:12748/:12830 — 응급의료관리료 오더의 산정 여부. 코드 목록은
GetDtlMst("EMGEMGCOD") 그대로 + 0건이면 하드코딩 6종 폴백.
행이 없으면 레거시 $NoCheck$ — 체크 상태를 정하지 않는다.
SJRCH 분기 미이식), OCM_전과내역(:1103 — 과명+공백7+구간, CRLF 결합.
과 조인이 의사 조인에 종속되는 원문 모양 유지).

체크류 렌더 보완: 값을 못 만든 태그(사유)는 체크박스·라디오의 컨트롤을
그대로 둔다 — 레거시도 SetValue 를 건너뛴다. 라벨에 사유를 쓰면
"남 ☑" 이 "[태그 — 사유] ☑" 가 되어 서식이 깨진다.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 193종)
- --db-patient ①~㉟ 전건 통과 (㉟ 에 7종 추가 — 보험유형 실값 4자,
  응급 쿼리의 ORDER BY ROWID 모호성은 OdrKey 타이브레이크로 수정)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:16:37 +09:00
MsystechandClaude Fable 5 7bae9b9057 부가정보 2차분 20종 — 165종 → 185종 (워크플로 조사 라운드 4·5)
병렬 조사(읽기 11 + 반박 검증 11)로 39종을 분석하고 그중 20종을 이식:

값 18종 — OCM_발병일(:8125, 발병시작일 최신 고정), OCM_당일식이
(:12620, PIVOT 원문 유지·"아침 : X " 결합), OCM_알러지+조영제/약물/
음식/기타(:14941-15140, LISTAGG CRLF — 카테고리는 한글 리터럴),
OCM_알러지_List(:15142 — " - " 구분자가 한 번 켜지면 안 꺼지는
스티키 결함 보존), OCM_외출예상시작/종료일시(:15197/:15231 — 같은
행의 양끝, 원문부터 결정적), ETC_건강보험증번호(:15269 — PisInsCod=1
숫자 리터럴 유지), ETC_재해발생일(:15323), ETC_산재지정번호(:15727 —
진료과→병원 HspWrkNum, 문맥 시점), OCM_감염분류_감염정보(:14084),
OCM_퇴원분석_수술처치명(:13920 — 후행 개행까지 원문), OCM_병실인실
(:7065, 최신 입실 고정), OCM_병실차액(:7103 — 원문부터 첫 입실
결정적), OCM_VITAL접수일시_LAST(:12996 — 시각만 정렬하는 원문 유지),
OCM_진료과_전화번호(:2773 — 진료과 행 재사용, 왕복 0), OCM_전과일시/
전과일자(:12985/:12990 — 문맥 진료 행).

사유 완결 2종 — OCM_감염정보(:14122 원문 SQL 의 바인드 콜론 누락
→ 상시 ORA-00904 → 빈 값. 고치면 없던 값이 생기므로 사유로 안내),
OCM_Cosign_의사명(:14812 — 저장 기록 키(EmrKey) 의존, 미리보기 부재).

검증이 잡은 레거시 결함 3건 기록: 감염정보 콜론 누락, 알러지_List
스티키 구분자, 진료기간_진료과 SQL B 의 sDepCod 미바인드(다음 배치).

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 185종)
- --db-patient ①~㉟ 전건 통과 — ㉟ 신설: 새 조회 12종 실행 확인
  (O_OkdInf·O_ImlInf·O_OutAdmInf·S_AlgInf·S_IcnInf·S_DssOpn 전부 존재)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:09:38 +09:00
MsystechandClaude Fable 5 a33229b5e1 ETC_접속유저_부서번호 — 164종 → 165종
이름은 "부서번호"지만 값은 로그인 사용자 부서의 전화번호다
(bzDataInterface.vb:17287-17313 — M_DepMst.DepTelNum, SYSDATE 기준
M_UidMst 조인). 우리 로그인 조회가 이미 같은 시점·같은 조인으로
부서 행을 붙이고 있어 컬럼 하나(NVL(d.DepTelNum,' '))만 얹었다 —
왕복이 늘지 않는다. HisUser.DepPhone → UserField.DepPhone.

- dotnet test 338/338 · --edit-smoke 실패 0
- --db-patient ①~㉞ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 18:57:12 +09:00
MsystechandClaude Fable 5 7f147a3012 혈액형·LMP·BST·체중 파생 8종 — 156종 → 164종
OCM_혈액형(bzDataInterface.vb:11783 — S_BlpInf.BlpAboTyp, 갱신일시
최신 고정), OCM_LMP(:11813 — O_PrgInf.PrgLmpDte, 같은 표를 읽는
임신주기의 갱신일시 최신순으로 고정), OCM_BST/BST_LAST(:12042/:12710 —
바이탈과 다른 표 E_EmdInf_BST, VitalStore 를 표 인자화),
OCM_BMI(:12159 — 차트 전체 최신 체중/키², 반올림 2자리),
OCM_표준체중_LAST(:12234 — (키/100)²×여21·남22),
OCM_조정체중_LAST(:12284 — 표준+(실제-표준)×0.25, 키·체중이 같은 행),
OCM_비만도(:12333 — 이것만 내원 기준, 체중/Round(표준,0)×100).

BMI·표준·조정은 내원(EmrComNum)이 아니라 차트(EmrChtNum) 기준이다 —
주석 처리된 EmrComNum 이 그 흔적. 성별 불명이면 표준·조정은 레거시
그대로 "0" 이 찍히고, 비만도는 0 나눗셈이라 사유로 말한다.
OCM_임신주기는 사용자별 레지스트리 설정(DB_REGISTRY, UidCod 컬럼에
UidNam 을 비교하는 수상한 조건 포함) 의존이라 보류 — 별도 조사 대상.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 164종)
- --db-patient ①~㉞ 전건 통과 (S_BlpInf·O_PrgInf·E_EmdInf_BST 실행 확인)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 17:16:00 +09:00
MsystechandClaude Fable 5 f2f4cf81d3 협진과·입퇴원의사 4종 — 152종 → 156종
OCM_협진과(bzDataInterface.vb:6360 Case Else — 회신 완료 CstSttFlg='G'
협진의 회신 과, DISTINCT + 시작일 순 앞 2건을 ", " 결합. GNBEDRO 의
이중 DISTINCT 분기는 미이식), OCM_협진과_협진의(:6437 — M_UidMst 조인
+ 앞 3건을 "과[의사]" 결합), OCM_입원의사(:6499 — 접수 시점 담당의
코드 → 접수일자 기준 UidNam), OCM_퇴원의사(:6583 — 퇴원일시가 비면
바로 빈 값, ILV 면 퇴원 시점·아니면 현재 시점 담당의 → 퇴원일자
기준 이름. 원문의 "퇴원일시 빈 값" If 갈래는 위의 조기 반환 탓에
도달 불가한 데드코드라 옮기지 않았다).

CodInfAt(진료과+담당의 코드 행)·UserNameAt·Consults 를 신설.
이 시험 DB 에 O_CstInf 가 15,224행 있어 ㉞(회신 완료 협진이 있는
내원 → 협진과 실값)를 신설했다 — 2건 결합 확인.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 156종)
- --db-patient ①~㉞ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 17:08:58 +09:00
MsystechandClaude Fable 5 c0ad782b6a 진료과 파생·입퇴원과 8종 — 144종 → 152종, OCM_진료과 개원일 분기 복원
진료과 파생 4종(bzDataInterface.vb:5556-6007): 그룹코드("한글명(그룹
코드)" 결합 — 행이 있으면 조각이 비어도 결합), 영어(DepEngNam),
대외명칭(DepOfcNam), 약어명칭(DepBrfNam). 전부 프리페치된 진료과
행(M_DepMst SELECT *)의 다른 컬럼이라 왕복이 늘지 않는다. 레거시의
시점 판정(퇴원→퇴원시점·재원→현재·외래→접수)은 문맥 적용일시와 같은
규칙이다 — 자격이 퇴원보다 먼저 끝난 희귀 케이스만 갈린다(주석).

입원과 2종(:6009-6110)은 접수 시점, 퇴원과 2종(:6162-6318)은
퇴원(ILV)/현재 시점의 진료과 — 문맥과 다른 시점이라
DepartmentCodeAt(GetCodInfDT 대응, 시작일시 최신 1행 고정)과
DischargeDepartment(P_ComInf 에 날짜 조건만으로 M_DepMst 를 조인하는
원문 모양 그대로)를 신설했다. 퇴원과 본체의 If/Else 동일 쿼리(낮병동
주석과 달리 복붙 결함 — 퇴원일시 비면 0행→빈)도 그대로 보존,
한글명칭만 접수일시 폴백이 실제로 있다.

OCM_진료과: 개원일(HspStrDte)이 요양기관 행에 생겨 레거시 분기
(2022-07-01 이후 개원 + 대외명칭 있음 → 대외명칭)를 그대로 복원 —
전에는 개원일이 없어 DepKorNam 으로 고정했었다.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 152종)
- --db-patient ①~㉝ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 17:04:26 +09:00
MsystechandClaude Fable 5 975aff4211 입·퇴원 일시와 재원일수 13종 — 131종 → 144종
FromStay 12종(bzDataInterface.vb:7152-7940, 전부 Case Else 갈래):
입원시간(HH:MM)·입원일시(이름과 달리 날짜만 — SJRCH·BSYD 만 시각
포함)·입원일시_영문·입실시간(ComTrsDtm 슬래시 12자)·의사퇴원예고일시
(ComSchLev)·퇴원예정일시(ComLevSch)·퇴원시간·퇴원일시·퇴원예고일시
(ILV→ComLevDtm / ILP→ComLevSch)·재원일수·퇴원예고재원일수
(DateDiff(Day)+1). FromExtra 에 입원일자_낮병동(:7260 — P_DcpInf
구간 매칭, 시작일 최신 행 고정).

MsgBox 갈래("퇴원일자를 현재로 설정하시겠습니까?")는 조회 중 대화
상자라 옮기지 않고 사유로 말한다. 그 질문을 건너뛰는 개원일 분기
(HspStrDte>=20200901 → 빈, 외래 <20190501 → 오늘)는 그대로 옮겼다 —
개원일과 서버 오늘을 요양기관 행에 컬럼 2개로 추가(HspStrDte,
ServerToday). 재원일수의 HspCod >= "KBJY" 는 등호가 아닌 크기비교
(레거시 오타로 보임) — MsgBox 갈래라 사유로 흡수된다.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 144종)
- --db-patient ①~㉝ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 16:56:54 +09:00
MsystechandClaude Fable 5 ec47c98bdd 내원 이력 6종 — 125종 → 131종
OCM_FollowUp(bzDataInterface.vb:1028 — 지금 이후 취소 아닌 내원.
원문의 ROWNUM 은 ORDER BY 보다 먼저 걸려 임의 행이었다 — 인라인뷰
정렬로 "가장 가까운 미래 내원"에 고정), OCM_외출외박신청일/종료일
(:1883/:1936 — 오늘 포함 CowSlpOut='Y' 구간의 시작/끝. 0행이면
서버 현재시각이 나오는 레거시 폴백을 SQL NVL 로 그대로 옮겼다 —
㉛에서 12자 폴백 확인), OCM_최초내원일(:1988 Case Else — 이 쿼리는
원문부터 인라인뷰 정렬이 있어 결정적. KIMEYE 분기 미이식),
OCM_초진일_발병일(:2040)·OCM_신환_초진일(:2138 — 진료과 앞 2자를
레거시는 GetCodInfDT 임의 행에서 뽑았지만 우리는 문맥의 진료 행에서).

"지금/오늘"은 전부 서버 시각(SYSDATE) — 레거시 SystemDateTime 과
같은 의미이고 단말 시계에 좌우되지 않는다.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 131종)
- --db-patient ①~㉝ 전건 통과 — ㉝ 신설: 재진 있는 차트로 실값 검증
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 16:50:25 +09:00
MsystechandClaude Fable 5 4642439ffd 환자 부가정보 7종 — 118종 → 125종
PAT_실제생년월일(bzDataInterface.vb:226 — P_PatEtcInf.PatEtcBirDte,
8자리·연월일 조각이 0 이 아닐 때만, YYYYMMDD 원문 그대로),
PAT_국적(:639 — M_DtlMst NATCOD), PAT_장애등급(:687 — P_pdsoInf
PdsoFlg='E', PdsoGrd 두 번째 글자), PAT_건보세대주명(:732 — P_PISINF
⨝P_CoiInf, PisInsCod='1'), PAT_건보_급여_세대주명(:780 — 자격
(코드·순번) 직접 지정; _Refer 갈래는 미리보기 미설정이라 자기 내원만),
PAT_협력업체(:837 — ComCoopHsp→M_DtlMst, 원문대로 DtlTblCod 없음),
PAT_보호자연락처(:881 — PatEtcGrdnPhn IS NOT NULL).

새 PatientExtraStore 에 자기 SQL 을 모았다. 차트번호는 문맥이 Trim
하므로 CHAR 컬럼은 RPAD(:c,10) 복원 매칭 — ㉜ 가 실값으로 증명한다.
P_pdsoInf 만 VARCHAR2 라 IN (:c, RPAD(:c,10)) 양쪽을 본다
(㉛ [조사]: 이 DB 는 패딩 행 0 — 운영 DB 가 다를 수 있어 유지).
ORDER BY 없는 Rows(0)/RowNum 1 은 전부 정렬 명시로 고정(갱신일시·
시작일 최신) — 장애등급의 P_ComInf 조인은 행만 곱해서 EXISTS 로 대체.

해석기는 지연 + 태그별 캐시(이 태그 없는 서식이 대부분).

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 125종)
- --db-patient ①~㉜ 전건 통과 — ㉛ 7종 실행, ㉜ 국적 실값 검증 신설
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 16:45:03 +09:00
MsystechandClaude Fable 5 adb3572e40 입통원구분·성별표기·내원일시 8종 — 110종 → 118종, 체크류 SetValue 의미론
Computed 6종: OCM_입통원구분_입원/통원(Boolean→"True"/"False",
bzDataInterface.vb:989/1009, PatTyp=ComPatTyp 는 bzPatientInfo.vb:639),
OCM_외래내원일시(:1068), OCM_외래내원일자_영문(:1091 —
Data2Format_ENG "MM-DD-YYYY" 는 월 영문화가 아니라 자리 재배열,
clsCommonLib.vb:8591), OCM_입원내원일시/일자(:2824/:2840 Case Else —
KIMEYE 만 ComTrsDtm, 미이식 주석).

FromResidentNumber 2종: PAT_성별_남/여(:317/:338) — F/M→N·Y 변환,
그 외 값은 레거시가 Select Case Else 없이 그대로 돌려주므로 그대로.

WithTagValue 에 체크류 갈래 신설: CheckBox/RadioButton 에 태그가
걸리면 Text 치환이 아니라 Checked 를 바꾼다 — 레거시 SetValue 가
"Y"/"TRUE"(대소문자 무시)만 체크로 바꾼다(CheckBox.vb:688-696 ·
RadioButton.vb:338-351). 값을 Text 에 넣으면 라벨이 "True" 가 된다.
미리보기·인쇄가 같은 BuildPageVisual 경로라 한 곳 수정으로 둘 다 반영.

PAT_성별_나이는 AgeCheck 의존이라 나이 계열과 함께 보류.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 118종)
- --db-patient ①~㉚ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 16:33:58 +09:00
MsystechandClaude Fable 5 ba6da2252f 병리판독의·진단검사의 4종 — 106종 → 110종
ETC_병리판독의사명/전문의번호(bzDataInterface.vb:17318/:17515 — M_UidMst
UidDtrYon='Y' AND UidDepCod='TLAB' + 유효기간), ETC_진단검사의사명/
전문의번호(:17550/:17655 — M_DepMst DepGrpCod='LAB' 조인 + UidLicNum
IS NOT NULL). 환자와 무관한 원내 의사 조회지만 레거시가 환자 태그로
노출하므로 PatientTagResolver 에 둔다(lazy 소스 + 캐시 — 이 태그가
없는 서식이 대부분이라 프리페치하지 않는다).

의도적 이탈 하나: 원문엔 ORDER BY 가 없어 TLAB 의사가 둘이면
이름과 전문의번호가 서로 다른 사람이 될 수 있었다(태그마다 따로
조회하므로). ORDER BY UidCod + 1행 고정으로 두 태그가 반드시 같은
사람을 가리키게 했다 — 담당의 때 정한 결정 규칙의 세 번째 적용.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 110종)
- --db-patient ①~㉚ 전건 통과 — ㉚ 신설: 두 조회가 예외 없이 돈다
  (이 시험 DB 는 TLAB·LAB 의사 0행 — 태그는 사유로 완결되는 정상 경로)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 16:18:37 +09:00
MsystechandClaude Opus 5 4dad271e9c 수술 일정 6종을 옮긴다 — 100종 → 106종
S_OprInf(수술 접수 일정 — GetOprInfDT 의 E_OprInf 축 큰 조회와 다른 표) 를
OprComNum + OprStt='E' 로 거르고 OprKey 순으로 한 행:

- ETC_수술일자_1_몇년/_1_몇년2자리/_2_몇월/_3_몇일 — DESC(마지막 수술, '수술실 요청' 주석 그대로), OprDte 자름
- OCM_수술일자_마지막수술 — DESC, "9999-99-99" 형식
- OCM_수술일자 — Else 갈래는 ASC(첫 수술). PURME(주사오더 O_OdrInf 조회)·
  BSGH·HIMCHAN_*·GJHNSS(DESC) 분기는 미이식 안내 — Else 로 뭉개면
  그 병원에서 첫/마지막이 뒤바뀐 날짜가 조용히 찍힌다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 106종)
- --db-patient 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:07:27 +09:00
MsystechandClaude Opus 5 e065c5a645 로그인 계열 6종 — 세션 사용자 행을 넓혀 해결. 95종 → 100종 + Dead 1
## 세션 조회 확장(UserContextStore)

M_UidMst 행에서 UidLicNum·UidSpcLic 를 더 읽고, 직종명은 레거시 그대로
DtsDtlCod='JOBCOD' 만으로 M_DtsMst 를 조인해(bzDataInterface.vb:17944 —
DtsTblCod 조건이 없다) DtsCodNam 을 가져온다. HisUser 에 세 필드 추가.

## 태그 5종 + Dead 1종

- ETC_로그인_의사면허번호(UidLicNum) · 전문의번호(UidSpcLic) · 직종(DtsCodNam)
- ETC_로그인_근무부서 = 세션 부서 한글명. 레거시는 개원일(HspStrDte>=20220701)로
  DepOfcNam 을 가르지만 개원일 미상 결정에 따라 DepKorNam 갈래다(기존 결정과 동일).
- ETC_로그인_근무부서_사용자명 = "부서-이름". 레거시는 UidNam 으로 걸러
  동명이인에서 남의 부서가 나올 수 있는데 세션 사용자 행이라 결정적이다(주석 기록).
- ETC_로그인_직급 → Dead. 값은 UidNam 을 담고 조건은 UidCod 로 걸어(:17262·17272)
  실무상 늘 빈칸이던 태그다. 고치면 없던 값이 갑자기 채워지므로 합의 전까지 레거시 유지.

레거시의 면허·전문의번호 조회는 유효기간 없이 M_DepMst 를 불필요하게 조인해
ORDER BY 없이 첫 행을 집는 비결정 조회였다(조사 문서 risk) — 세션 행(유효기간 검증
완료)에서 읽는 쪽이 결정적이고 값 원천(M_UidMst 같은 행)은 같다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 100종)
- --db-patient 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:03:54 +09:00
MsystechandClaude Opus 5 94bdcc763f 바이탈 17종 + 주치의 1종 + _Refer 사유 정정 — 76종 → 95종
## 바이탈(측정치) 17종

E_EmdInf_VITAL ⨝ E_EmrInf(삭제 제외) 를 내원으로 거르고 해당 컬럼이 빈 값이 아닌
행을 측정일시 순으로 한 건 집는다 — 무인자는 첫 측정(ASC), _LAST 는 마지막(DESC).
키/몸무게/체온/맥박/호흡/SPO2 (±LAST) · 혈압/혈압_LAST(BPS||'/'||BPD) ·
혈압_BPS/_BPD(단독값이지만 널 필터는 둘 다 — 레거시 그대로) · VITAL접수일시_LAST(HH:MM).

태그별 (식·컬럼·필터·정렬)을 전부 본문에서 확인해 표로 박았다(:11508-13046).
조회는 태그가 물을 때 한 값씩, 태그별 1회 캐시.

## 머리둘레 2종은 레거시가 고장이다

SELECT 는 EmdHc AS HC 인데 Item("BP") 를 읽는다(:11744, 12581) —
행이 있으면 예외 → MessageBox → "". 레거시에서 한 번도 값이 나온 적 없는 태그다.
그대로 빈 값 + "레거시 결함(컬럼명 불일치)으로 항상 빈 값이던 태그" 사유로 둔다.
고쳐서 값을 내면 레거시와 달라진다 — 고칠지는 별도 결정.

## 실DB 확인

--db-patient ㉙ 신규: 내원 6004487 에서 첫/마지막 몸무게 2자.
표 부재 가능성도 수술과 같은 방식으로 가른다(ORA-00942 명시).

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 95종)
- --db-patient ①~㉙ 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:59:04 +09:00
MsystechandClaude Opus 5 3e9d5b5cf3 STFDTR(주치의)을 옮기고 _Refer 계열의 진실을 밝힌다 — 75종 → 76종 + 사유 정정 ~40종
## OCM_STFDTR

담당의 조회를 코드 컬럼만 일반화했다(CodDtrCod → CodStfDtr 파라미터).
시각 갈래는 담당의와 같다(bzDataInterface.vb:4026-4067).
코드가 비어 있으면 레거시도 즉시 "" — 진료과 행이 돌아오면 주치의 행이 아니므로 비운다.

## _Refer 계열은 "안 옮긴 것"이 아니었다

참조 내원은 EMR 실행 화면(ActionTag — fmCodLstActionTag.vb:54 등)에서
<b>사용자가 지정하는 값</b>이고, 레거시 서식생성기 미리보기(TestPatientSetting)는
그것을 설정하지 않는다. 즉 <b>레거시 미리보기에서도 _Refer 태그는 전부 빈 값</b>이다.

지금까지 그 태그들이 "환자 태그이지만 아직 옮기지 않았습니다"로 나왔는데 그건 틀린 사유다 —
옮길 것이 없는 게 아니라, 이 화면에는 참조 내원이라는 개념 자체가 없다.
사유를 갈랐다: "참조 내원 태그입니다 — 참조 내원은 EMR 실행 화면에서 지정되며,
레거시 미리보기에서도 빈 값입니다". _Refer ~40종의 표시가 이것으로 바뀐다.

이로써 <b>서식생성기 미리보기 기준의 레거시 호환</b>에서 _Refer 계열은 완결이다 —
레거시가 빈 값인 자리는 빈 값(+정확한 사유)이 맞는 이식이다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 76종)
- --db-patient 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:51:29 +09:00
MsystechandClaude Opus 5 26d9606d1e 요양기관 12종을 옮긴다 — 왕복 12회를 1회로 접었다. 63종 → 75종
ETC_요양기관명칭_* 계열(bzDataInterface.vb:15656-16709). 12종 전부
<b>같은 조인(M_DepMst⨝M_HspMst)의 다른 컬럼</b>이라는 것을 본문에서 확인했다 —
병원명(이미 이식)·병원명_영문·병원대표자·병원장·병원장귀하·전화번호·팩스번호·
전화_팩스·영문_전화_팩스·주소·주소_영문·사업자등록번호·요양기관기호.

레거시는 태그마다 이 조인을 따로 돌린다(12종 = 왕복 12회).
컬럼 9개를 한 번에 뽑아 행으로 들고, 값 선택은 해석기가 한다.
HospitalName(문자열) → HospitalRow(행) 로 일반화했다.

접미·결합도 본문 그대로다:
  병원장 = 병원명+"장" / 병원장귀하 = +"장 귀하"
  전화_팩스 = "Tel : … FAX : …" / 영문판 = "Tel : +82 - … FAX : +82 - …"
접두·접미가 붙는 태그는 <b>재료 컬럼</b>으로 빈 값을 판정한다 —
병원명이 비면 "장"만 찍히는 것을 값으로 세지 않는다.

실DB 확인: --db-patient ㉔ 가 9열·병원명 6자를 돌려줬다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 75종)
- --db-patient 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:42:26 +09:00
MsystechandClaude Opus 5 ef4f36419f 심사 상병 4종을 옮긴다 — 59종 → 63종. 워크플로가 조사한 new 는 이제 상병·수술·심사까지 끝났다
Bkd_상병쿼리(bzDataInterface.vb:14625-14811)의 이식. DiagnosisStore 와 구조가 같고
표만 B_OkdInf(심사분)다. SQL 52줄 원문 기계 추출(블록1 UNION 블록2, 주/부 필터는
각 블록의 RN 접기 앞). 병원 분기는 KIMEYE 하나 — 그 병원만 미이식 안내.

태그 4종(본문 확인): 심사_주상병명/코드는 첫 행, 심사_부상병명/코드는 줄바꿈 결합
(가로형이 없다).

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 63종)
- --db-patient 전건 통과 (B_OkdInf 도 이 시험 DB 에 없을 수 있어 수술과 같은 취급 —
  런타임에서 해석기가 예외를 사유로 바꾼다)
- --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:25:49 +09:00
MsystechandClaude Opus 5 460f6c4a91 수술 8종을 옮긴다 — 그리고 이 시험 DB 에는 O_OprInf 가 없다는 것을 알았다. 51종 → 59종
## GetOprInfDT(bzDataInterface.vb:13203-13363)의 이식

상병과 달리 병원 분기가 없다(Select Case 0회). 바인드는 내원번호 하나 —
접수일자 조건은 원문에서 이미 주석 처리된 죽은 코드다.
SQL 133줄을 원문에서 기계 추출했다(O_OprInf 축, 수술명·부위·상병·집도의·마취의 LISTAGG).

소비 태그 8종이 전부 <b>첫 행에서 컬럼 하나</b>를 집는다:
  OCM_OPNAME · OPRCODNAM · DX · 수술상병(OkdNam) · 수술부위(OprRegionName)
  수술_OprPatETC · 수술집도의(OprDtrNam) · 수술마취의사(AneUidCod)

원문 Catch 는 ErrorMessageBox 를 띄운다 — 조회 함수의 대화상자 부작용은 옮기지 않고
던져서 해석기가 사유로 바꾼다. 수술 조회도 상병과 같은 지연 정책이다
(수술 태그가 없는 서식이 다수라 환자를 붙일 때 무조건 읽지 않는다).

## 검증의 한계를 정확히 적는다

--db-patient ㉘ 이 O_OprInf 에서 내원을 못 찾았고, 처음엔 "행이 없다"로 적었다.
가려 보니 <b>ORA-00942 — 이 시험 DB(SRCH_TEST)에 표 자체가 없다.</b>
즉 수술 태그는 여기서 실행 검증이 불가능하고 운영 DB 에서만 확인할 수 있다.
SKIP 문구를 그 사실대로 고쳤다 — "확인했다"고 적을 뻔한 것을 진단이 막았다.
런타임에서는 해석기가 예외를 잡아 "수술 조회에 실패했습니다"로 말한다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 59종)
- --db-patient ①~㉗ 통과, ㉘ SKIP(표 부재 명시)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:22:16 +09:00
MsystechandClaude Opus 5 0da3305a75 상병 11종을 옮긴다 — SQL 은 원문에서 기계 추출했다. 40종 → 51종
## Okd_상병쿼리(bzDataInterface.vb:14160-14623)의 이식

구조: 블록1(M_KcdMst 와 맞는 상병, INNER JOIN) UNION 블록2(마스터에 없는 코드 —
LEFT JOIN 후 KcdCod IS NULL). 주/부 필터(OkdDspSeq='1' / <>'1')가 <b>각 블록에,
ROW_NUMBER 접기 앞에</b> 붙는다. 그래서 전체(T)를 받아 클라이언트에서 가르면 안 된다 —
같은 코드가 주·부 양쪽에 있으면 RN 접기가 한쪽을 지워 결과가 갈린다.
갈래(T/M/S)별로 따로 조회하되, 태그가 물을 때 한 번만 읽고 캐시한다.

SQL 은 손으로 다시 치지 않고 <b>VB 원문에서 기계 추출했다</b>(Select Case 를
Case Else 경로로 시뮬레이션, AppendLine 페이로드만 수집) — 55줄이 공백까지 원문 그대로다.

## 병원 분기를 얼버무리지 않는다

원문은 쿼리 자체가 6갈래(KIMEYE·NYJB·SYBS·HIMCHAN_CW·SPHH·Else)로 갈리고,
이름 규칙이 JEGG(배제 접미), 코드 규칙이 BSGH(KcdSeqCod)에서 또 갈린다.
이 DB(SRCH)의 Case Else 만 옮겼고, 그 병원들에서는 <b>미이식이라고 말한다</b> —
Else 로 뭉개면 그 병원 서식에 다른 모양의 상병이 조용히 찍힌다.

NYJB 갈래에는 사용자 코드를 SQL 에 <b>문자열로 잇는</b> 주입 구멍도 있다(:14235) —
옮길 때 바인드로 바꿔야 한다는 것을 조사 문서가 이미 적어 뒀다.

## 태그 11종 (전부 본문에서 결합 규칙 확인)

  상병명_가로/세로 · 영어_가로/세로 · 코드_가로/세로   전체(T)
  부상병코드 · 부상병POA                               부(S)
  주상병명 · 주상병코드 · 주상병POA                    주(M) 첫 행

가로 = ", " 구분, 세로 = 줄바꿈. 한글명에만 확→(확진)·의→(의증) 접미.
주상병명은 접미 없이 KcdKorNam(:10126), 주상병코드는 KcdElcCod(:9744).

## 실DB 확인 (--db-patient ㉕~㉗ 신규)

O_OkdInf 에 행을 가진 내원을 찾아 걸었다(행 없는 내원으로 판정하는 함정을 이미 두 번 밟았다).
내원 6005266: 전체 1행 = 주 1 + 부 0, 한글명 11자. 주/부 필터 분해가 전체와 일치한다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 51종)
- --db-patient ①~㉗ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:15:29 +09:00
MsystechandClaude Opus 5 9278b39a6d conflict 6건을 고치고 재료 재사용으로 10종을 더한다 — 30종 → 40종
## conflict 6건 (통합 계획의 "고칠 것")

값이 바뀌는 것 둘:
- ETC_현재년도 — "2026" → "2026년"(bzDataInterface.vb:16923 의 + "년").
  형제 ETC_현재일시_1_년 은 접미가 없어 둘을 갈랐다(ClockFormat.YearSuffixed 신설).
  "2026" 을 단정하던 테스트도 고쳤다.
- ETC_로그인_사용자ID_사용자명 — 레거시(:17930)는 빈 값 검사 없이 항상 결합한다.
  한쪽이 비어도 "U01." 이 그대로 나간다. 조건을 붙였던 것이 레거시와 달랐다.

행 선택이 바뀌는 것 둘:
- ETC_로그인_사용자연락처 / 원내연락처 — 전에는 M_UidMst 를 조건 없이 첫 행.
  이력이 여러 행인 사용자에서 옛 연락처가 나올 수 있었다.
  유효기간(SYSDATE 일자 BETWEEN)으로 고르고, 0행이면 레거시 폴백(:10641)처럼
  조건 없이 다시 찾는다.

의도적 차이로 기록한 것 둘(코드 주석):
- 유효기간 판정 일자 — 레거시는 로그인 시점, 여기는 SYSDATE(자정 넘긴 세션에서만 갈림).
- Trim — 레거시는 무가공, 여기는 걷는다(CHAR 잔여 공백이 종이에 찍히는 쪽이 더 이상하다).

## new 48종 중 재료 재사용으로 되는 10종

워크플로 매핑 상세(docs/new48-mappings.json)를 붙여 보니 46종이 needs-lookup 인데,
그중 10종은 <b>이미 든 행의 다른 컬럼</b>이거나 문맥 계산이었다:

- 담당의 행 재사용 3: OCM_담당의사_영문(UidEngNam) · 면허번호(UidLicNum) · 전문의번호(UidSpcLic)
  — 조회 갈래가 OCM_담당의사와 문자 그대로 같고 집는 컬럼만 다르다(:4094·4119·4144)
- CowInf 재사용 3: OCM_병실베드(CowBedCod) · 병동_병실 · 병동_병실_베드(" / " 결합, 행 없으면 "")
- 문맥 계산 1: OCM_내원유형(입원/응급실/외래 — ComPatTyp·ComEmgYon)
- User 계열 3: OCM_User진료과 · _한글명칭 · User진료의사명.
  로그인 사용자가 의사(UidDtrYon=Y)면 사용자의 과·이름, 아니면 환자의 것.
  HisUser 에 DoctorYn·DepOfficialName 을 추가했다(세션 조회 SELECT 확장).

남은 new 는 새 조회가 필요한 세 무리다: 상병(Okd_상병쿼리) · 수술(GetOprInfDT) ·
심사(Bkd_상병쿼리). 다음 배치.

## 게이트

- dotnet test 338/338 (현재년도·ID.사용자명 판정 갱신)
- --edit-smoke 실패 0 (이름 검사 40종)
- --db-patient ①~㉔ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:05:38 +09:00
MsystechandClaude Opus 5 e62e43d176 진료과·병동·병실·요양기관명을 옮긴다 — 26종 → 30종
사용자가 서식에서 빈칸으로 확인한 자리들이다(B·C 배치).

## 본문에서 확인한 것

- OCM_병동 / OCM_병실 (bzDataInterface.vb:6718, 6801)
  문맥의 CowInf 행에서 <b>코드값 그대로</b>다(CowWadCod·CowRomCod).
  마스터로 이름을 찾지 않는다 — 레거시가 코드를 그대로 찍는다.
  외래는 CowInf 가 0행이라 자연히 빈 값(레거시도 외래·비낮병동은 "").

- OCM_진료과 (:5437-5495)
  M_DepMst 를 CodDepCod + 접수일자 BETWEEN 으로 찾는다.
  유효기간 일자는 입원·외래 무관하게 <b>접수일자</b>다(:5445).
  이름 선택: NH 는 DepOfcNam, 그 밖은 개원일 2022-07-01 이후 + DepOfcNam 존재 시
  DepOfcNam, 아니면 DepKorNam. 개원일은 세션에 없어 빈 값 결정(기존과 동일)이라
  그 갈래는 DepKorNam 으로 간다.

- ETC_요양기관명칭_병원명 (:15792-15860)
  M_DepMst ⨝ M_HspMst(DepHspCod=HspCod) 에서 HspInsNam.Trim.
  진료과 코드로 병원을 찾는 구조다(다부지 구성).
  판정 일자가 진료과와 다르다 — ILV 는 퇴원일시·재원 중은 지금·외래는 접수일시.
  문맥의 적용일시가 정확히 그 판정이라 AdpDtm 을 그대로 쓴다.

원본 마스터 조회에 ORDER BY 가 없어 담당의 때와 같은 규칙(최신순 1행)으로 고정했다.

## 왕복은 환자당 두 번 추가

진료과·요양기관명을 환자를 붙일 때 한 번씩 읽는다(주민번호·주소·담당의와 같은 정책).
미리보기 창과 Ctrl+P 인쇄(PatientSession.ResolversFor) 둘 다 같은 재료를 받는다.

## 실DB 확인

--db-patient ㉓·㉔ 신규: 진료과 코드 '0506' → 44열·한글명 5자, 요양기관명 6자.
코드가 이름이 되는 것까지 확인해야 태그가 값을 낸다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 30종)
- --db-patient ①~㉔ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:54:18 +09:00
MsystechandClaude Opus 5 db969fc6b5 인쇄 경로가 해석기를 잃고 있었다 — 화면과 다른 종이가 나왔다
사용자가 미리보기 화면과 실제 인쇄 PDF 를 나란히 놓고서야 드러난 결함.
미리보기에는 환자 값(등록번호·성명·생년월일)이 찍히는데
같은 창의 '인쇄' 버튼으로 뽑은 종이는 태그 이름 그대로였다.

## 원인

BuildFixedDocument 가 BuildPageVisual(page) 을 인자 없이 불렀다 —
기본값이 tags=null, fields=null 이라 인쇄 경로에서만 해석기가 사라졌다.
화면에서 확인하고 인쇄하면 다른 종이가 나온다. 미리보기의 존재 이유를 무너뜨리는 결함이다.

## 고친 것

- Print/BuildFixedDocument 가 해석기 한 벌을 받아 끝까지 넘긴다.
- 미리보기 창의 인쇄는 그 창이 화면에 그리는 것과 <b>같은 해석기</b>를 넘긴다.
- 미리보기를 안 거친 인쇄(Ctrl+P)도 세션 환자를 따른다 —
  PatientSession.ResolversFor(document) 가 한 벌을 만들어 준다
  (주민번호·주소·담당의를 그 자리에서 한 번씩 읽는다).
- printFilter 는 인쇄에서 고정 true 다. 레거시 인쇄가 PrintOutPut=False 를 빼므로
  미리보기의 보기 토글과 무관하다.

## 게이트를 추가했다

이 판정이 없어서 지금까지 못 잡았다:

  인쇄(대조군): 해석기가 없으면 값이 없다
  인쇄: 해석기를 넘기면 환자 값이 인쇄 종이에 찍힌다
  인쇄: 미리보기 종이와 인쇄 종이의 글자가 <b>같다</b> (문자열 완전 일치)

대조군이 먼저다 — 없으면 "값이 어디서든 나온다"와 구분되지 않는다.

## 남긴 것

PDF MediaBox 높이가 856 DIP(642pt)가 아니라 735pt 로 나오는 문제는 별도다 —
용지·여백 처리 조사가 필요하고 이 수정과 무관하게 재현된다.

## 게이트

- dotnet test 338/338
- --edit-smoke 실패 0 (인쇄 판정 3건 신규)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:47:46 +09:00
MsystechandClaude Opus 5 41e6961b0e CTX 통합 계획 — 순증 48종, 고칠 것 6건. 그리고 내 문서가 하나 틀렸다
검증 통과 태그를 SheetMe 현행 구현과 대조했다(에이전트 6, 73만 토큰).

  new 48 / already 13 / conflict 6 / skip 1

통합하면 26종 → 74종이 되고, 그와 별개로 6건을 고쳐야 한다.
대조를 안 했으면 already 13종을 두 곳에서 처리하게 됐을 것이다.

## 지난 커밋의 내 주장이 틀렸다

WORKFLOW-CTX-ROUND1.md 에 ETC_로그인_사용자명 이 NVL(UidNam,' ') 때문에
레거시와 갈린다고 적었다. 틀렸다 — UserContextStore.cs:80 의 .Trim() 이
그 공백을 도로 걷어내서 NULL 케이스는 양쪽 다 "" 다.
검증이 코드를 직접 읽고 반박했다. 남는 실차이는 다른 것이다(SheetMe 만 Trim).

## 네 번째 차이를 찾았다

ETC_현재년도 — 레거시는 "2026년"(접미 "년"), SheetMe 는 "2026".
앞선 문서의 "차이 3건"에 없던 것이다.
고칠 때 TagPreviewCatalogTests.cs:108 이 "2026" 을 단정하므로
그 판정도 함께 바뀌어야 한다 — 검증이 짚어 줬다.

## conflict 6건

사용자ID_사용자명(결합 조건) · 사용자ID(기준일·Trim) · 사용자명(Trim) ·
사용자연락처(유효기간 없이 첫 행) · 원내연락처(같은 조회 공유) · 현재년도(접미사).

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:25:34 +09:00
MsystechandClaude Opus 5 dd96874c7c SQL 태그 231종을 조사한다 — 그대로 옮기면 안 된다는 것이 결론이다
오케스트레이션 에이전트 14개, 183만 토큰, 도구 413회. ok 210 / wrong 21.

## 결론

검증이 붙인 위험 신호 빈도가 이 조사의 답이다.

  ORDER BY 없이 Rows(0)          79
  결과가 비결정적                74
  예외 경로 문제                 76
  문자열 결합(바인드 아님)       47
  SQL 주입 구멍                  28
  조회 함수가 MessageBox 를 띄움 42

담당의에서 겪은 <b>정렬 없는 Rows(0)</b> 이 79곳에 더 있다.
그때 세어 보니 25,488건이 걸렸다 — 종당 같은 조사가 필요하다는 뜻이다.

## 검증이 잡은 레거시 결함(표본)

- ETC_로그인_전문의번호_Refer 는 컬럼명에 후행 공백이 있어 행이 있을 때 예외가 난다.
- ETC_로그인_직급 은 값은 UidNam 을 담고 조건은 UidCod 로 건다 —
  실무상 0건이라 늘 빈칸이었을 것이고, 고치면 없던 값이 갑자기 채워진다.
- ETC_병리판독의사싸인 은 SELECT UidNam 한 컬럼만 뽑고 Item("UidImgPth") 를 읽는다.

## 검증이 추출을 반박한 예

ETC_마취및소독간호사List 에서 추출은 "0건이면 NullReferenceException"이라 했다.
검증이 clsOracleDA.vb:789 를 직접 읽고 <b>빈 DataTable 이 반환된다</b>고 반박했다 —
실제 동작은 예외가 아니라 구분선 한 줄만 든 목록이다. 이식 시 분기가 완전히 달라진다.

혼자 읽었으면 그대로 믿었을 것이다. 반박을 임무로 준 것이 잡았다.

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:00:02 +09:00
MsystechandClaude Opus 5 fd295744dd CTX 126종 검증을 끝낸다 — 반박 6건, 그리고 기존 코드의 차이 3건
배치 2 재실행 완료. 에이전트 12개 전부 성공, 오류 0.
ok 118 / wrong 6 / unsure 2, 고유 태그 105종.

## 가장 값어치 있는 것은 "옮길 것"이 아니었다

검증이 <b>이미 들어가 있는 SheetMe 코드</b>의 차이를 셋 잡았다.

1. ETC_로그인_사용자ID_사용자명 — 레거시는 항상 UidCod + "." + UidNam 을 만드는데
   TagPreviewCatalog.cs:197 은 둘 다 비어 있지 않을 때만 만든다.
2. ETC_로그인_사용자명 — UserContextStore.cs:62 가 NVL(UidNam,' ') 로 공백을 채워
   UidNam 이 NULL 인 사용자에서 레거시("")와 값이 갈린다.
3. 로그인 사용자 유효기간 기준일 — 레거시는 GetAdpDtm 앞 8자, SheetMe 는 SYSDATE.

셋 다 태그를 새로 옮기다 나온 게 아니라 <b>기존 동작이 이미 달랐던</b> 것이다.

## 되살릴 때 걸리는 것

ETC_건강보험증번호_Refer 는 죽은 코드지만 주석 속 원래 구현이 ComInfDR_Refer
(참조 내원행)를 읽는다 — 다섯 문맥 표의 ComInf 로 그냥 치환하면 안 된다.

## 통합 전 대조가 먼저다

이번 배치는 대부분 ETC_ 계열이라 환자 태그가 아니고,
상당수를 SessionTagResolver/TagPreviewCatalog 가 이미 처리한다.
그대로 넣으면 같은 태그를 두 곳에서 처리하게 된다.

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 13:20:45 +09:00
MsystechandClaude Opus 5 949db1aa18 워크플로 1차 결과를 저장소로 옮긴다 — 검증이 10건을 반박했다
오케스트레이션으로 레거시 본문을 병렬로 읽고 각 매핑을 다른 에이전트가
<b>반박하도록</b> 시킨 결과. 에이전트 11개, 103만 토큰, 도구 299회.
내 컨텍스트는 거의 쓰지 않았다 — 오케스트레이션을 쓴 이유가 그것이다.

  ok 94 / wrong 10 / unsure 1, 고유 태그 84종

검증을 "통과시키는" 임무로 줬다면 wrong 10건이 그대로 코드에 들어갔을 것이다.
이 세션에서 당한 사고가 전부 "확인 없이 넣은 것"이었다.

## 이번 배치는 환자 태그가 아니다

대부분 ETC_ 계열(시각·로그인 사용자)이고, 상당수는 SessionTagResolver 가
이미 처리하고 있을 수 있다. 통합 전에 중복을 걸러야 한다 — 그대로 넣으면
같은 태그를 두 곳에서 처리하게 된다.

## 건진 것

- ETC_건강보험증번호_Refer 는 죽은 코드다(본문 전체가 주석, Return "" 하나).
- ETC_현재일시_MaskBox 는 마스크 때문에 일과 시가 붙는다(2026-08-1914:30).
- ETC_로그인_원내연락처 의 컬럼은 UIDOFFTEL 대문자다(형제는 파스칼).
- 검증이 기존 SheetMe 코드의 차이도 잡았다 — 레거시는 GetAdpDtm 앞 8자,
  SheetMe 는 SYSDATE 로 사용자 유효기간을 본다.

배치 6개 중 1개는 API 521 로 미실행이라 재실행이 필요하다.

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 13:00:37 +09:00
MsystechandClaude Opus 5 5ec31cee41 나이 개월·일 계산 두 종을 순수 함수로 옮긴다 — 레거시가 달력을 안 쓴다
CTX 126종 배치의 시작. 본문을 정확히 읽은 둘만 넣는다 —
나머지 세 변형(_00Months_00Days · _개월_00일 · _00개월_일)은 형식이 다를 수 있어
읽기 전에는 넣지 않는다.

## 레거시가 이상해 보이지만 그대로 옮긴 것 둘

**PAT_나이_개월수** = `DateDiff(Month, PatBthDay, SystemDate) - 1`
VB 의 DateDiff(Month, ...) 는 <b>날짜를 보지 않고 월 경계만</b> 센다(연차×12 + 월차).
그래서 1일생과 말일생이 같은 값이 나오고, 같은 달이면 -1 이 찍힌다.
일수로 환산하면 레거시와 다른 수가 된다 — 판정으로 못 박았다.

**PAT_나이_00개월_00일** = `days \ 30 & "개월 " & days Mod 30 & "일"`
달력이 아니라 <b>30일로 나눈다</b>. 365일이 "12개월 5일"이 된다.
달력으로 고치면 값이 달라진다.

## 내 산수가 틀렸다

기대값을 처음에 11로 적었다. 2025-01 → 2025-12 는 월 경계 11 이고
거기서 1을 빼면 10 이다. 구현이 아니라 판정이 틀렸고, 테스트가 그것을 잡았다.

## 아직 배선하지 않았다

두 함수는 "오늘"이 필요한데 해석기에 그 값을 넣는 배선(서버 시각 1회 읽기)이 남았다.
컨텍스트가 부족해 배선까지 하면 게이트를 못 돌리므로, 검증된 순수 함수만 먼저 넣는다.
다음 세션 첫 작업이 배선이다.

## 게이트

- dotnet test 338/338 (나이 판정 2건 신규)
- --edit-smoke 실패 0
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:03:18 +09:00
MsystechandClaude Opus 5 99b8aaa9c2 남은 360종 색인을 만들고, 앞선 분류를 정정한다 — 옮길 수 없는 태그는 없다
## 정정

전에 "MessageBox 를 띄우는 48종은 대화형이라 미리보기에서 원리상 실행할 수 없다"고
문서와 답변에 적었다. <b>틀렸다.</b> MessageBox 는 전부 Catch ex As Exception 안에 있다 —
오류 경로일 뿐이고, 우리는 예외 시 대화상자 대신 사유를 돌려주면 된다.

앞선 분류가 그렇게 나온 이유는 내 awk 가 MessageBox 를 SELECT 보다 먼저 봤기 때문이다.
분류 순서 하나로 "못 옮긴다"는 결론이 나왔고, 그것을 사용자에게 두 번 말했다.

즉 <b>옮길 수 없는 태그는 없다.</b> "완벽한 레거시 호환"은 달성 가능하고 남은 것은 물량이다.

## 실제 분포 (남은 360종)

  CTX 126  문맥·계산만 — 조회 없이 옮길 수 있다
  SQL 231  본문에 자기 SQL — 읽고 바인드로 옮기고 실DB 로 확인
  HSP   3  병원 코드로 갈린다

태그별 줄 번호까지 색인에 넣었다 — 다음 작업이 레거시를 다시 파헤치지 않고
해당 줄로 바로 가서 구현만 하면 된다. CTX 126종이 다음 목표다.

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:59:57 +09:00
MsystechandClaude Opus 5 5bf2cf034d OCM_담당의사 3종을 옮긴다 — 처음으로 레거시와 의도적으로 다르게 간다
23종 → 26종. 조회 둘(P_CodInf 선택 + M_UidMst)이 새로 붙었다.

## 원칙을 처음 깬다

지금까지는 "레거시가 이상해 보여도 그대로 옮긴다"였다
(주민번호의 DateValidCheck 가 그랬다 — 조건이 항상 참이지만 그대로 뒀다).

여기서는 그럴 수 없다. GetCodInfDT 는 ORDER BY 없이 CodMtiSeq='0' 만 걸고
호출부가 Rows(0) 을 집는데, 이 DB 에서 그런 내원이 <b>25,488건</b>이고 최대 41행이다.
41행 중 첫 행은 사실상 무작위다 — <b>재현할 결정적 동작이 존재하지 않는다.</b>

그래서 적용일시로 거르고 최신순 1행을 집는다(문맥의 CodInf 와 같은 규칙).
레거시도 이 시각을 넘기기는 한다 — 마스터 유효기간 조인에만 쓸 뿐이고
P_CodInf 의 CodStrDtm/CodEndDtm 은 WHERE 에 없다.
"그 시점의 진료과"를 뽑으려던 의도가 반쯤만 구현된 것으로 보인다.
근거는 docs/TAG-PORTABLE-48.md 에 남겼다.

## 폴백을 빠뜨리지 않았다

GetUidMst(clsCommonLib.vb:10615)는 유효기간으로 먼저 찾고
<b>0행이면 코드만으로 다시 찾는다</b>. 그 폴백을 빼면 유효기간이 지난 의사가
통째로 빈칸이 된다. 두 번째 조회까지 옮겼다.

## 세 갈래 시각

  입원 · ComPrgStt='ILV' → 퇴원일시
  입원 · 그 밖           → 문맥의 적용일시
  외래                   → 접수일시
담당의 유효기간을 볼 날짜도 갈린다(입원은 진료과 행의 CodEndDtm, 외래는 접수일자).

## 또 같은 함정을 밟을 뻔했다

㉒ 를 처음 돌렸을 때 0열이 나왔다. 쿼리 문제로 보였지만 아니었다 —
시험 내원(1988년)에 P_CodInf 행이 아예 없었다. 앞서 "내원 0건인 환자로 판정하려 한" 것과
같은 함정이다. 행을 가진 내원을 찾아 걸도록 고쳤다: 내원 5000323 → 52열, 이름 3자.

## 게이트

- dotnet test 336/336
- --edit-smoke 실패 0 — 이름 검사가 26종 전부 실제 태그임을 확인
- --db-patient ①~㉒ 전건 통과 (㉒ 담당의 조회 신규)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:50:25 +09:00
MsystechandClaude Opus 5 942780ae97 C 를 먼저 세 봤다 — 25,488건이 걸린다. "레거시 그대로"는 선택지가 아니다
CodMtiSeq=0 행이 2개 이상인 내원: 25,488건, 최대 41행.
0건이면 없는 문제였겠지만 아니었다.

41행짜리 내원에서 ORDER BY 없는 Rows(0) 은 사실상 무작위다.
레거시는 지금도 그 25,488건의 담당의를 확정적으로 찍지 못하고 있다 —
재현할 대상 자체가 없으므로 "그대로 옮긴다"가 성립하지 않는다.

정황이 하나 더 있다. 레거시는 GetCodInfDT 에 적용일시를 넘기면서도
그것으로 P_CodInf 를 거르지 않는다(넘긴 날짜는 마스터 유효기간 조인에만 쓴다).
P_CodInf 에 CodStrDtm/CodEndDtm 이 있는데 WHERE 에 없다 —
"그 시점의 진료과"를 뽑으려던 의도가 반쯤만 구현된 것으로 보인다.

측정 코드는 진단에 남겼다. 세기 전에는 A 와 B 중 어느 쪽인지 알 수 없었다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:45:00 +09:00
MsystechandClaude Opus 5 3e9f50ee52 OCM_담당의사 배치의 SQL 을 확보하고, 옮기기 전에 정할 것 둘을 적는다
조회 두 개(GetCodInfDT With_Join · GetUidMst)와 세 갈래 호출 조건을
문서에 그대로 옮겼다 — 다음 세션이 다시 파헤치지 않아도 된다.

## 그 과정에서 걸린 것 둘

① 조회에 ORDER BY 가 없는데 호출부가 Rows(0) 을 집는다.
   한 내원에 진료과 이력이 여럿이면 <b>오라클이 어느 행을 먼저 주느냐에 따라
   담당의가 달라진다</b>. 레거시 동작 자체가 비결정적이라
   "그대로 옮긴다"가 성립하지 않는다 — 정할 문제다.

② 위 SQL 이 이미 담당의 이름(CodDtrCod_Nam)을 돌려주는데
   태그는 그것을 안 쓰고 다른 날짜로 GetUidMst 를 한 번 더 친다.
   날짜가 다르니 두 값이 갈릴 수 있다. 왕복을 줄이려고 첫 결과를 쓰면
   값이 달라진다 — 레거시를 따르려면 두 번 쳐야 한다.

그리고 이 조회는 PatientContextStore 가 읽는 CodInf 와 선택 규칙이 다르다
(그쪽은 BETWEEN + 최신순 1행). 문맥의 CodInf 를 재사용하면 안 된다.

코드 변경 없음(문서만). 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:39:50 +09:00
MsystechandClaude Opus 5 d6d594490c 태그 이식 순서를 문서에 못 박는다 — "모두"는 배치로만 갈 수 있다
남은 363종의 성격이 서로 달라 한 번에 갈 수 없다.
232종은 각각 자기 SQL 을 든 별도 이식이고,
48종은 MessageBox 를 띄워 미리보기에서 원리상 실행할 수 없다.

다음 배치를 순서대로 적어 뒀다. OCM_담당의사 계열이 먼저인 이유는
그것이 요구하는 조회 둘(GetCodInfDT·GetUidMst)을 만들고 나면
뒤따르는 여러 태그가 같은 것을 재사용하기 때문이다.

배치마다 지킬 것도 함께 적었다 — 근거 줄번호, 이름 검사,
짐작 금지, 값보다 사유.

문서만 변경. 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:34:34 +09:00
MsystechandClaude Opus 5 d63f6f6bc0 주소 4종을 옮긴다 — 결합 규칙이 셋 다 다르다
19종 → 23종. M_ZipMst 를 건물관리번호(PatZipBdg)로 찾는 조회 하나가 추가됐다.

## 결합식을 그대로 옮긴다

레거시(clsCommonLib.vb:16600)는 DECODE 로 빈 조각을 건너뛰며 구분자를 붙인다.
시도·시군구·읍면·도로명·지하여부·건물번호 본번/부번·건물명이 각각 다른 규칙으로 이어진다.
손으로 다시 짜면 주소가 미묘하게 달라지므로 SQL 을 그대로 가져왔다(바인드는 유지).

## 눈으로 보면 오타 같은 것 셋

  PAT_도로명주소      도로명주소 + " " + 상세      (공백 있음)
  PAT_지번주소        지번주소 + 상세              (공백 <b>없음</b>)
  PAT_영문도로명주소  상세 + " " + 영문주소        (순서가 <b>뒤바뀐다</b>)

레거시가 그렇게 찍는다. 맞춰야 같은 종이가 된다.

그리고 PAT_우편번호 가 돌려주는 것은 <b>우편번호 컬럼이 아니라 구역번호</b>다.
조회는 둘 다 가져오지만 레거시가 쓰는 것은 구역번호다.

주소를 못 찾으면 빈 값이 아니라 <b>상세주소만</b> 돌려준다 — 레거시와 같다.

## 왕복은 환자당 한 번

주소 태그가 넷이라 태그마다 읽으면 왕복이 넷이 된다.
주민번호와 같이 환자를 붙이는 시점에 한 번 읽어 해석기에 넘긴다.
읽기에 실패해도 던지지 않는다 — 상세주소만으로 값을 만들 수 있고 레거시도 그렇게 한다.

## 게이트

- dotnet test 336/336
- --edit-smoke 실패 0 — 이름 검사가 23종 전부 실제 태그임을 확인
- --db-patient ①~㉑ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:33:00 +09:00
MsystechandClaude Opus 5 510663c5fc 태그를 11종 → 19종으로. "전부"의 규모를 먼저 재 봤다
"나머지도 전부 옮기자" — 추측하지 않고 386종을 본문 모양으로 분류했다.

## 실제 분포 (bzDataInterface.vb 386종)

  자기 SQL 을 든 것        232  각각 별도 이식 — 본문 SQL 을 읽고 바인드로 옮기고 실DB 로 확인
  MessageBox 를 띄우는 것    48  대화형이라 미리보기에서 <b>돌 수 없다</b>
  문맥만 읽는 것             48  옮길 수 있는 것
  기타(다른 라이브러리 호출) 55  하나씩 봐야 한다
  HspCod 로 갈리는 것         3  병원별 분기

즉 "전부"는 태그 늘리기가 아니라 <b>232건의 데이터 접근 이식</b>이다.
48종 목록은 docs/TAG-PORTABLE-48.md 에 본문째로 뽑아 뒀다 — 다음 작업이 기계적이 된다.

## 이번에 옮긴 8종

순수 형식 가공만 하는 것들. Computed 표(태그 → 문맥 함수)를 새로 뒀다 —
컬럼 하나로 안 되는 것(두 컬럼을 보거나 잘라 붙이는 것)을 담는다.

  PAT_생년월일_영문 · PAT_생년월일_영문_일월년도   자리만 바꿔 붙인다
  PAT_연락처                                      휴대전화가 있으면 그것, 없으면 자택
  PAT_연락처_뒷4자리 · PAT_휴대전화번호_뒷4자리    -, . 을 걷고 뒤 4자리
  OCM_외래내원일자 · OCM_입원전환일자             ComAcpDtm·ComTrsDtm 앞 8자리
  OCM_입원전환일시                                12자리를 일시 형태로

자리수가 모자라면 <b>빈 값</b>이다. 잘라서 그럴듯하게 만들지 않는다.

## 이번에 뺀 것과 이유

  PAT_우편번호 · PAT_도로명주소 · PAT_지번주소 · PAT_영문도로명주소
    → GetZipInfoByBuildingNumber_Simple 조회가 먼저 필요하다(왕복 추가)
  OCM_담당의사 계열
    → GetCodInfDT + GetUidMst 두 조회를 거치고 입원/외래로 갈린다
  OCM_외래내원일시
    → 별도 헬퍼(외래내원일시)의 형식을 아직 안 봤다

## 게이트

- dotnet test 336/336
- --edit-smoke 실패 0 — 이름 검사가 19종 전부 실제 태그임을 확인
  (지어낸 이름 셋이 조용히 앉아 있던 적이 있어서 이 검사가 있다)
- --db-patient ①~㉑ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:27:33 +09:00
MsystechandClaude Opus 5 222d1987d5 환자 검색·내원 조회를 재 보고 고친다 — 5초 두 곳이 각각 441ms·1ms 가 됐다
"검색 및 선택하면 시간이 너무 오래걸려" — 체감이 아니라 숫자였다.
진단에 시간을 재는 줄을 먼저 넣었다. 어디가 느린지 숫자 없이 고치면 엉뚱한 곳을 만진다.

## 실측 (--db-patient)

  성명 검색     5,025ms → 441ms
  ListVisits    5,000ms 내외 → 1~45ms
  문맥 읽기     557ms (그대로 — 병목이 아니었다)

## 검색 — 보이지도 않는 값에 5초를 쓰고 있었다

최종내원일시와 재원여부를 파생표 두 개로 붙였는데 둘 다 P_ComInf 를 <b>통째로 GROUP BY</b>
한 뒤에야 조인된다. 조건에 맞는 환자가 몇 명이든 내원 테이블 전체를 집계했다.

그리고 최종내원일시는 <b>목록 열에 없다</b> — 차트번호·성명·주민번호·휴대전화·비고뿐이다.
아무도 보지 않는 값을 위해 5초를 쓰고 있었다. 뺐다.

재원여부는 비고 배지에 쓰므로 남기되, 행 상한을 먼저 걸고 <b>돌아온 환자에 대해서만</b>
따로 읽는다(FillInpatient). 왕복이 하나 늘지만 집계 범위가 전체 → 최대 200명으로 줄어든다.
차트번호는 바인드로 넘긴다 — 이어 붙이면 레거시의 주입 구멍을 되살린다.

## 내원 조회 — 원인을 한 번 잘못 짚었다

마스터 셋(M_DepMst·M_UidMst·M_InsMst)을 ROW_NUMBER 파생표로 접은 것이 문제라고 보고
상관 스칼라(MAX ... KEEP DENSE_RANK FIRST)로 바꿨다. 결과는 같고 정렬이 사라지는데
<b>시간은 그대로 5초였다.</b>

진짜 원인은 WHERE 였다 — <c>TRIM(A.ComChtNum) = :c</c> 는 컬럼에 함수를 씌워
인덱스를 못 타고 P_ComInf 전건을 스캔한다. 내원이 한 건인 환자도 5초가 걸린 이유다.

LIKE 접두로 인덱스 범위 스캔을 살리고, 그것만으로는 '123' 이 '1234' 까지 잡으므로
TRIM 등호를 함께 둬서 넘친 행을 걸러 낸다. 5,000ms → 1ms.

측정을 안 했으면 마스터 조인만 고치고 "고쳤다"고 보고했을 것이다.
그 변경도 유지한다(정렬이 사라지는 것은 이득이다) — 다만 그것이 원인은 아니었다.

## 게이트

- --db-patient ①~㉑ 전건 통과(판정 23건 유지 — 결과가 달라지지 않았다)
- dotnet test 336/336 · --edit-smoke 실패 0
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:23:49 +09:00
MsystechandClaude Opus 5 4bdd583adb 주민번호 복호화를 붙인다 — 이 DB 는 실제로 암호화되어 있었다
문서(docs/RESNUM-DECRYPT.md)의 1→2→3 을 실행. 태그 7종 → 11종.

## 이 DB 가 암호화 병원이다

진단이 읽은 설정: 전체=Y 주민번호=Y.
앞 커밋에서 PAT_주민번호 를 뺀 판단이 맞았다 —
PatResNum 을 그대로 썼으면 <b>암호문이 종이에 찍혔다</b>.

## 앱은 키를 갖지 않는다

복호화는 오라클 패키지가 한다.

  SELECT SUBSTR(:e,1,7) || CRYPTO_AES256.DEC_AES(SUBSTR(:e,8,256)) FROM DUAL

바인드 변수이고 접근 통제는 DB 권한이다. SCHILDREN 갈래(UDF_GETDECRESNUM_damo)도 함께 옮겼다.
앞 7자리가 평문이라 SUBSTR(...,1,7) 을 이어 붙이는 것을 빠뜨리면 앞자리가 사라진다.

## 설정을 못 읽으면 값을 만들지 않는다

"암호화 아님"으로 가정하면 PatResNum 을 평문처럼 써서 암호문을 찍는다.
PatientIdentity 는 예외 시 빈 문자열을 돌려주고 사유만 로그에 남긴다 — 값은 남기지 않는다.
주민번호를 만드는 자리를 한 곳으로 좁혀 뒀다(만드는 경로가 여럿이면 어디서 새는지 추적할 수 없다).

## 순수 계산은 표로 고정했다 (테스트 13건)

보정 ①(기관기호, SRCH·JINJU): 앞 6자리를 PatBthDay 로 갈아 끼운다.

  원본은 If Not DateValidCheck(Substring(1,6)) 으로 감싸 있지만 <b>그 조건은 항상 참이다</b> —
  DateValidCheck 는 길이가 8·12·14·17 이 아니면 무조건 False 인데 6자리를 넘긴다.
  여기서 "고쳐서" 진짜 날짜 검사를 넣으면 레거시와 다른 번호가 나온다(이 DB 가 SRCH 다).
  이식은 동작을 옮기는 일이지 바로잡는 일이 아니다 — 그 사실을 테스트로 못 박았다.

보정 ②(외국인): 7번째 자리 0/9 를 PatBthDay 세기로 6/8·5/7 로. 국적코드가 있어야 걸린다.
성별: 7번째 자리 mod 2. PAT_성별 은 CoiCalCod 가 "24" 로 시작하면 PatSexTyp 을 먼저 쓴다.

## 추측하면 걸린다

M_DtsMst 컬럼을 DtsTabCod·DtlCod 로 추측해 ORA-00904 를 맞았다.
실제는 DtsTblCod·DtsDtlCod·DtsCod 다(dtCommonLib.vb:1136).

## 확인한 것과 못 한 것을 갈라 적는다

진단은 HIS 사용자 문맥 없이 도므로 병원코드가 비고, SRCH 기관기호 보정 갈래는
이 경로로 확인되지 않는다. 통과로 세지 않고 [미확인] 줄로 적는다 —
그 갈래는 순수 함수 테스트가 덮는다.

진단 리포트에 <b>값을 쓰지 않는다</b> — 길이와 "전부 숫자인가"만 적는다. 리포트는 파일로 남는다.

## 아직 안 한 것

나이(AgeCheck)는 병원별 규칙과 의료급여 전산관리번호 규약이 붙어 별도 작업이다.
개인정보 접근 로그(C_PdrInf)에 쓸지는 운영 DB 쓰기라 승인 전까지 하지 않는다.

## 게이트

- dotnet test 336/336 (주민번호 판정 13건 신규)
- --db-patient ①~㉑ 전건 통과 (⑲ 설정 · ⑳ 13자리 · ㉑ 성별)
- --edit-smoke 실패 0 (이름 검사가 파생 태그 4종까지 본다)
- --dialog-shots FAIL 0
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:15:24 +09:00
MsystechandClaude Opus 5 34c53e3d69 주민번호 복호화 경로를 찾아 문서로 남긴다 — 앱에 키를 넣지 않아도 된다
환자 태그의 다음 관문. 성별(277건)·나이(204건)가 전부 복호화된 주민번호를 선행 조건으로 삼는다.

## 가장 중요한 발견

복호화가 <b>DB 안에서</b> 일어난다(dtCommonLib.vb:6960).

  SELECT SUBSTR(:enc,1,7) || CRYPTO_AES256.DEC_AES(SUBSTR(:enc,8,256)) FROM DUAL

바인드 변수이고 키는 오라클 패키지 안에 있다 — 접근 통제가 DB 권한이라는 뜻이고
그것이 있어야 할 자리다. <b>앱은 키를 갖지 않는다.</b>
이 성질을 깨는 이식(키를 상수로 옮기는 등)은 하지 않는다.
앞 7자리는 평문이고 8자리부터만 암호화되어 있다.

병원 SCHILDREN 만 UDF_GETDECRESNUM_damo 로 갈린다(2023-10-14 DAMO 전환).

## 어떤 병원에서 켜지는가

M_DtlMst(HSPCFG/PersonalDataEnc) 가 Y 일 때만
M_DtsMst(HSPCFG/PersonalDataEnc/ResNum) 를 한 번 더 본다(clsCommonLib.vb:5835-5841).
N 이면 P_PatInf.PatResNum 이 평문이고, Y 면 그 컬럼은 쓰지 말고 PatResEnc 를 복호화해야 한다.
앞 커밋에서 PAT_주민번호 를 뺀 이유가 정확히 이것이다.

## 복호화 뒤 보정이 더 있다

SRCH·JINJU 는 주민번호에 기관기호가 들어간 경우를 PatBthDay 로 고친다.
<b>이 개발 DB 가 SRCH 다 — 살아 있는 갈래다.</b>
외국인은 7번째 자리 0/9 를 PatBthDay 세기에 따라 6/8 또는 5/7 로 바꾼다.
둘 다 순수 문자열 계산이라 표로 고정할 수 있다.

## 성별은 지금 옮길 수 있다

7번째 자리 mod 2(0=F, 1=M). 암호화 여부로 갈라지지만 두 갈래 본문이 같다.
단 PAT_성별 에는 CoiCalCod 가 "24" 로 시작하면 PatSexTyp 을 쓰는 선행 분기가 있고,
CoiCalCod 는 P_CoiInf 라 이미 문맥에 들어와 있다.

## 나이는 지금 옮기지 않는다

AgeCheck(clsCommonLib.vb:735)는 7번째 자리로 세기를 정하는데 갈래가 많고
REDCROSS 별도 규칙과 의료급여 전산관리번호 규약이 붙는다. 보험코드까지 결과를 바꾼다.
원본 주석이 <b>1901~1919년생 외국인과 2001년 이후 보호기관 영아는 원리상 구분 불가</b>라고
명시한다(20080821).

절반만 옮기면 종이에 그럴듯한 나이가 찍힌다 — 아무도 의심하지 않으므로 빈칸보다 나쁘다.
사례 표를 먼저 만드는 별도 작업으로 둔다.

## 개인정보 접근 로그

레거시에 C_PdrInf 전용 경로가 있다(clsCommonLib.vb:28279).
주민번호를 복호화하기 시작하면 가장 민감한 필드를 다루는 것이므로 기록 범위를 정해야 한다.
그 표에 쓰는 것은 운영 DB 쓰기라 사용자 승인이 필요하다 —
정하기 전에는 파일 로그에 사실만 남기는 쪽이 안전하다.

코드 변경 없음(문서만). 게이트는 앞 커밋 상태 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:03:49 +09:00
MsystechandClaude Opus 5 128aedc34f 환자 태그 매핑을 레거시 함수 본문으로 다시 세운다 — 셋은 존재하지 않는 이름이었다
"환자 태그이지만 아직 옮기지 않았습니다 엄청 많은데?" — 맞다. 그리고
옮겼다고 적어 둔 것도 절반이 거짓이었다.

## 지어낸 이름 셋

PAT_휴대전화 · PAT_전화번호 · PAT_주소 는 <b>실존하지 않는 태그</b>다.
컬럼 이름만 보고 태그 이름을 추측해 표에 넣었고, 이름이 안 맞으면 매칭이 안 되므로
값도 안 나오고 오류도 안 난다 — 화면에는 "아직 안 옮겼습니다"로 보인다.
실제 이름은 PAT_휴대전화번호 · PAT_자택전화번호 이고 PAT_주소 는 없다.

그래서 진단에 이름 검사를 넣었다 — 옮긴 이름이 전부
LegacyTagCatalog.DataInterfaceTags 에 있는지 본다. 대조군(없는 이름이 실제로 걸리는지)도 함께 둔다.

## PAT_주민번호 는 빼야 했다

레거시는 PatResNum_DEC(복호화 컬럼)를 읽는다(bzDataInterface.vb:87).
P_PatInf 의 PatResNum 을 그대로 쓰면 <b>암호화 병원에서 암호문이 종이에 찍힌다</b>.
빈칸보다 나쁜 값이라 표에서 뺐다.

## 실제로 맞게 돌던 것은 2종이었다

PAT_이름 · PAT_차트번호. 앞서 "6종"이라고 보고한 것은 틀렸다.

## 이번에는 함수 본문을 읽고 넣었다

기계적으로 옮길 수 있는 것은 <b>본문이 컬럼 하나를 그대로 돌려주는</b> 5종뿐이다
(추출해서 셌다). 거기에 형식이 하나 붙는 생년월일을 더해 7종:

  PAT_이름 · PAT_영문이름 · PAT_휴대전화번호 · PAT_자택전화번호 · PAT_여권번호
  PAT_차트번호(내원의 ComChtNum — moPatInfoBiz.ChtNum 이 그것이다, :70)
  PAT_생년월일(PatBthDay → YYYY-MM-DD, :188-198)

차트번호를 PatInf 에서 ComInf 로 고쳤고, 진단의 가상 문맥도 같이 고쳤다 —
가상 문맥이 옛 매핑을 쓰고 있으면 실제와 다른 것을 검사한다.

## 왜 나머지가 많이 남는가 (노력 부족이 아니다)

- PAT_성별/한글성별 : CoiCalCod 가 "24" 로 시작하면 PatSexTyp, 아니면
  GetSexAge(PatResNum_DEC) 로 주민번호에서 뽑는다(:283-316) — 복호화가 선행 조건이다.
- PAT_나이 계열 : 병원 코드로 갈라지고 CGCH 는 자기 SQL 을 들고 있다(:373-).
- 그 밖 238종 : 함수 본문에 자기 SQL 이 있다.

즉 다음 관문은 태그를 더 옮기는 것이 아니라 <b>주민번호 복호화</b>다.
그것이 풀리면 성별 계열이 따라 열린다. 짐작으로 채우면 종이에 그럴듯한 값이 찍히고,
그건 아무도 의심하지 않는다.

## 게이트

- dotnet test 323/323
- --edit-smoke 실패 0 (이름 검사 + 대조군 2건 신규)
- --db-patient ①~⑱ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:56:42 +09:00
MsystechandClaude Opus 5 5ecc7b514a 환자 선택을 제목줄로 올린다 — 한 번 고르면 앱 전체가 그 환자를 본다
테마 버튼 오른쪽에 user 아이콘. 전에는 미리보기 창 안에만 있어서
쿼리 편집처럼 환자 값이 필요한 다른 화면에서는 고를 방법이 없었다.

## 방향을 뒤집었다

전에는 미리보기 창이 환자를 들고 있고 세션에 <b>밀어 올렸다</b>.
그러면 제목줄에서 바꿀 때 창이 모르고, 세션과 창이 어긋나는 경로가 생긴다.

이제 모든 진입점이 PatientSession 에만 쓰고, 창들은 Changed 를 <b>듣는다</b>.
제목줄·미리보기·쿼리 편집 어디서 바꿔도 나머지가 따라온다.
미리보기 창은 열 때 세션의 환자를 물려받는다 —
창을 열 때마다 초기화하면 "앱 전체 적용"이 거짓이 된다.

## 아이콘이라 상태를 색으로 알린다

늘 보이는 자리라 글자를 둘 폭이 없다. 고르면 강조색, 이름은 툴팁에 둔다 —
제목줄에 환자 이름을 상시 노출하지 않는다.

이미 고른 상태에서 누르면 <b>메뉴</b>가 뜬다(바꾸기 / 해제).
"누르면 해제"로 두면 늘 보이는 아이콘이라 실수로 지운다.

## 말뿐인지 값인지 확인했다

제목줄 아이콘은 세션만 건드린다. 열려 있는 창이 그것을 듣지 않으면
아이콘은 켜지는데 종이는 그대로다 — "설정했는데 안 먹는다"가 된다.
그래서 판정은 <b>창의 메서드를 부르지 않고</b> 세션에만 써서 종이가 따라오는지 본다.

구독을 떼는지도 본다. 정적 이벤트라 안 떼면 창이 회수되지 않고
죽은 창이 계속 그리려 든다 — 닫은 뒤 세션을 흔들어 예외 없이 지나가는지 확인한다.

## 게이트

- dotnet test 323/323
- --edit-smoke 실패 0 (전체 적용 판정 3건 신규)
- --dialog-shots FAIL 0 · --query-popup 실패 0
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:47:24 +09:00
MsystechandClaude Opus 5 1baf805f02 흐림의 진짜 원인은 TextFormattingMode 였다 — 색 번짐만 재서 두 번 놓쳤다
전역 TextBlock 스타일이 TextOptions.TextFormattingMode="Ideal" 을 박고 있었다
(DesignerTheme.xaml:40). Ideal 은 WPF 기본값이고 글리프를 픽셀 격자에 맞추지 않아
11~14px UI 글자가 눌린 듯 흐려 보인다.

## 왜 두 번이나 못 잡았나

<b>ClearType 과 다른 축이다.</b> Ideal 이든 Display 든 색 번짐은 똑같이 생긴다 —
그래서 색 번짐을 세는 --cleartype 으로는 원리상 구분이 안 된다.
표면을 늘려 가며(카드 제목 → 본문 → 목록) 전부 "켜짐"을 확인했고 게이트는 초록불이었는데
사용자 눈에는 계속 흐렸다. 측정하고 있던 것이 물어야 할 것이 아니었다.

이번에는 픽셀이 아니라 <b>속성 값</b>으로 잠근다 —
G·H·I 세 표면에서 GetTextFormattingMode() == Display 를 확인한다.

## 화면과 종이를 상속으로 가른다

- 창(ThemedWindow)에 Display. 상속 속성이라 TextBlock·TextBox·버튼·표 글자에 모두 닿는다.
- 종이는 Ideal. PageView 의 용지 Grid 와 PrintService 의 캔버스가 각각 명시하고
  그 안의 글자가 상속한다. 인쇄 지면 자릿수가 바뀌면 레거시와 다른 종이가 된다.

전역 TextBlock 스타일에서는 이 속성을 <b>빼야</b> 했다.
스타일 세터는 상속값을 이기므로, 거기에 값을 박으면 종이 안의 글자까지 그 값이 되어
용지 Grid 가 정한 Ideal 을 덮는다. 지금은 두 쪽 다 상속으로만 정한다.

## 종이가 끌려가지 않았는지 두 겹으로 확인

- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --edit-smoke 에 '종이 글자는 Ideal 을 유지한다' 를 추가.
  md5 는 서식 하나만 보므로 속성 값으로도 못 박았다.

## 게이트

- dotnet test 323/323
- --cleartype 실패 0 (색번짐 3표면 + Display 판정 3건 신규)
- --edit-smoke 실패 0 · --query-popup 실패 0 · --dialog-shots FAIL 0
- --db-render md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:41:43 +09:00