88dab2edcf5aa77b8da7e7dc0ee4427c0868ed89
전 커밋까지 환자 조회·시각 판정·권한 규칙은 다 있었지만 화면에 붙은 것이 하나도 없었다. 미리보기를 열면 환자 선택이 없고, 태그는 전부 사유만 찍혔다. ## 만든 사슬 PatientPickerDialogView(모달) → PatientContextStore(문맥 5행) → PatientTagResolver(태그 → 컬럼) → PreviewWindow.UsePatient → 종이 모달로 둔 이유: 30초 안에 끝나는 자족적인 일이고 권한 문이 걸린 동작이다. 사이드 패널로 두면 실제 환자 정보를 꺼내는 창이 항상 열려 있게 되는데 이 창은 열려 있는 것 자체가 비용이다. 환자와 내원을 한 화면에 둔다. 고르는 대상은 환자가 아니라 내원이다 — 문맥 5행이 전부 ComNum + AdpDtm 으로 읽히므로 환자만 골라서는 아무것도 못 읽는다. 기준 시점(마지막/처음)을 화면에 노출한다. 입원 내원의 진료과·병실·자격 값을 실제로 바꾸는 값이라 숨기면 값이 왜 다른지 설명할 수 없다. 문맥 읽기를 창 안에서 한다. 닫고 나서 읽으면 실패를 미리보기 창에서 알려야 하고 그때는 이미 고른 것이 사라진 뒤다. ## 매핑은 확인된 6종만 PAT_이름 / PAT_차트번호 / PAT_주민번호 / PAT_휴대전화 / PAT_전화번호 / PAT_주소. 전부 레거시 조회 SQL 의 SELECT 목록에서 컬럼을 직접 확인한 것이다. PAT_나이·PAT_성별은 넣지 않았다 — UDF_GETAGE·UDF_GETSEX 를 거치는 값이라 컬럼을 짐작해 넣으면 값이 조용히 틀린다. 종이에 그럴듯한 값이 찍히면 아무도 의심하지 않으므로 빈칸보다 나쁘다. 안 옮긴 환자 태그는 "아직 옮기지 않았습니다"라고 말한다. "DB 접속이 없습니다"로 뭉개면 환자를 골랐는데도 그 사유가 나와 접속을 의심하게 된다. ## 문맥 조회를 한 번에 다섯 번 읽는다 레거시는 5행을 프로퍼티 접근 시점에 하나씩 지연 로딩한다(각각 별도 왕복). 태그마다 다시 읽으면 태그 40개짜리 서식에 왕복 40회가 된다. ## 개인정보 - 감사 로그는 고른 직후 남긴다 — 종이에 값이 찍히는 것과 무관하게 조회는 이미 일어났다. - 로그에 환자 이름을 남기지 않는다(내원번호로 추적 가능하고, 로그가 개인정보를 들고 있으면 로그 자체가 위험물이 된다). - 진단 리포트도 값 대신 길이만 적는다. - 권한 문은 수정(TK_MODIFY)과 같은 범위다. 넓히면 서식생성기가 환자 조회 도구가 된다. - 화면에서 감사 기록 사실을 말한다. ## 게이트가 잡은 것 셋 ① --db-patient 가 "전건 통과"인데 ⑧~⑬ 이 통째로 SKIP 이었다. 첫 환자에게 내원이 0건이라 문맥 조회를 한 번도 안 돌린 채 초록불이었다. 내원 있는 환자를 30명까지 훑고, 못 찾으면 SKIP 이 아니라 FAIL 로 바꿨다. ② 그렇게 찾은 내원에서 CodInf·CoiInf·CowInf 가 셋 다 0행이었다. 0 을 적어 두는 것으로는 "조건이 어긋났다"와 "행이 없다"를 못 가른다. 조건 없는 원본 행을 함께 찍게 하니 후자였다(1988년 내원, 행 자체가 없음). ③ 그러면 기간 조회 세 갈래는 아직 한 번도 행을 돌려준 적이 없다. 안 돌아본 갈래는 통과한 갈래가 아니므로, 각 표에서 직접 내원을 찾아 들어가 BETWEEN·MtiSeq 조건이 실제로 행을 뽑는 것까지 확인하게 했다(⑮). --edit-smoke 판정 7건 추가. 전부 대조군이 있다 — 환자 없이 같은 종이를 찍어 사유가 나오는 것을 먼저 확인한 뒤 값으로 바뀌는 것을 본다. 미리보기 창 배선도 따로 본다(붙이면 값, 떼면 사라짐). 그래서 고르는 일과 붙이는 일을 나눠 두었다 — 모달에 묻어 두면 "골랐는데 종이가 그대로"를 자동으로 못 잡는다. ## 게이트 - dotnet test 312/312 - --edit-smoke 실패 0 (환자·미리보기 판정 7건 추가) - --dialog-shots FAIL 0 (08b-patient-picker 추가) - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 (변화 없음) - --db-smoke 1,271건 diff 0 - --db-patient ①~⑮ 전건 통과 (기간 조회 3갈래 실제 통과 확인) - --db-modify-smoke S999 8/8 (⑤ 는 E_SctMst 0행으로 여전히 SKIP) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SheetMe 서식생성기 (C#/.NET 10 WPF)
MsystechHIS 레거시 서식생성기([003]EMR\[001]SheetDesigner, VB.NET WinForms)를 대체하는 신규 WPF 서식 디자이너.
레거시 XML 포맷을 읽기·쓰기 모두 무손실 호환하여, 여기서 만든 서식을 기존 EMR 런타임(EMRLoader)이 그대로 렌더링한다.
프로젝트 구성
| 프로젝트 | TFM | 역할 |
|---|---|---|
src/SheetMe.Core |
net10.0 | 포맷 중립 모델(FormDocument) · LegacyXmlSerializer(읽기+쓰기, PropBag 무손실 왕복) · FormJsonSerializer(부 포맷) · ControlRegistry(팔레트/속성 스키마) |
src/SheetMe.Data |
net10.0 | Oracle 저장소 — OracleLegacyFormStore(E_ShtMst/E_SdgMst/E_SctMst, 레거시 동일 저장 의미론) · SctMstXmlWalker · EmrDataContext(M.MW.Data.EMR) |
src/SheetMe.Designer |
net10.0-windows | WPF MVVM 디자이너(M.Framework.WPF) — 캔버스/팔레트/인스펙터/페이지/레이어 |
tests/SheetMe.Core.Tests |
net10.0 | MSTest 44건 — 실서식 XML 왕복 의미론 diff 0 게이트 포함 |
실행
dotnet build
dotnet run --project src/SheetMe.Designer
dotnet test
진단 모드 (자동 검증)
# 첫 페이지 오프스크린 렌더 → PNG (레거시 시각 대조용)
SheetMe.Designer.exe --render-smoke <서식.xml> <출력.png>
# 편집 코어 자동 검증(선택/이동/리사이즈/마퀴/복붙/Undo/왕복) → 리포트
SheetMe.Designer.exe --edit-smoke <리포트.txt>
주요 기능 (v1)
- 캔버스 편집: 클릭/Ctrl/Shift/마퀴 선택, 드래그 이동(스냅: 그리드+정렬 가이드, Alt=자유), 8핸들 리사이즈(다중선택 비례), 키보드 넛지, z-order, 정렬 6종
- Undo/Redo: 문서 스냅샷 메멘토 — 드래그=1스텝, 넛지 400ms 코얼레스
- 팔레트 14종: Label·TextBox·MaskedTextBox·CheckBox·RadioButton·ComboBox·ListBox·CheckList·DateTimePicker·Panel·GroupBox·Line·PictureBox·CalcBox — 미지원 레거시 컨트롤(Spread/MSign 등)은 자리표시로 무손실 보존(이동/리사이즈 가능, 저장 시 원본 속성 전부 유지)
- 인스펙터: 스키마 기반 속성 편집(다중선택 교집합), 폰트/색은 레거시 invariant 문자열 정합
- 페이지/레이어: 페이지 추가/삭제/용지 크기, 레이어 목록(잠금/숨김)
- 그룹: Ctrl+G/Ctrl+Shift+G — 그룹 멤버 클릭 시 그룹 전체가 한 단위로 선택/이동
- 컨테이너 자식 편집: Panel/GroupBox 내부 컨트롤을 캔버스에서 직접 클릭·이동·리사이즈·삭제(상대좌표 유지)
- 미리보기/인쇄: 편집 크롬 없는 렌더(캔버스와 동일 템플릿), FixedPage 1:1(DIP) 인쇄
- 파일: 레거시 .xml 열기/저장(TK_ExportXML 호환) + JSON 가져오기/내보내기(부 포맷)
- DB: E_ShtMst 검색 → 활성 디자인 열기 · DB 저장(레거시 동일: ShtCneYon='Y'→제자리 갱신 / 그 외 SdgDelYon 버저닝 + E_SctMst 재생성, 시퀀스→MAX+1 채번, 단일 트랜잭션) · 신규 서식 등록(미등록 코드 저장 시 자동 안내 — ShtTyp='D' 관행 기본값)
설정 (src/SheetMe.Designer/appsettings.json)
{
"ConnectionStrings": { "His": "Oracle 접속 문자열" },
"FormStore": { "SaveMode": "File" } // DB 저장은 "Db" 로 명시 변경 시에만 활성(안전 게이트)
}
레거시 호환 규약 (정합성 기준: bzDesignSheetLoader.LoadObject 가 오류 없이 복원)
- XML:
<Sheet name="SheetDesignInfo">루트에 페이지당<Object>(MDesignerHost) N개, 자식 Object 먼저 → Property 나중 - 타입: 레거시 AQN 원문 보존, 신규는
M.EMR.UserControl.{Cls}, M.EMR.UserControl, Version=1.0.0.0, ... - 속성값: WinForms TypeConverter invariant 포맷(
"120, 88","굴림, 11.25pt, style=Bold","224, 224, 224") - LocationOnBase 는 저장 시 재계산(부모 절대좌표 누적),
<Binary>는 verbatim 보존(신규 생성 안 함 — .NET 10 BinaryFormatter 제거) - z-order: XML 첫 Object = 최상위(WinForms Controls 규약) ↔ WPF 캔버스는 역순 렌더
실DB 검증 결과 (2026-07-16, 테스트DB 192.168.100.222/MSYSTECHHIS)
- 읽기: 전체 디자인 서식 1,271건 일괄 왕복(Read→Write) 의미론 diff 0 / 예외 0 — 미지원 컨트롤 보존 서식 242건 포함
- 쓰기: S999 무변경 재저장 — 신규 버전 생성(SdgKey 52241→52380), 재저장본 diff 0, E_SctMst 36행 워커 기대치 일치, 이전 버전
SdgDelYon='Y'이력 보존 확인 - 호환성 세부: 텍스트 내 CR 은
NewLineHandling.Entitize( 보존), 레거시 데이터의 제어문자(0x05)는CheckCharacters=false관용 파싱(구식 XmlTextReader 동등)
계획 범위(P0~P5) 전체 완료 — 후속 아이디어
- 레거시 SheetDesigner/EMR 미리보기로 S999 육안 확인 (권장)
- v2 후보: 미지원 컨트롤 네이티브 지원(Spread 표, MSign 서명), 계산박스 수식 평가, 컨트롤 간 자식 드래그 재배치(reparent), 자 눈금(룰러), REST API 경유 모드
설계 계획서: C:\Users\Msystech\.claude\plans\c-net-wpf-flickering-gizmo.md
Languages
C#
99.8%
PowerShell
0.2%