Commit Graph
174 Commits
Author SHA1 Message Date
MsystechandClaude Fable 5 4b400b94e0 캔버스에서 고른 것을 레이어 목록이 따라오게 한다
1,067개짜리 서식에서 종이 한가운데 컨트롤을 클릭하면 목록에 아무 변화가 없는 경우가 흔했다.
둘 중 하나였다 — 행이 뷰포트 밖이거나, 조상이 접혀 있어 <b>행 자체가 만들어지지 않았거나</b>.

접기는 레이어 목록만의 개념이라 캔버스에는 자식이 그대로 보인다. 그래서 사용자는
"분명히 골랐는데 레이어에는 선택이 없는" 상태를 보고 어느 쪽이 맞는지 판단할 단서를 잃는다.
접기·검색·그룹을 쓸수록 어긋나므로 <b>기존 기능이 오히려 신뢰를 깎고 있었다</b>.

반대 방향(목록 더블클릭 → 캔버스 스크롤·페이지 전환)은 이미 잘 되어 있어 비대칭이 더 눈에 띄었다.
이번 것은 새 기능이 아니라 그 역방향을 잇는 일이다.

## 어떻게

선택이 바뀌면 주 선택의 행을 찾아 스크롤한다. 행이 없으면 조상(페이지 마디·그룹 폴더·컨테이너)을
편다 — 그 결과로 목록이 다시 만들어지고, 재구성 이벤트가 이 경로를 한 번 더 부를 때
행이 있으므로 스크롤만 하고 끝난다. 펴기는 <b>한 번만</b> 시도한다(이미 펴져 있으면 false 를
돌려주므로 재구성이 무한히 돌지 않는다 — 검사로 고정했다).

<b>필터는 건드리지 않는다.</b> 펼 것이 없는데도 행이 없다면 검색·타입 필터에 걸린 것이다.
그건 사용자가 건 것이라 임의로 풀면 그쪽이 더 놀랍다 — 아무것도 하지 않는다.

되먹임은 기존 syncing 가드가 막는다. 목록에서 고르는 경로는 그 가드에 걸려
스크롤이 돌지 않으므로, 클릭하던 자리가 움직이지 않는다.

## 판정

- 단위 시험 388 · edit-smoke 404건 전건 통과 (자동 스크롤 4건 추가)
- --dialog-shots 69장 · --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

검사는 VM 층(조상 펴기·행 생성·재진입 방지)까지다. 실제 스크롤 위치는 ListBox 가상화가
관여하므로 앱에서 긴 서식으로 한 번 확인하는 것이 좋다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:17:06 +09:00
MsystechandClaude Fable 5 6a0331ec8a 레이어 행을 항상 두 줄로 — 둘째 줄에 타입명을 둔다
목록에 1줄 행과 2줄 행이 섞여 있었다. 둘째 줄(이름)이 <b>표시명과 다를 때만</b> 나왔기 때문이다 —
문구가 없는 컨트롤은 표시명이 곧 이름이라 한 줄로 그려졌다.
훑을 때 눈이 매번 자리를 다시 잡아야 하고, 어느 행이 어디서 끝나는지 리듬이 없다.

## 둘째 줄에 무엇을 두는가

<b>타입명</b>이다. 아이콘만으로는 안 갈리는 짝이 실제로 있다 —
라벨과 마스크입력이 같은 글리프를 쓰고, 리스트박스와 체크리스트도 같다.
그래서 아이콘의 단순 중복이 아니다.

이름(Id)은 <b>더할 정보가 있을 때만</b> 붙인다. 세 갈래로 갈린다.
- 문구가 없다(표시명 = 이름) → 타입명만. "TextBox1 / 텍스트박스"
- 갓 놓았다(문구가 타입명 그대로) → 이름만. 윗줄이 이미 타입을 말했다. "라디오버튼 / RadioButton1"
- 사람이 문구를 썼다 → 둘 다. "환자명 / 라벨 · Label1"

같은 글자가 두 줄에 두 번 나오는 경우가 하나도 없도록 셋을 다 검사로 고정했다.
이름을 계속 보여 주는 이유는 그것이 <b>외부 참조 키</b>이기 때문이다 —
수식(RadioButton1+CheckBox3)·액션 대상 컨트롤·이벤트 매핑이 이름 문자열로 서로를 가리킨다.

## 값을 치르는 것

행 높이가 일정해진 대신 문구 없는 컨트롤이 많은 서식에서 목록이 길어진다.
사용자가 리듬 쪽을 택했고, 타입명이 아이콘 모호성까지 함께 없애므로 값은 한다.

## 판정

- 단위 시험 388 · edit-smoke 400건 전건 통과 (레이어 둘째 줄 5건 추가)
- --dialog-shots 69장 전건 통과 (레이어 4종을 눈으로 대조 — 행 높이 균일 확인)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:10:57 +09:00
MsystechandClaude Fable 5 8c76a978a9 캔버스·인스펙터 잔손질 넷 — 마퀴·치수 칩·죽은 버튼·잘린 설명
## 패널 안에 그린 마퀴가 아무것도 안 잡았다

마퀴가 최상위만 훑고 컨테이너 안으로 안 들어갔다. 패널 안 체크박스 5개를 한 번에 고르려고
안쪽을 끌면 패널만 잡히거나 아무것도 안 잡혀서, Ctrl+클릭 반복이나 레이어 목록으로 우회했다.
컨테이너 자식은 전체 컨트롤의 <b>18.3%</b>(실측 18,749개/컨테이너 4,099개)라 드문 경우가 아니다.

규칙은 <b>'담김'</b>이다 — 마퀴가 컨테이너 사각형 안에 들어가 있으면 그 안의 것을 고른다
(내 상자가 패널보다 작으니 그 안을 고르려는 뜻이다). 걸치기만 하면 예전처럼 패널이 잡힌다.
모드도 수정키도 없이 <b>그린 모양만으로</b> 갈리므로 예측할 수 있다.

한 군데를 더 고쳐야 했다. 클릭 선택이 쓰는 GroupMatesOf 는 자식을 부모 컨테이너로 되돌리는데
(패널 안의 것을 집으면 패널이 움직여야 하므로 옳다) 마퀴가 그것을 그대로 쓰고 있었다 —
안쪽을 정확히 골라 놓고도 결과가 다시 패널 하나가 되어 위 규칙이 아무 일도 못 한다.
마퀴 전용 경로를 따로 뒀다. 걸치기 동작이 그대로인 것도 검사로 고정했다.

## 끄는 동안 숫자가 캔버스에 없었다

라벨 폭을 종이 칸에 맞추는 동안 숫자는 300px 떨어진 인스펙터에만 떴다 —
끌고, 손 떼고, 오른쪽 보고, 다시 끈다. 간격 수치 칩은 <b>균등 스냅이 성립할 때만</b> 나타나므로
"지금 이게 몇 px 인가"는 드래그 중에 알 방법이 아예 없었다.

이동이면 좌표, 크기 조절이면 크기를 보여 준다 — 지금 <b>바꾸고 있는 값</b>이 그것이다.
오버레이 층이라 종이에는 없고, 줌이 걸린 층이라 역배율로 글자 크기를 화면 고정으로 만든다.

## 눌러도 아무 일이 없는 ⋮ 버튼

Command·Click·ContextMenu 가 <b>전부 없었다</b>. 그런데 툴팁은 '이 컨트롤에 대한 명령'이라
"단일 선택일 때만 되는 뭔가가 있다"고 오해시켰다. 없는 버튼보다 죽은 버튼이 나쁘다 —
컨트롤 단위 명령은 캔버스 우클릭 메뉴에 이미 다 있다.

## 주석이 논증한 값과 실제 값이 달랐다

라벨 폭 주석이 "92px + 줄바꿈이면 두 줄로 접혀 전부 읽힌다"고 근거까지 적어 놓고
값은 72 였다. 그래서 '액션 대상 컨…' 처럼 잘렸다. 92 로 맞췄다.

같은 자리에서 설명 행(ReadOnlyRow)이 값처럼 잘리고 있었다 —
"타입이 달라 값의 뜻이 다른 속성 2개 — 타입별로 따로 고르세요" 에서
<b>지시에 해당하는 뒷부분</b>이 사라져 무엇을 하라는 건지 알 수 없었다. 접어서 다 보여 준다.

## 판정

- 단위 시험 388 · edit-smoke 396건 전건 통과 (마퀴 2건 · 치수 칩 4건 추가)
- --dialog-shots 69장 전건 통과 (인스펙터 8종을 눈으로 대조 — 라벨·설명 잘림 없음)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:05:30 +09:00
MsystechandClaude Fable 5 f6b456753c 환자 선택 — 첫 걸음이 틀렸다는 것을 화면이 말하게 한다
## 가장 흔한 입력이 조용히 0건이었다

차트번호는 <b>완전일치</b>다. 병원 P_PatInf 는 고정 폭이라 실제 값이 0으로 채워져 있는데
사람은 "12345" 처럼 앞의 0 을 빼고 친다 — 그러면 0건이다.
저장소는 주석으로 "병원별 zero-pad 후 전달"을 요구하는데(PatientVisitStore.cs:8)
유일한 호출부가 그 계약을 안 지키고 있었다.

<b>자릿수를 설정으로 정하지 않았다.</b> 병원마다 다르고 틀리면 멀쩡한 검색을 망친다.
대신 0건일 때만 8·10자리로 한 번 더 찾는다(레거시가 쓰던 두 폭이다).
찾았으면 "자릿수를 채워 '0000012345' 로 찾았습니다"를 적고 그 번호를 칸에 되쓴다 —
다음부터 그 번호를 쓸 수 있게. 틀릴 여지가 없는 방식이다.

## 0건일 때 무엇으로 찾았는지 말하지 않았다

유형이 틀려서 0건인 경우가 가장 흔한데(습관대로 이름을 쳤는데 유형은 차트번호)
화면에 힌트가 하나도 없어 접속이나 권한을 의심하게 된다.
"없습니다(차트번호 완전일치로 찾았습니다)"로 유형과 일치 방식을 함께 적는다.

## '기준 시점'이 무슨 뜻인지 알 수 없었다

입원 내원은 <b>구간</b>이라 어느 시점의 진료과·병실·보험 자격을 쓸지 정해야 하는데,
목록에 접수일시만 있어서 그것이 구간이라는 사실 자체가 안 보였다.
이미 조회돼 있으면서 화면에 없던 <b>퇴원일시</b> 열을 붙이고,
선택지를 '마지막(퇴원 시점)'·'처음(입원 시점)'으로 바꿔 무엇을 고르는지 글자로 말한다.

열을 하나 더하면서 상태 열이 화면 밖으로 밀리고 콤보 글자가 잘렸다 —
사진으로 확인하고 일곱 열이 다 들어오도록 폭을 다시 잡았다.

## 판정

- 단위 시험 388 · edit-smoke 390건 전건 통과
- --dialog-shots 69장 전건 통과(다크·라이트 눈으로 확인 — 열·콤보 잘림 없음)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 남은 것

검색 유형 자동 판별(입력 모양으로 주민번호·휴대전화·성명·차트번호를 가려내는 것)은
안 넣었다 — 콤보를 사람 몰래 바꾸는 동작이라 "왜 유형이 저절로 바뀌지"를 새로 만든다.
0건 안내가 유형을 밝히게 된 것으로 같은 혼란의 대부분은 없어진다.
자릿수 보정 뒤 재조회는 DB 가 붙은 단말에서 실제 차트번호로 확인이 필요하다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 08:48:13 +09:00
MsystechandClaude Fable 5 d83478619e 쿼리 편집기 — Enter 가 새던 것과 지나간 검증 결과
## Enter 한 번에 변수를 못 넣은 채 저장되고 닫혔다

치환 변수 검색칸에 KeyDown 이 없었고 확인 버튼은 IsDefault 다.
그래서 '차트' 를 치고 Enter 를 누르면 검색도 삽입도 아니라
<b>SQL 이 저장되고 창이 닫힌다</b>. 변수를 하나도 못 넣은 채 편집이 끝나는데,
무엇이 일어났는지 알기도 어렵다 — 창이 사라졌으니 되짚을 화면이 없다.

Enter 는 지금 걸러진 목록의 선택 항목을 넣는다(검색으로 하나까지 좁혀진 경우가 대부분이다).
↓ 는 목록으로 이동한다 — 손을 마우스로 옮기지 않고 훑을 수 있어야 한다.
둘 다 e.Handled 로 기본 버튼에 닿지 않게 막는다.

## 검증한 뒤 SQL 을 고쳐도 초록 요약이 그대로 남았다

"검증했다"고 믿고 저장하는데 저장되는 것은 <b>검증하지 않은 문장</b>이다.
검증 시점의 원문을 기억해 두고, 지금 텍스트와 다르면 결과 칸을 흐리게 낮추고
요약 앞에 '(수정됨 — 다시 실행하세요)'를 붙인다.

<b>결과를 지우지는 않는다.</b> 방금 본 컬럼명이 사라지면 그것대로 불편하다 —
배선 필드 칸에 옮겨 적는 중일 수 있다. 유효하지 않다는 사실만 말한다.

## 판정

- 단위 시험 388 · edit-smoke 390건 전건 통과
- --query-popup(자동완성 팝업 진단) 전건 통과
- --dialog-shots 69장 · --modal-check · --maxrect 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 남은 것

조사에서 함께 나온 것 중 이번에 안 한 것들 — '돌린 SQL 보기'(치환을 마친 문장을 창 안에서 확인),
0행일 때도 컬럼 머리글 남기기, 치환 변수를 안 쓰는 쿼리에 늘 붙는 '구문만 검사' 꼬리표 제거,
인스펙터로 연 편집기에서 컨트롤 변수 칩이 사라지는 것.
전부 근거가 잡혀 있고 다음 회차에 이어서 할 수 있다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 08:44:53 +09:00
MsystechandClaude Fable 5 e162d0fb6b 태그가 값과 사유를 사실대로 말하게 한다
세 가지가 <b>사실과 다른 말</b>을 하고 있었다.

## ① 로그인 사용자 태그 다섯 갈래가 피커에서만 비어 보였다

피커가 TagPreviewCatalog.FromUser 의 <b>5-인자 위임판</b>을 부르고 있었다.
그 판은 면허·전문의·직종·부서·부서전화를 빈 문자열로 채운다.

    FromUser(tag, id, name, mobile, office)
      → FromUser(tag, id, name, mobile, office, "", "", "", "")

그래서 의사면허번호·전문의번호·직종·부서·부서전화 태그를 피커에서 고르면
"이 계정에는 값이 비어 있습니다"가 뜨는데, 정작 종이에는 실제 값이 찍힌다.
사용자는 그 태그를 못 쓰는 것으로 판단하고 다른 것을 찾게 된다.

이 값들은 로그인 세션(HisUser 15컬럼)이 이미 들고 있다 — 조회를 더하지 않고 본판으로 넘긴다.

## ② 액션 태그 149종에 데이터 태그용 사유가 나갔다

액션 태그는 <b>값이 아니라 동작</b>이다(더블클릭·버튼이 실행한다).
그런데 피커가 데이터 태그와 같은 값 칸을 그려서
"환자 정보가 있어야 값이 나옵니다" 같은 문구가 149종 전부에 붙었다.
오른쪽 설명 칸이 통째로 거짓말을 하고 있었다.

행 종류를 받아 액션이면 "값이 아니라 동작입니다 — 더블클릭·버튼에서 실행됩니다" 한 줄만 둔다.

## ③ 오타 난 태그가 아무 표시 없이 커밋됐다

인라인으로 직접 치는 것이 이 행의 <b>주 경로</b>인데 거기에 검증이 하나도 없었다.
'PAT_이룸' 처럼 한 글자만 틀려도 조용히 저장되고, 그 사실은 미리보기에서 <b>빈칸</b>으로만 드러난다 —
빈칸의 원인이 오타인지 이 환자에게 자료가 없어서인지 가릴 방법이 없다.

글꼴 행과 <b>같은 규칙</b>으로 ⚠ 를 붙인다. 막지는 않는다 —
사이트마다 카탈로그에 없는 커스텀 태그를 쓰기 때문이다. 값은 한 글자도 건드리지 않는다.

## 곁가지 — 사유가 접속에 묶여 있었다

DB 가드가 값 계산보다 <b>위</b>에 있어서, 접속이 없으면 '값을 만들 수 없습니다' 사유까지
통째로 감춰졌다. 그 사유는 정적 판정이라 접속과 무관하다 —
정작 가장 필요한 상황(접속 안 된 단말에서 서식을 훑을 때) 안 보였다.
가드를 값 계산 직전으로 내렸다.

## 판정

- 단위 시험 388 · edit-smoke 390건 전건 통과 (태그 6건 추가)
- --dialog-shots 69장 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 08:42:03 +09:00
MsystechandClaude Fable 5 73095c1415 레이어 목록이 계층과 범위를 말하게 한다
사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 했다. 원인은 색이 아니라 <b>산수</b>였다.

## 부모와 자식이 같은 x 에 그려지고 있었다

계층을 나타내는 수단은 들여쓰기 하나뿐인데(평탄한 ListBox 다), 그 폭이
셰브론 폭에 정확히 상쇄되고 있었다.

- 접기 셰브론은 폭 16, 자식이 없으면 Collapsed → <b>폭이 통째로 사라진다</b>
- 컨테이너 자식 들여쓰기는 정확히 16

그래서 패널의 아이콘과 그 <b>직속 자식</b>의 아이콘이 같은 x 에 놓였다.
목록만 보면 부모-자식이 아니라 형제로 읽힌다. 3단 중첩에서는 어느 행이 누구 밑인지
따라갈 수가 없다. 셰브론 자리를 항상 남기도록 고쳤다 — 이제 깊이 한 단계가 16px 사다리로 보인다.

## 접힌 컨테이너가 무엇을 품었는지 말하지 않았다

페이지 행에는 (7), 그룹 폴더에는 (3)이 이미 붙는데 컨테이너만 없었다.
접힌 패널이 3개를 품었는지 300개를 품었는지 펴 보기 전에는 알 수 없었고,
그 패널을 지우거나 옮기기 전에 무엇이 함께 가는지도 알 수 없었다.
중첩까지 세어 붙인다 — 그 수가 곧 "이걸 지우면 몇 개가 같이 지워지나"다.

## 고른 컨테이너의 범위가 안 보였다

캔버스에서 패널을 끌면 자식이 전부 따라 움직이는데 목록은 그 사실을 말하지 않았다.
그룹을 통째로 고르면 멤버가 네이비 밴드로 묶이는 기구가 <b>이미 있었는데</b>
컨테이너에는 그 갈래가 없었다. 조상 중에 선택된 컨테이너가 있으면 같은 밴드를 준다.
그룹 판정보다 뒤에 둬서 기존 그룹 색을 이기지 않는다.

## 판정

- 단위 시험 388 · edit-smoke 384건 전건 통과 (레이어 4건 추가)
- --dialog-shots 69장 전건 통과 (레이어 샷 4종을 눈으로 대조 — 사다리·개수·밴드 확인)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 아직 남은 것

행 높이가 1줄/2줄로 갈린다 — 표시명이 Id 와 다를 때만 둘째 줄(Id)이 뜨기 때문이다
(ControlViewModel.LayerId). 목록을 훑을 때 리듬이 깨지는데, 고치려면 '항상 두 줄'(공간 낭비)이나
'한 줄에 합치기'(정보 밀도 변화) 중 하나를 골라야 해서 이번 범위에 넣지 않았다.

캔버스에서 고른 것을 목록이 따라 스크롤·펼치는 것(조사에서 medium 으로 나온 항목)도 남았다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 18:43:10 +09:00
MsystechandClaude Fable 5 f8531cb7e1 데이터소스 배선이 실사용에서 전부 죽어 있었다
구조 사용성 조사를 돌렸더니 가장 무거운 것이 사용성이 아니라 <b>버그</b>였다.

## 무슨 일이었나

배선 실행기가 데이터소스를 <b>레거시 클래스명</b>으로 찾고 있었다.

    if (control.Type == "MDataTable")

그런데 서식을 XML 에서 읽는 순간 LegacyTypeCatalog:45 가 그 이름을 중립 이름
"DataTable" 로 바꿔 둔다. 즉 실사용 서식에서는 이 조건이 <b>한 번도 참이 되지 않는다</b> —
데이터소스를 한 개도 못 모은다.

결과는 조용하다. 배선을 옳게 걸어도 모든 DataTableField 가
"데이터소스 X 이 이 서식에 없습니다"로 실패해 미리보기와 <b>인쇄</b>에서 빈칸이 되고,
미리보기의 '데이터소스...' 버튼은 영영 회색이다(Names.Count 가 0이라).
사용자 입장에서는 "배선을 걸었는데 값이 안 나온다" 뿐이고 이유를 알 방법이 없다.

## 왜 여태 못 봤나

진단 네 곳이 전부 ControlElement 를 <b>손으로</b> 만들면서 같은 레거시 이름을 썼다
(EditSmoke:395,739 · DialogShots:465 · DbSmoke:2269). 러너의 오타와 진단의 오타가 짝이 맞아
"배선: 서식에서 데이터소스 이름을 모은다" 가 계속 초록이었다.
<b>로더를 우회해 만든 문서로 로더의 계약을 검사하고 있었다</b> — 검사가 실사용과 다른 세계를 보고 있었다.

## 고친 것

① 러너가 중립 타입명으로 비교한다. 문자열은 LegacyTypeCatalog.DataTableType 상수 하나로 모았다 —
   두 이름이 공존하는 한 각자 적으면 같은 일이 또 난다.
② 진단 네 곳이 같은 상수를 쓰게 했다.
③ 실사용과 <b>같은 경로</b>로 만드는 검사를 새로 넣었다 — 팔레트 경로(AddControlAt("DataTable"))로
   데이터소스를 만들고 러너가 그것을 찾는지, 배선이 '이 서식에 없습니다'로 떨어지지 않는지 본다.

## 고치기 전이었으면 잡혔는지 확인했다

러너 한 줄만 옛 비교로 되돌려 돌려 봤다 — 새 검사 2건과 <b>기존 검사 1건</b>이 실패했다.
기존 검사가 실패한 것은 ②로 진단이 더 이상 오타를 복제하지 않기 때문이다.
이 세 줄이 앞으로 같은 어긋남을 막는다.

## 판정

- 단위 시험 388 · edit-smoke 380건 전건 통과(배선 3건 추가)
- --dialog-shots 69장 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
  (그 게이트는 PageView 직접 렌더라 해석기를 타지 않는다 — 이 수정과 무관하다는 뜻이기도 하다)

DB 가 붙은 단말에서 실제 배선이 값을 내는지는 --db-patient 계열로 별도 확인이 필요하다.
이 커밋이 고친 것은 "데이터소스를 찾는 것"까지다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 18:37:57 +09:00
MsystechandClaude Fable 5 f4e239075d 창 내용 여백을 16 으로 맞추고, 거짓이 된 주석을 고친다
앞 커밋에서 남겨 둔 것 — 타이틀바는 통일했지만 내용 여백은 창마다 달랐다.
실측하니 12(여섯 창) · 14(데이터소스 배선) · 16(서식 등록)으로 갈려 있었다.
설명 문장이 창 가장자리에 바짝 붙는 창이 여럿이었고, 타이틀바가 생기면서 그 대비로 더 드러났다.

16 으로 맞춘다. 12 는 36px 타이틀바 아래에서 답답하고, 기준으로 삼은 MessageDialogView 는
본문에 20,18,20,14 를 쓴다 — 그 사이에서 큰 창(1180×760 쿼리 편집기)의 내용을
밀어내지 않는 값이 16 이다. 잘림 검사(dialog-shots 69장)가 전건 통과하는 것으로 확인했다.

## 거짓이 된 주석

태그 선택 창에 이렇게 적혀 있었다 —
"창 껍데기는 OS 크롬을 유지한다. … 이 앱의 대화상자 10종이 전부 OS 크롬이라
 이 창만 바꾸면 크롬 규약이 두 벌이 된다."

앞 커밋에서 열한 창을 전부 자체 크롬으로 옮겼으므로 그 전제가 무너졌다.
그대로 두면 다음 사람이 "이 창은 일부러 OS 크롬"이라고 읽고 되돌린다.
무엇이 왜 바뀌었는지까지 적어 둔다 — 주석이 틀렸다는 사실 자체가 정보다.

## 판정

- 단위 시험 388 · edit-smoke 377건 전건 통과
- --dialog-shots 69장 전건 통과(잘림 없음, 다크·라이트 눈으로 확인)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 18:00:27 +09:00
MsystechandClaude Fable 5 13edf3ecd9 제목줄 환자 아이콘이 혼자 까맸다
지적을 받고서야 실제로 봤다. 앞 커밋에서 "제목줄의 환자 아이콘 버튼은 손대지 않았다 —
옆 아이콘들과 같은 스타일·크기다" 라고 썼는데, <b>마크업만 읽고 화면을 안 봤다.</b>
스타일과 크기는 같았지만 색이 달랐다.

## 무슨 일이었나

이웃 아이콘(복사·삭제·테마·저장)은 XAML 컨버터를 타고, 그 기본 브러시 키가 <b>B.Muted</b> 다.
환자 아이콘만 코드에서 만드는데 안 고른 상태에 <c>null</c> 을 넘겼고,
LucideIcons.Paint 는 키도 색도 없으면 <c>Brushes.Black</c> 으로 칠했다 —
어두운 제목줄에서 <b>안 보이는 아이콘</b>이라는 뜻이다.

컴파일러가 CS8604 로 그 자리(MainView.xaml.cs:139)를 이 세션 내내 경고하고 있었다.
빌드 로그에서 매번 지나쳤다.

## 두 군데를 고친다

① 호출부 — 안 고른 상태의 키를 "B.Muted" 로. 이웃과 같은 토큰이라 테마 전환도 따라간다.
② 기본값 자체 — 색도 키도 없으면 이제 B.Muted 리소스 참조로 떨어진다.
   검정은 다크 테마에서 아이콘을 숨기는 기본값이라 함정으로 남겨 둘 이유가 없다.
   null 을 넘기던 호출부는 ①이 유일했으므로 다른 곳의 색은 바뀌지 않는다.

## 확인 방법도 고쳤다

이번에는 제목줄을 잘라 3배로 키워 다크·라이트 양쪽을 눈으로 대조했다.
전에는 대화상자 사진만 보고 제목줄은 마크업으로 판단했다 — 그래서 놓쳤다.
회귀 방지로 edit-smoke 에 "아이콘 기본색이 하드코딩 검정이 아니다" 를 넣는다.

## 판정

- 단위 시험 388 · edit-smoke 377건 전건 통과 (아이콘 색 1건 추가)
- CS8604 경고 소멸
- --dialog-shots 69장 · --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:56:36 +09:00
MsystechandClaude Fable 5 33461a6c67 환자 선택 창을 확인하다 버튼 두 가지를 고친다
창 자체는 앞 커밋의 공통 크롬을 그대로 받아 타이틀바가 붙었다. 확인하다 두 가지가 걸렸다.

## '찾기(_F)' 가 밑줄 대신 글자 그대로 나왔다

버튼 템플릿의 ContentPresenter 에 RecognizesAccessKey 가 없었다.
그래서 화면에 <b>"찾기(_F)"</b> 가 그대로 찍히고 Alt+F 도 먹지 않았다.
접근키를 쓰는 버튼이 앱 전체에서 이 하나뿐이라 20년째 아무도 안 밟은 자리다.

일반 버튼 템플릿 셋에만 켠다. 태그 제안 칩(SuggestChip)은 자기 템플릿이 따로 있어
걸리지 않는데, <b>거기서 켜면 안 된다</b> — 칩 내용이 데이터 바인딩이라
PAT_이름 같은 태그명이 "PAT이름"으로 잘려 보인다. 버튼 중 데이터를 싣는 것은 그것뿐이다.

## 비활성 강조 버튼의 글자가 배경에 묻혔다

'선택' 버튼은 환자를 고르기 전까지 비활성이다. 앞 커밋에서 이 버튼에 Primary 를 주면서
드러났는데, Primary 의 비활성 처리가 <b>불투명도만 0.45 로</b> 내리는 것이었다.
강조색이 옅어질 뿐 여전히 파랗고, 그 위의 흰 글자는 옅어진 파랑에 묻힌다 —
"누를 수 있어 보이는데 안 눌린다"와 "글자가 안 읽힌다"가 겹친다.

비활성은 색을 빼는 쪽으로 바꿨다(중립 채움 + 흐린 글자).
Primary 를 쓰는 모든 창이 같이 얻는다.

## 판정

- 단위 시험 388 · edit-smoke 376건 전건 통과
- --dialog-shots 69장 전건 통과 (다크·라이트 양쪽에서 눈으로 확인)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

제목줄의 환자 아이콘 버튼은 손대지 않았다 — 옆 아이콘들과 같은 스타일·크기(Subtle 34×28)이고
고른 상태를 아이콘 색으로 알리는 것도 그대로다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:50:51 +09:00
MsystechandClaude Fable 5 12e5ec6452 열리는 창들의 결을 통일한다 — 타이틀바와 강조 버튼
## 창 열둘 중 하나만 우리 옷을 입고 있었다

커스텀 크롬을 가진 것은 메인 셸과 MessageDialogView 둘뿐이었고,
나머지 열한 창(서식 이력·데이터소스 배선·글꼴 관리자·태그 선택·쿼리 편집기·마스크·
환자 선택·데이터소스 결과·레이어 필터·서식 등록·미리보기)은 <b>윈도우 기본 타이틀바</b>를 달고 떴다.
다크 테마에서 어두운 앱 위에 OS 바가 얹히고, 높이·글자·모서리·닫기 버튼 모양이 전부 달라
"다른 프로그램이 열렸다"로 보였다.

열한 창이 이미 전부 ThemedWindow 를 걸고 있었으므로, 그 옆에 템플릿을 가진
ThemedDialogWindow 를 만들고 각 창은 <b>스타일 이름 한 줄</b>만 바꿨다.
창마다 타이틀바 markup 을 복사했다면 다음에 또 갈라진다.

## WindowChrome 은 스타일로 줄 수 없었다

두 번 막혔고 둘 다 같은 뿌리다 — WindowChrome 은 Freezable 인데 창에 붙으면서
스스로 속성을 고친다. Setter.Value 에 두면 열한 창이 <b>한 인스턴스를 나눠 쓰다</b>
두 번째 창부터 "읽기 전용 상태라 속성을 설정할 수 없습니다"로 죽었다.
x:Shared="False" 로도 안 됐다 — 공유되는 것은 리소스가 아니라 Setter 가 이미 붙든 값이다.

그래서 ctl:DialogChrome 첨부 속성이 창마다 새 인스턴스를 만들어 붙인다.
템플릿 안 닫기 버튼은 Click 핸들러를 가질 수 없으므로(템플릿에 코드비하인드가 없다)
표준 SystemCommands.CloseWindowCommand 를 쓰고 그 CommandBinding 도 여기서 단다.
DialogResult 를 건드리지 않으므로 ShowDialog 는 false 를 돌려주고 호출부의 '취소' 경로가 그대로 탄다.

최대화 보정도 같이 건다. WindowStyle=None 인 창은 최대화하는 순간 작업표시줄을 덮는데
(메인 창에서 겪은 결함이다) 대화상자 열 개가 크기 조절 가능이라 타이틀바 더블클릭으로
그 상태에 들어갈 수 있다.

## 진단이 바로 그 부분에 눈이 없었다

--dialog-shots 는 window.Content 만 찍고 있었다. 그래서 이 결함을 <b>한 번도 보여 준 적이 없다</b> —
MessageDialogView 만 타이틀바가 사진에 나왔는데, 그건 그 창의 바가 Content 안에 있어서였다.
창 전체를 찍도록 바꿨다(템플릿의 PART_Root). 고칠 대상이 사진에 안 나오는 진단은
있으나 마나였고, 실제로 이번에도 첫 캡처가 "고쳤는데 그대로"로 보여 한 번 헛짚게 만들었다.

## 강조 버튼 — 규칙은 이미 있었다

Primary 스타일을 열한 창 중 넷만 쓰고 있었다. 그런데 IsDefault="True" 는 대부분의 창이
이미 주 동작에 달아 두었다 — 규칙을 새로 만들 필요가 없었다.
<b>기본 버튼 = 강조</b>로 맞춘다(서식 이력·서식 등록·마스크·레이어 필터·데이터소스 결과·환자 선택).

글꼴 관리자만 예외로 손으로 골랐다. IsDefault 가 없고 버튼이 셋인데,
강조는 <b>고른 것만</b> 바꾸는 '선택 조합에 적용'에 준다. '전체에 적용'은 문서 전체를
한 번에 바꾸므로 눈이 먼저 가는 자리에 두지 않았다 — 되돌리기가 있어도
되돌릴 생각을 하려면 먼저 알아채야 한다.

환자 선택의 '선택'에는 Primary 만 주고 IsDefault 는 건드리지 않았다 —
검색칸에서 Enter 는 '찾기'여야 한다.

## 판정

- 단위 시험 388 · edit-smoke 376건 전건 통과
- --dialog-shots 69장 전건 통과(창 전체 캡처로 바뀐 뒤에도)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 남은 것

내용 여백은 아직 창마다 다르다 — 설명 문장이 창 위쪽 가장자리에 바짝 붙는 창이 여럿이다.
타이틀바가 생겨 덜 도드라지지만 통일된 것은 아니다. 별도로 잡을 것.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:42:12 +09:00
MsystechandClaude Fable 5 020595f8d4 사용성 2순위 — 있는 명령에 닿는 길을 낸다
1순위가 '틀린 것 고치기'였다면 여기는 <b>이미 있는 명령에 닿는 길</b>이다.
새 패널·새 화면 없이 커맨드·단축키·메뉴 층에서 끝난다.

## 선택 확장 4종 — 정렬 8종의 실효 사용률을 통째로 끌어올린다

정렬 6종·간격 4종·같은 크기가 다 있는데, 그 명령들이 전제하는 '올바른 선택'을
만들 수단이 없었다. 표 서식에서 가장 흔한 편집이 "이 행의 입력칸 8개를 조금 높이고
아래로 붙이자"인데, 그 8개를 고르는 길이 <b>Ctrl+클릭 8번뿐</b>이다.
마퀴는 교차 판정이라 행 띠를 스치는 것이 전부 딸려 온다 —
실측 787건에서 행 무리의 94.2%에 침입자가 있고 그 수가 중앙값 7개·p90 41개다.
즉 지금까지는 "드래그 → 딸려온 것 빼기"가 매번 앞에 붙었다.

주 선택 기준으로 같은 행/열/종류/크기인 <b>형제</b>를 선택에 더한다(Alt+1~4, 우클릭 메뉴 맨 위).
범위를 형제로 한 것은 정렬 명령이 쓰는 범위와 같아야 결과를 예측할 수 있어서다.

2px 오차를 허용한다. 정확히 같은 값만 묶으면 눈으로 맞춘 서식에서 <b>일부만</b> 잡히고,
8개 중 5개를 정렬해 놓고 나머지가 남은 것을 못 알아채는 것이 가장 나쁜 결과다.
격자 크기에 묶지 않았다 — 격자를 바꿨다고 선택 규칙이 달라지면 예측할 수 없다.
잠긴 것은 뺀다. 편집하려고 넓히는 선택인데 안 움직이는 것이 섞이면
정렬을 눌렀을 때 "왜 이건 그대로지"가 된다.

## 줌 단축키 — 커맨드는 처음부터 있었고 키만 없었다

8pt 글자를 확인하려 200% 로 올렸다가 전체 배치를 보려면, 캔버스에서 손을 떼고
<b>화면 맨 아래 가운데</b> 플로팅 바의 배율 버튼을 눌러 팝업을 열고 '화면 맞춤'을 골라야 했다.
확대·축소조차 Ctrl+휠뿐이라 트랙패드에서는 배율을 바꿀 키보드 수단이 아예 없었다.

Ctrl+0(100%) · Ctrl+1(화면 맞춤) · Ctrl+± 를 <b>창</b> InputBindings 에 건다.
캔버스 키 처리기에 두면 안 된다 — 그쪽은 포커스가 TextBox/ComboBox 면 통째로 비켜서므로
인스펙터 값을 고친 직후에 줌 키가 죽는다.

## Ctrl+방향키로 크기 — 위치에만 있던 미세 조정의 짝

입력칸 폭을 3px 늘리려면 오른쪽 인스펙터 W 칸으로 가서 숫자를 읽고 3 을 더해 입력했다 —
캔버스와 300px 떨어진 패널 사이를 마우스로 왕복한다. 마우스 핸들은 판정 반경이 화면 6px 이라
작은 컨트롤에서 3px 를 정확히 집기 어렵고, 스냅이 붙으면 원하는 값에 못 선다.

넛지와 같은 400ms 코얼레스를 쓴다. 선은 LineGeometry 를 태워 두께 축이 늘어나지 않게 막는다 —
허용하면 디자이너에만 두꺼운 선이 보이고 EMR 은 원래 두께로 그린다.

## 잠금·숨김을 캔버스에서, 그리고 잠긴 것을 보이게

배경 괘선 40개를 잠가 두고 그 위에서 라벨만 만지고 싶을 때, 지금은 좌측 패널을 레이어 탭으로
바꾸고 트리에서 40행을 찾아 13px 자물쇠를 40번 눌러야 했다. 마퀴로 40개를 잡아 놔도
한 번에 잠글 수단이 없었다. Ctrl+L / Ctrl+Shift+H 와 우클릭 메뉴를 붙인다.

<b>표시가 없던 것이 더 문제였다.</b> 숨김은 0.25 투명이라 티가 나는데 잠금은 아무 표시가 없어,
클릭도 드래그도 안 되는 이유가 화면에 없었다 — "왜 이것만 안 잡히지"만 남는다.
점선 테두리를 <b>오버레이 층</b>에 그린다. 종이 층(PageView)에 넣으면
db-render 게이트가 그것을 찍어 md5 가 깨진다 — 레거시와의 픽셀 동일성을 잠그는 게이트다.

메뉴 이름은 '선택 잠그기'로 한정했다. <b>해제는 캔버스에서 못 한다</b> —
히트 판정이 잠긴 것을 후보에서 빼므로 우클릭조차 닿지 않는다. 해제 경로는 레이어 패널이다.

## Ctrl+\ — 유일한 레이아웃 변경

좌 248 + 스플리터 10 + 우 300 = 558px 가 <b>항상</b> 점유된다. MinWidth 180/250 때문에
스플리터를 끝까지 밀어도 사라지지 않고, 보기 메뉴에도 항목이 없었다.
가로 용지를 120% 로만 올려도 캔버스에 가로 스크롤이 생기는데 '종이만 보기' 상태가 없다.

좌우를 <b>함께</b> 접는다. 개별 토글 두 개를 두지 않았다 — 목적이 하나라 키도 하나여야 손이 기억한다.
MinWidth 도 함께 0 으로 내려야 실제로 사라진다(Width=0 만으로는 최소폭이 이긴다).
펼친 폭은 따로 기억한다 — 접힌 0 을 사용자가 정한 폭으로 착각하면 다음에 못 되돌린다.
F4(인스펙터 진입)를 누르면 접힌 인스펙터를 먼저 편다.

레이아웃은 이것 말고 바꾸지 않았다. 좌 248 / 우 300 은 이미 측정된 값이고
(레이어 행 최소치, 인스펙터 X|Y 한 줄 최소치) 라벨 1개 선택 시 자주 쓰는 4종이
0 스크롤·0 클릭이다. 컨트롤 1,000개짜리 서식에서 트리와 속성을 동시에 봐야 하는 작업이라
떠 있는 패널로 바꾸면 겹침·재배치 비용만 생긴다.

## 판정

- 단위 시험 388
- edit-smoke 376건 전건 통과 (선택 확장 7건 · 크기 조절 2건 · 잠금 5건 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 (잠금 표시가 종이에 안 나온다는 증거)
- <b>--cleartype 은 이번에 판정 불가다</b> — 게이트가 스스로 "캡처 실패, 색 픽셀 0" 이라고 보고한다.
  이전 커밋에서 stash 로 되돌려 돌려도 같은 결과라 이 변경 탓이 아니다(환경 문제).
  화면 캡처가 되는 상태에서 다시 확인할 것.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:28:43 +09:00
MsystechandClaude Fable 5 fccd441193 사용성 1순위 — 조용히 틀리게 동작하던 넷을 고친다
사용성 감사를 돌렸더니 결론이 뜻밖이었다. <b>없는 기능이 문제가 아니었다</b> —
정렬 6종·간격 4종·같은 크기·그룹·스냅 가이드·레이어 트리·일괄 편집이 이미 다 있다.
진짜 문제는 있는 것이 틀리게 동작하고, 그 실패가 화면에 아무 흔적을 남기지 않는 것이었다.
넷 다 사용자가 무엇을 잃었는지 모르는 채로 저장하는 경로다.

## ① Ctrl+Shift+Z 가 다시 실행이 아니라 되돌리기였다

CanvasKeyboardBehavior 의 `case Key.Z when ctrl` 에 shift 검사가 없었다.
바로 아래 Key.G 는 ctrl && shift 를 가르는데 Z 만 빠져 있었다.
두 번 되돌린 뒤 한 단계 물리려 Ctrl+Shift+Z 를 누르면 <b>세 단계 전</b>으로 간다.
방향이 반대인 줄 모르면 두세 번 더 눌러 편집분을 통째로 날리는데,
되돌리기는 언제나 성공하므로 경고가 없다. 레이어 패널 쪽도 같은 짝이라 함께 고쳤다.

## ② Ctrl+드래그 복사를 Esc 로 취소하면 사본이 남았다

이번 세션에 Ctrl+드래그 복사를 넣으면서 만든 회귀다. CancelDrag 는 좌표만 되돌리는데,
사본은 이미 문서에 들어가 있으므로 원래 자리로 되돌리면 <b>원본과 픽셀 단위로 겹친다</b>.
화면은 취소된 것처럼 보이고, 그대로 저장하면 종이에 같은 글자가 두 번 인쇄된다 —
증거는 "조금 굵어 보인다" 뿐이다.

복제한 드래그였으면 좌표 복원 대신 복제 자체를 물린다. 순서가 중요하다:
Undo 는 문서를 딥클론으로 갈아끼우므로 origBounds·downControl 이 죽은 참조가 된다 —
상태를 먼저 비우고 그다음에 되돌린다. 그리고 DiscardRedo 로 redo 이력도 버린다.
Esc 는 "되돌린다"가 아니라 "없던 일로 한다"이므로, Ctrl+Y 로 되살아나면 취소가 아니다.

## ③ 우클릭이 커서 아래를 잡지 않았다

우클릭 메뉴는 20항목인데 우클릭 <b>선택</b> 핸들러가 저장소 전역에 0건이었다.
라벨 A 를 골라 둔 채 떨어진 체크박스 B 를 우클릭하면 메뉴는 B 위에 뜨는데
[삭제]는 A 를 지운다. 선택이 비어 있으면 항목이 전부 살아 있는 채로 아무 일도 안 한다.
윈도우의 거의 모든 프로그램이 지키는 규약이라 사용자는 메뉴가 가리키는 대상을
확인할 생각조차 하지 않는다 — 메뉴 전체의 신뢰가 이 한 걸음에 달려 있다.

이미 선택 안에 있으면 선택을 유지한다(다중 선택 우클릭 정렬 흐름을 깨면 안 된다).
e.Handled 는 두지 않아 기존 ContextMenu 가 그대로 열린다.

<b>함정 하나</b>: HitTestControl 은 잠금·숨김을 히트 후보에서 아예 빼므로,
잠가 둔 배경 괘선 위 우클릭이 '빈 곳'으로 판정돼 선택이 사라진다.
잠금은 편집 보호지 부재가 아니다 — AnyControlAt 을 따로 만들어 그 위에서는 선택을 지우지 않는다.

## ④ 저장 안 된 문서를 화면에서 구별할 수 없었다

UndoService.IsDirty 는 저장 지점 깊이까지 추적해 정교하게 있었는데
(되돌리기로 저장 지점에 돌아오면 자동으로 깨끗해진다) 화면에는 한 번도 나오지 않았다.
서식 셋을 열어 둘을 고치고 하나는 보기만 한 뒤, 어느 것이 안 저장됐는지 알 방법이 없다 —
탭 제목이 저장 전후로 글자 하나 다르지 않다. 그래서 안전하게 전부 다시 저장하게 되는데
<b>DB 저장은 새 버전을 만든다</b> — 서식마다 쓸데없는 버전이 쌓인다.

탭에 6px 점을 찍는다. 통지는 값이 <b>바뀔 때만</b> 울린다 — 매번 울리면 드래그 한 번에 수백 번이다.

## 판정

- 단위 시험 388
- edit-smoke 362건 전건 통과 (Esc 취소 4건 · 되돌리기/저장표시 6건 · 잠김 히트 4건 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --dialog-shots · --cleartype 전건 통과

## 감사에서 나온 나머지

2순위(선택 확장 4종·줌 단축키·Ctrl+방향키 크기·잠금 토글)와 3순위는 아직 안 했다.
<b>레이아웃은 바꾸지 않기로 했다</b> — 좌 248/우 300 은 이미 측정된 값이고
(레이어 행 최소치, 인스펙터 X|Y 한 줄 최소치) 라벨 1개 선택 시 자주 쓰는 4종이
0 스크롤·0 클릭이다. 다만 패널을 접을 수단이 없는 것은 실재하는 부재다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:16:15 +09:00
MsystechandClaude Fable 5 be9d697a0d 글꼴을 목록에서 고른다 — 매번 타이핑하지 않는다
글꼴 칸이 자유 입력 텍스트 하나였다. "돋움"을 매번 손으로 쳐야 했고,
한 글자만 틀려도 레거시는 <b>아무 말 없이</b> 기본 글꼴로 떨어진다 —
디자이너에서는 멀쩡해 보이고 EMR 에서만 다른 글꼴로 나온다.

이제 설치된 글꼴 목록에서 고른다. 목록의 각 이름은 그 글꼴로 그린다 —
이름만으로는 어떤 모양인지 알 수 없다. 페이지 기본 글꼴 칸도 같은 것을 쓴다
(전에는 페이지 쪽만 자유 입력으로 남아 있었다).

## 이름의 형태가 핵심이다

레거시가 저장하는 값은 GDI 가 주는 <b>한글 이름</b>이다 — 실사용 서식 실측:
"굴림, 11.25pt, style=Bold" · "돋움, 9.75pt". 그런데 WPF 의 FontFamily.Source 는
같은 글꼴을 "Gulim" 으로 준다. 영문 이름을 목록에 쓰면 <b>고르는 순간</b> 서식의 글꼴
이름이 바뀌고, 레거시가 그 이름을 못 찾으면 조용히 기본 글꼴이 된다.

그래서 글꼴이 스스로 담고 있는 ko-KR 이름표를 먼저 읽고, 없을 때만 원래 이름을 쓴다.
검사가 이것을 고정한다 — 목록에 "굴림"·"돋움"·"맑은 고딕" 중 하나는 반드시 있어야 한다.

## 목록에 가두지 않는다

병원 PC 와 이 PC 의 설치 글꼴은 다르다. 목록에만 고를 수 있게 하면
여기 없는 글꼴을 쓰는 서식을 열었을 때 그 값을 고를 수 없고, 칸을 건드리는 순간 사라진다.
그건 서식이 틀린 것이 아니라 <b>지금 이 화면이 병원과 다르게 보인다</b>는 뜻일 뿐이다.

그래서 고르기도 되고 쓰기도 되는 칸으로 두고, 이 PC 에 없는 글꼴이면
값은 그대로 둔 채 ⚠ 로 알린다("저장되는 값은 그대로지만, 화면에는 대체 글꼴로 그려집니다").

커밋은 포커스를 잃을 때 한 번이다 — 커밋 한 번이 문서 전체 딥클론 1회라
글자마다 커밋하면 "맑은 고딕" 여섯 자에 스냅샷 여섯 개가 쌓인다.

## 판정

- 단위 시험 388
- edit-smoke 348건 전건 통과 (글꼴 9건 추가 — 한글 이름·저장 형식·없는 글꼴 보존·경고)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --dialog-shots · --cleartype 전건 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 16:30:49 +09:00
MsystechandClaude Fable 5 d45b674e29 여러 개를 골라 공통 속성을 한 번에 바꾼다
커밋 규약은 그대로다 — BindRow 는 이미 다중 대상이었고(스냅샷 1회 → 선택 전체 순회)
바뀐 것은 "어떤 행을 만드는가"뿐이다. 새 커밋 경로를 만들지 않았다.

## 막혀 있던 곳

타입이 하나라도 다르면 타입 전용 섹션이 통째로 사라졌다(RebuildCore 의 items.All(i => i.Type == type)).
그런데 여러 개를 함께 고쳐야 하는 속성 — 읽기 전용·인쇄 출력·필수 입력·재조회 — 이 정확히
거기 있었고, 없는 이유를 알려 주는 표시도 없어 "이 도구로는 안 되는 일"로 보였다.

더 나쁜 것은 <b>탭</b>이었다. 탭 가시성을 행을 만들기 전에 descriptor 하나로 정하는 바람에
타입이 섞이면 데이터·동작 탭이 접히고 selectedTab 이 디자인으로 강제 복귀했다.
공통 섹션을 완벽하게 만들어도 그 뒤에 갇힌다 — 순서를 바꾸지 않으면 어떤 병합 규칙으로도 못 고친다.
그래서 '① 공통 속성 계산 → ② 탭 → ③ 섹션' 으로 재배치했다.

## 무엇을 공통으로 보는가 — BatchEditPolicy

키 이름이 같은 것으로는 부족하다. Editor·Choices(원소와 순서)·Default 까지 같아야 한 칸에 묶는다.
이 규칙 하나가 예외 코드 없이 함정들을 잡는다.

- 정렬(TextAlign): 라벨은 ContentAlignment 9값, 텍스트박스는 HorizontalAlignment 3값이다.
  이름만 보고 묶으면 "MiddleCenter" 가 텍스트박스에 들어가고 레거시는 그것을 읽지 못한다.
- 테두리(BorderStyle): 기본값이 Fixed3D 대 None 이다. 기본값이 갈리면 키가 없는 대상이
  <b>남의 기본값</b>으로 보이고, 갈렸는지 판정부터 틀어진다.
- 표시(visible/Visible): 라벨만 소문자다(레거시가 그림자 속성을 쓴다). 키가 달라 자연히 탈락한다.
  <b>별칭으로 묶지 않았다</b> — 묶으면 라벨에 대문자 키가 생겨 EMR 에서는 사라지는데
  우리 미리보기는 계속 보여 준다.
- 스키마를 모르는 타입(표 등)이 섞이면 빈 집합이다. 무엇이 공통인지 말할 근거가 없다.

그 위에 대상 집합을 보는 차단을 건다. 근거 대부분이 키 이름이 아니라 <b>누가 선택됐는가</b>에 있다.

- 유일해야 하는 것: 이름·서명 슬롯·탭 순서·링크 대상. 서명 슬롯이 같으면 전자동의서가
  보호자 칸에 환자 서명을 찍는다 — 종이에는 서명이 다 있어 아무도 의심하지 않는다.
- 남을 이름으로 가리키는 배선: 액션 대상 컨트롤·합산식·이벤트 매핑. 같은 값을 넣으면
  5번 문항을 누를 때 1번 문항이 비워지는 식으로 <b>다른 곳</b>이 깨진다.
- 되돌릴 수 없는 것: 일련번호 발급은 서식을 여는 것만으로 DB 카운터를 올린다.
  스냅샷으로 덮이지 않는 유일한 부류라 커밋 자체를 막는다.
- 같은 부모의 라디오 Checked: 레거시 라디오는 GroupName 이 없어 직계 부모가 문항인데,
  로더가 자식을 부모에 붙이기 <b>전에</b> 값을 대입해 형제가 꺼지지 않는다.
  한 문항에 답이 둘 켜진 채로 저장되고 그대로 열린다. 부모를 모르면 막는 쪽으로 뒀다.
- 선의 방향·굵기: 전용 편집기는 컨트롤마다 크기까지 함께 고치므로 허용하고,
  고급(원문) 경로만 막는다 — 거기서는 속성만 바뀌어 XML 은 Vertical 인데 경계는 가로인 선이 된다.

이름 행은 items.Count == 1 을 <b>유지</b>했다. 푸는 것이 목표처럼 보였지만 반대다 —
풀면 두 번째 대상부터 이름이 충돌하는데, 그 거절이 모달을 띄우고도 커밋 루프를 멈추지 못한다.

## 고급(원문) 섹션을 다중 선택에도 열되, 판정을 대상 전체로

전에는 행의 종류·편집 여부·키 목록을 items[0] 하나로 정하면서 커밋은 선택 <b>전체</b>에 썼다.
그래서 이미지·중첩·참조 값을 지키던 읽기 전용 방어가 "처음 클릭한 컨트롤"에만 걸렸고,
클릭 순서에 따라 같은 조작이 안전해지거나 파괴적이 됐다.
게다가 GetText 가 문자열 아닌 값에 null 을 주므로 빈 값과 이미지 원문이 둘 다 "" 로 접혀
<b>갈렸다는 표시조차 나오지 않았다</b> — 경고 없이 base64 가 문자열로 교체된다.

이제 전부 갖고 전부 문자열일 때만 편집을 연다. 아니면 이유를 밝힌 읽기 전용 행이다
("3개 중 1개에만 있음", "대상마다 값의 형태가 다릅니다"). 없던 대상에 키를 만들면
레거시 서식생성기가 열 때마다 오류 모달을 띄우므로 만들지 않는다.
'속성 추가'는 단일 선택만 — 중복 검사·쓰기가 전부 대상 하나 기준이라 N개 중 1개에만 키가 생긴다.

## 함께 고친 것 — 다중 편집을 넓히기 전에 막아야 했던 것들

**갈린 토글을 한 번 누르면 꺼졌다.** 실측으로 확인했다(진짜 ToggleButton + 진짜 바인딩):
누르기 전 IsChecked=null → 커밋 "False" → 누른 뒤 False. 즉 굵게를 <b>켜려고</b> 누른 한 번이
선택 전체의 굵게를 껐다. WPF 는 불확정에서 IsThreeState 와 무관하게 false 로 간다.
게다가 세그먼트 토글에는 갈림 표시가 없어 '꺼짐'과 픽셀 단위로 같았다 — 무엇이 일어났는지
볼 수도 없었다. 갈린 상태의 클릭을 '켜기'로 읽고, {x:Null} 표시를 붙였다.

**갈린 값이 '없음'으로 보였다.** 쿼리·마스크·배선 행은 빈 칸이 아니라 "(쿼리 없음)" 같은
<b>단정</b>을 그린다. 서로 다른 쿼리를 든 둘을 골랐을 때 "쿼리 없음"이 나오면 빈 칸보다 나쁘다 —
없다고 믿고 새로 쓰면 양쪽 원본이 한꺼번에 사라진다. "(여러 값)" 으로 바꿨다.
정렬 격자도 갈리면 9칸이 전부 꺼져 '아직 안 고름'과 같았다 — 격자 오른쪽에 표식을 뒀다.

**안 바뀌었는데 문서가 '수정됨'이 됐다.** 갈린 숫자 칸에 "100px" 을 넣으면 스냅샷이 먼저
쌓이고 대상마다 파싱에 실패해 아무것도 안 바뀌었다. 그걸 지우려 누르는 Ctrl+Z 가 다음 문제를 밟는다.
RowBinding.Validate 를 두어 커밋 전에 거르고 칸을 되돌린다. 값이 이미 전부 같으면 스냅샷도 안 찍는다.

**Undo 한 번에 선택이 증발했다.** 문서 교체 후 재구성의 첫 줄이 Selection.Clear() 라,
'여럿 고르기 → 바꾸기 → 확인 → Ctrl+Z → 다시'라는 이 기능의 유일한 작업 흐름이
첫 되돌리기에서 끊겼다. Id 로 다시 찾아 선택을 복원한다.

**텍스트·항목 목록은 확인을 받는다.** 대상마다 다르던 고유값이 한 번에 사라지는 편집이다.
좌표·색·인쇄여부에는 붙이지 않았다 — 확인을 남발하면 정작 위험한 것도 습관적으로 넘긴다.

## 재현하지 못한 것

조사에서 P0로 지목된 ComboBox 코어스 되쓰기(목록에 없는 값을 null 로 바꿔 소스에 되써서
세로선을 회전시킨다)는 <b>재현되지 않았다</b>. 목록 밖 값을 넣은 양성 대조에서도 되쓰기가
관측되지 않아, 이 진단은 그 경로에 둔감하다. 그래서 '되쓰기가 없다'를 주장하지 않는다.
대신 갈렸을 때 빈 문자열 자리를 목록에 만들어(EnsureMixedPlaceholder) 코어스가 성립할
조건 자체를 없앴다 — 덤으로 빈 칸이 "여러 값"으로 보인다.

## 판정

- 단위 시험 388 (BatchEditPolicy 20건 추가)
- edit-smoke 339건 전건 통과 (일괄 편집 12건 · 혼합 표시 4건 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --db-patient "김" · --dialog-shots · --cleartype · --db-filecfg 전건 통과

## 남는 것

- 혼합 상태에서 '선택 전체를 빈 값으로' 는 못 한다 — ""→"" 가 커밋되지 않아서다.
  하려면 커밋을 우회하는 새 경로가 필요하고 그건 별건이다.
- 갈린 숫자 칸의 ↑/↓ 는 무동작이다(빈칸을 0으로 뭉개지 않으려는 기존 방어).
  다중 선택은 좌표가 갈리는 것이 기본에 가까워 자주 보이지만 이번 범위 밖으로 뒀다.
- 라벨의 대문자 Visible 을 PrintFilter 가 레거시와 다르게 읽는 문제는 <b>지금도</b> 있다.
  이번 설계는 병합에서 라벨을 빼 새 사례를 만들지 않을 뿐, 기존 어긋남은 고치지 않았다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 16:24:16 +09:00
MsystechandClaude Fable 5 8cc2d68ac1 미리보기를 눌러 볼 수 있게 하고, Ctrl+드래그를 복사로 바꾼다
사용자 지적 두 건. 둘 다 라디오 배타 그룹이 걸린 문제였다.

## 미리보기 클릭이 안 먹었다

레거시 미리보기(TK_PREVIEW)는 정적 그림이 아니라 런타임 모드라 눌러 보며 확인한다.
우리 미리보기는 그림이었다 — 그래서 <b>라디오가 배타로 묶였는지 확인할 방법이 없었다</b>.
패널로 묶었다고 믿고 저장했다가 임상 화면에서 두 개가 동시에 켜지는 것이
이 서식에서 가장 흔한 사고인데, 저장 전에 그걸 볼 수단이 없었던 것이다.

PreviewCheckState — 컨트롤 Id → 체크 상태 사전. 문서는 건드리지 않는다.
확인하려고 누른 것이 서식 수정이 되면 미리보기를 열었다 닫은 것만으로
저장할 내용이 생긴다. 렌더 시점(WithTagValue)에만 덮고, 창을 닫으면 사라진다.

배타는 직계 부모 안에서만. 레거시 RadioButton 은 WinForms 를 그대로 상속해
GroupName 이 없고(RadioButton.vb:7-8) 배타 판정을 부모 컨테이너에 맡긴다.
운영 실측도 라디오 15,184개 중 15,026개(98.96%)가 패널 직계 자식 — 패널 1개 = 문항 1개다.

- PreventEditing=True 는 안 눌린다(RadioButton.vb:271-279 — 값 대입만 되고 클릭은 무시).
  Locked 는 편집 보호일 뿐이라 눌린다.
- 라디오는 다시 눌러도 안 꺼진다. 끄는 것은 형제를 누르는 것뿐이다.
- 손으로 누른 값이 태그 값을 이긴다 — 그 칸을 직접 눌러 보는 중이므로 사용자의 손이 마지막이다.
- 환자를 바꾸거나 서식을 갈아타면 지운다. 안 지우면 손으로 누른 값이
  새 환자의 실제 값을 가려, 확인하려고 만든 창이 확인을 막는다.
- 이 창에서 인쇄하면 화면과 같은 상태로 나간다(같은 checks 를 Print 까지 넘긴다).

히트테스트는 WPF 가 아니라 모델 트리로 한다. 종이 비주얼은 템플릿이 만든 그림이라
컨트롤 하나가 여러 요소로 쪼개져 있고 일부는 히트테스트에서 빠져 있다.
캔버스의 HitTestControl 과 두 군데만 다르다 — Locked 를 보지 않고,
그려지지 않은 것(숨김·데이터소스·인쇄 제외)을 뺀다.

## Ctrl+드래그가 복사가 아니었다

예전에는 '선택에 더하고 이동'이었다(Ctrl 이 선택 추가 수정자였으니까).
그래서 같은 컨트롤을 여러 개 놓는 방법이 복사·붙여넣기뿐이었는데,
붙여넣기는 <b>항상 최상위 +12,+12</b> 에 떨어진다.
패널 안 라디오를 그렇게 복사하면 사본이 패널 밖으로 나가 배타 그룹이 깨진다 —
눈으로는 안 보이고 임상 화면에서만 드러나는 고장이다.

DuplicateSelectionInPlace — 제자리 복제. 부모를 유지하므로 사본이 같은 문항 안에 남는다.
드래그 문턱을 넘은 지점에서 복제한다(누름 시점에 하면 Ctrl+클릭만으로도 사본이 생긴다).
복제가 스냅샷을 이미 찍었으므로 이어지는 이동은 추가로 찍지 않는다 —
안 그러면 "복사 후 이동"이 Undo 두 번이 되고, 한 번만 누른 사용자는
원본 자리에 사본이 겹쳐 남은 상태를 본다.

Ctrl 은 이 제스처에서 더하기가 아니다. 합집합으로 두면 아까 선택해 둔 컨트롤까지
함께 복제돼 하나 집어 끌었는데 화면 다른 곳에 사본이 여럿 생긴다. Shift 는 그대로 더하기다.

선택 단위가 패널이므로 자식을 그냥 집으면 <b>문항이 통째로</b> 복사되고,
더블클릭으로 들어간 뒤 집으면 <b>그 선택지만</b> 복사돼 같은 패널에 남는다.
둘 다 필요한 동작이라 둘 다 시험한다.

## 판정

- 단위 시험 368 (PreviewCheckState 10건 추가)
- edit-smoke 320건 전건 통과 (Ctrl+드래그 복사 9건, 미리보기 클릭 5건 추가)
  · 20-5f 의 옛 단언 2건은 의미가 바뀌어 교체했다("합집합으로 함께 움직인다" → "복사된다")
  · 20-5c(빈 선택 Ctrl+드래그)가 남기는 사본을 되돌린다 — 안 하면 뒤따르는 개수 검사가 흔들린다
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
- --db-patient "김" 39건 · --dialog-shots · --cleartype · --db-filecfg 전건 통과

시험을 처음 쓸 때 자식을 Children[0]/[1] 로 집었는데 자식 VM 은 z 순서로 온다.
이미 켜진 라디오를 누르는 시험이 되어 "문서가 바뀌었다"는 거짓 실패가 났다 — Id 로 집는다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 15:07:51 +09:00
MsystechandClaude Fable 5 d965b3f217 라디오버튼도 패널로 묶는다 — 차단을 경고로 (운영 표준 패턴을 막고 있었다)
조사 워크플로(읽기 3 + 반박 검증 3)로 확정한 사실:
- 레거시 RadioButton 은 System.Windows.Forms.RadioButton 을 그대로 상속하고
  GroupName 류 속성이 소스·IL 어디에도 없다 → <b>직계 부모가 곧 배타 단위</b>
  (검증자가 실제 어셈블리를 로드해 Panel/GroupBox/MExpandablePanel/호스트가
   각각 독립 집합이고, 패널 밖으로 옮기면 배타가 즉시 끊김을 실행으로 확인)
- 운영 전수(디자인 1,270건 전량 덤프): 라디오 15,184개 중 <b>15,026개(98.96%)가
  패널 직계 자식</b>, 그룹 5,582개(평균 2.72 · 중앙값 2 · 최대 72),
  최상위 직속은 158개(1.04%)뿐. 그룹을 나타내는 속성은 0건 —
  즉 "패널 1개 = 문항 1개" 가 사실상의 저장 규약이다

그런데 우리는 라디오 Ctrl+G 를 <b>아예 막아</b> 두었다(GroupBlockReason ①) —
운영 서식의 98.96% 패턴을 우리 도구로는 만들 수 없었다는 뜻이다.
막은 이유("일부만 묶으면 택1이 갈라진다")는 타당하지만 그것은 경고할 일이지
기능을 없앨 일이 아니다. 오히려 막으면 한 페이지의 라디오가 전부 한 그룹이 되어
문항을 여러 개 만들 수 없다.

- 차단 제거, 대신 같은 자리에 <b>묶이지 않고 남는 라디오가 있을 때만</b>
  "N개 중 M개만 묶습니다 — 남는 것은 다른 선택 묶음이 됩니다" 로 되묻는다
  (RadioSplitWarning). 전부 묶으면 되묻지 않는다.
- edit-smoke: "라디오는 묶이지 않는다" 판정을 뒤집어 "라디오도 패널로 묶인다"
  + "패널 자식이 된다" + 경고 판정 2건(일부만/전부)으로 교체
- GroupSelection 의 낡은 XML 주석("동일 GroupId 부여")을 실제 동작(패널 래핑)과
  근거 수치로 교체 — GroupId 계열은 이미 죽은 코드다

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:44:20 +09:00
MsystechandClaude Fable 5 345b2e1da1 싸인 이미지 SMB 직접 읽기 — 공유 매핑 구현(서버측 SMB 개방 대기)
사용자 결정(②SMB 직접 접근)에 따라 구현. 실측 확인:
- 파일서버 = DB 서버와 같은 호스트, 전송 데몬 포트 2002 는 열림
- <b>SMB 445·139 는 닫힘</b>, UNC·관리공유 접근 모두 실패 → 서버측 조치 필요
- 서명 경로는 서버측 절대경로 D:\MsystechHIS\{사이트}\... (설정 루트 E:\msys\ 와 다름)

그래서 공유가 열리는 즉시 동작하도록 매핑을 넣었다:
Images:SignatureShareMap 에 "서버접두=UNC접두" 를 두면 해석 순서가
①레거시 캐시 ②공유(UNC) ③직접 경로가 된다. 접두는 대소문자 무시로
<b>긴 것부터</b> 맞추고(짧은 접두가 정확한 대응을 묻지 않게), '=' 없는
설정 오타는 무시한다(반쪽 경로를 만들면 조용히 틀린다). 자격증명은
설정에 담지 않는다 — 공유 접근은 단말 로그인 계정으로 붙는다.

진단 --db-filecfg 확장: 접속 <b>대상</b>(Type·IP·Port·FileDirectory)과
경로 접두 분포를 찍고(자격증명은 길이만), 공유 매핑별 접근 가능 여부와
파일서버 SMB(445) 개방 여부를 판정해 무엇을 열어야 하는지 알려 준다.

- dotnet test 358/358 (매핑 4건 추가: 치환·긴 접두 우선·오타 무시·해석 순서)
- --edit-smoke 실패 0 · --db-patient ①~㊲ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 12:40:56 +09:00
MsystechandClaude Fable 5 a0333791b6 싸인·직인·로고 17종 — 경로 조회와 이미지 렌더 이식(전송 계층은 재현 불가)
워크플로 조사(읽기 5 + 반박 검증 5)로 전송 계층과 태그 17종을 훑었고,
검증이 조사서의 "FTP" 단정을 반박해 실체가 드러났다: 전송 수단은 단일
프로토콜이 아니라 DB 설정(M_DtlMst 'EMR_FileTransfer' Type)이 고르는
5갈래이고, 새 진단 --db-filecfg 로 확인한 우리 사이트 값은 Type=1 =
FileServer = ActiveX OCX(AxBITSFTLib.AxbitSFT)다. OCX 내부 프로토콜은
코드에 없고 서드파티·ActiveX 는 배포 금지라 <b>다운로드는 재현할 수 없다</b>.

그래서 받지 않고 찾는다: ①레거시 EMR 이 같은 단말에 이미 받아 둔 캐시
({설치경로}\Tmp\DownLoadedFiles\Signature\ — 레거시가 쓰는 그 폴더),
②DB 경로 자체가 열리는 경우(이 병원 저장 형태는 드라이브 절대경로).
둘 다 없으면 사유 → 종이는 빈칸(어제 결정). 판정은 순수 함수
SignatureImagePath 로 두고 테스트 7건(캐시 우선·직접 경로·둘 다 없음·
빈 경로·설치경로 없음·UNC).

경로 조회는 전부 순수 SQL 이라 그대로 이식(SignatureStore): 집도의
(DTRUIDCOD)·마취(수술 ANEUIDCOD / 부서 — 개원일 2021-03-01 분기)·
영상의학(XRAY)·진단검사(LAB)·외출외박 승인자(병동/원무)·병동 수간호사·
병원로고(M_HspMst.HspLgoPth)·병원직인(주석과 달리 DB_REGISTRY 의
'직인경로'). 로그인 사용자 3종은 세션 행에 UidImgPth 를 얹어 왕복 0,
담당의·진단서 의사는 프리페치 행 재사용.

레거시 결함 보존 2건: ETC_병리판독의사싸인은 SELECT 에 UidNam 만 있고
UidImgPth 를 읽어 항상 예외→빈 값(:17434), OCM_Cosign_싸인은 저장 기록
키(EmrKey) 의존이라 미리보기에서 값이 될 수 없다 — 둘 다 사유로 완결.

렌더: PictureBox 가 자리표시자만 그리던 것을 실제 이미지로. 파일을
잠그지 않고(OnLoad) 읽으며, 인쇄가 SizeMode 를 무시하고 사각형에 늘려
그리는 레거시 동작에 맞춰 기본 Stretch=Fill(Zoom 만 비율 유지).
WithTagValue 는 이미지류에서 Text 대신 경로 속성을 채운다.
분류도 정정 — 싸인 계열이 Image(글자만 보여 준다)에서 PatientContext 로.

- dotnet test 354/354 (경로 판정 7건 추가) · --edit-smoke 실패 0
- --db-patient ①~㊲ 전건 통과 — ㊲ 신설: 9개 경로 조회 실행,
  마취과 의사 싸인 경로가 실값 29자로 나온다
- --db-filecfg 신설(값 없이 모드만): Type=1 · E_FilInf 102만행 ·
  UidImgPth 157행 · 경로 모양 드라이브 절대경로
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 10:18:35 +09:00
MsystechandClaude Fable 5 2310971a10 인쇄 용지를 문서 크기로 제시 + Select 형 배선 필터 실행 — 남은 과제 2건
① PDF 높이 어긋남(735pt vs 문서 642pt) 원인 규명·수정: 인쇄가
용지(PrintTicket)를 지정하지 않아 드라이버가 폭이 비슷한 기본 용지
(540×735pt ≈ 16절 190×260mm — test1.pdf MediaBox 실측)에 문서를
얹었다. 폭이 우연히 같아 좌우는 멀쩡하고 아래에만 33mm 여백이 붙어
화면과 다른 종이가 됐다. ShowDialog 전에 PageMediaSize 를 문서 첫
페이지 DIP 크기로 제시 — 사용자가 대화상자에서 용지를 바꾸면 그
선택이 이긴다. (레거시는 사용자가 고른 용지에 문서를 맞추는 방식
(clPrintDesignSheet.vb:660)이라 이 기본값 제시와 충돌하지 않는다.)

② Select 형 DataTableField 필터(운영 268건, 12%) 실행:
지금까지 "아직 옮기지 않았습니다" 사유였다. 필터 문법을 직접 파싱하지
않고 .NET DataTable.Select 에 그대로 넘긴다 — 레거시 배선이 쓰는
바로 그 엔진이라 문법 호환을 증명할 필요가 없다. 행 선택을 순수 함수
(DataTableFieldPicker)로 분리해 DB 없이 검증(테스트 5건 — 필터·0행·
문법 오류·Rows 형·중복 컬럼명 Fill 규칙). 알려진 한계 주석: 우리
결과는 전부 문자열 컬럼이라 숫자 대소 비교 필터는 사전순이 될 수
있다(운영 필터 대부분은 동등 비교).

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 09:35:27 +09:00
MsystechandClaude Fable 5 31a05c205b 값을 못 만든 태그는 종이·미리보기에서 빈칸 — 사유 표기 제거(사용자 결정)
지금까지는 값이 안 되는 태그를 [태그명 — 사유] 로 그렸다("못 만든
것을 감추지 않는다"). 실사용 출력물에 그 문구가 찍히면 안 되므로
빈칸으로 바꾼다 — 태그 이름도 지운다(남기면 값이 나온 줄 안다).
렌더 공용 지점(WithTagValue) 한 곳 수정이라 미리보기·인쇄 동시 적용.
체크류(컨트롤 유지)·해석기 없음(디자인 화면 — 태그 이름 유지)은 그대로.

사유 자체는 사라지지 않는다 — 해석기(TagValue)가 계속 담고 있고,
진단이 그것으로 판정한다. 종이 기반이던 사유 판정 8건을 해석기 수준
판정 + "종이에 사유가 새지 않았다" 판정으로 전환:
- 못 만드는 태그/환자 없음/매핑 없음/값 빈 이름 → 해석기 사유 확인
  + 종이 빈칸 확인(각각 쌍)
- 배선 3건(컬럼 오타·없는 소스·형식 깨짐) → 해석기 사유 + 종이 빈칸
- 환자 뗌/세션 뗌 → "[PAT_차트번호] 사유 복귀" 대신 "이름·값 모두 없음"

- dotnet test 342/342 · --edit-smoke 실패 0 · --dialog-shots 통과
- --db-patient ①~㊱ 전건 통과 (⑬ 등은 해석기 직접 판정이라 무영향)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
  (렌더 대조는 해석기 없는 경로 — 이 변경의 영향 밖)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 09:18:46 +09:00
MsystechandClaude Fable 5 86ecfff1a1 퇴원일시_영문·퇴원일자_낮병동 — 257종 → 259종, 값 태그 이식 완주
OCM_퇴원일시_영문(bzDataInterface.vb:7604 Case Else — 퇴원일시와 같은
분기 구조에 포맷만 MM-DD-YYYY. BSKOREA·YJRCH 등 병원 분기와
KBJY·JEGG MsgBox 분기 미이식), OCM_퇴원일자_낮병동(:7763 — 낮병동
구간의 종료일, 기존 구간 조회 재사용). ETC_수술간호사목록(:15414)은
이름이 "목록"으로 끝나 규칙에 안 걸리는 DataTable 반환 — Table 분류.

이로써 카탈로그 대조 기준 잔여는 싸인·서명·로고·직인 15종(파일서버
이미지 — Image 사유 완결), 콤보용 표 계열(Table 사유 완결),
대조군으로 남긴 ETC_수술실간호사 1종뿐이다. 한 줄 값이 되는 태그는
전부 이식 또는 사유 완결됐다.

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 09:06:00 +09:00
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