내원 이력 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>
This commit is contained in:
Msystech
2026-08-19 16:50:25 +09:00
co-authored by Claude Fable 5
parent 4642439ffd
commit ec47c98bdd
3 changed files with 158 additions and 5 deletions
@@ -155,6 +155,12 @@ public sealed class PatientTagResolver : ITagValueResolver
? $"{value[..4]}-{value[4..6]}-{value[6..8]} {value[8..10]}:{value[10..12]}"
: string.Empty;
/// <summary>YYYYMMDDHHMM → "9999년99월99일 99시99분" (공백 3칸 — 레거시 포맷 문자열 그대로)</summary>
private static string StampKorean(string value)
=> value.Length >= 12
? $"{value[..4]}년{value[4..6]}월{value[6..8]}일 {value[8..10]}시{value[10..12]}분"
: string.Empty;
private readonly ITagValueResolver next;
private readonly PatientContext context;
@@ -893,7 +899,9 @@ public sealed class PatientTagResolver : ITagValueResolver
private TagValue? FromExtra(string tag)
{
if (tag is not ("PAT_실제생년월일" or "PAT_국적" or "PAT_장애등급" or "PAT_건보세대주명"
or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처"))
or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처"
or "OCM_FollowUp" or "OCM_외출외박신청일" or "OCM_외출외박종료일"
or "OCM_최초내원일" or "OCM_초진일_발병일" or "OCM_신환_초진일"))
{
return null;
}
@@ -976,8 +984,59 @@ public sealed class PatientTagResolver : ITagValueResolver
? Wrap(store.Cooperator(m), "협력업체 코드가 없거나 코드표에 이름이 없습니다")
: new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
default:
case "PAT_보호자연락처":
return Wrap(store.GuardianPhone(chtNum), "부가정보에 보호자연락처가 없습니다");
case "OCM_FollowUp":
{
// 지금보다 뒤의 내원이 없으면 레거시도 "" 다(:1055) — 사유로 말한다
var next = store.FollowUp(chtNum);
return next.Length >= 12
? new TagValue(true, Stamp(next))
: new TagValue(false, "지금 이후로 잡힌 내원이 없습니다");
}
case "OCM_외출외박신청일":
case "OCM_외출외박종료일":
{
var comNum = PatientContext.Value(context.ComInf, "ComNum");
if (!decimal.TryParse(comNum, out var m))
{
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
// 기록이 없으면 서버 현재시각이 온다 — 레거시 폴백 그대로(:1924-1927)
var moment = store.LeaveOfAbsence(m, endSide: tag == "OCM_외출외박종료일");
return moment.Length >= 12
? new TagValue(true, StampKorean(moment))
: new TagValue(false, "외출·외박 일시를 만들지 못했습니다");
}
default:
{
// 최초내원일·초진일 계열 — 이 내원의 접수일시가 상한이다(레거시 :2001)
var acpDtm = PatientContext.Value(context.ComInf, "ComAcpDtm");
if (acpDtm.Length == 0)
{
return new TagValue(false, "이 내원의 접수일시를 읽지 못했습니다");
}
string first;
if (tag == "OCM_최초내원일")
{
first = store.FirstVisit(chtNum, acpDtm);
}
else
{
// 진료과 앞 2자는 레거시가 GetCodInfDT 임의 행에서 뽑았다(:2057) —
// 우리는 문맥의 진료 행(결정 규칙 적용)에서 뽑는다
var depCod = PatientContext.Value(context.CodInf, "CodDepCod");
if (depCod.Length < 2)
{
return new TagValue(false, "이 내원의 진료과 코드를 읽지 못했습니다");
}
first = store.FirstVisitByDepartment(chtNum, acpDtm, depCod[..2],
includeNew: tag == "OCM_신환_초진일");
}
return first.Length >= 8
? new TagValue(true, $"{first[..4]}/{first[4..6]}/{first[6..8]}")
: new TagValue(false, "조건에 맞는 이전 내원이 없습니다");
}
}
}
catch (Exception ex)
@@ -1083,6 +1142,8 @@ public sealed class PatientTagResolver : ITagValueResolver
"PAT_성별_남", "PAT_성별_여",
"PAT_실제생년월일", "PAT_국적", "PAT_장애등급", "PAT_건보세대주명",
"PAT_건보_급여_세대주명", "PAT_협력업체", "PAT_보호자연락처",
"OCM_FollowUp", "OCM_외출외박신청일", "OCM_외출외박종료일",
"OCM_최초내원일", "OCM_초진일_발병일", "OCM_신환_초진일",
"PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소",
"OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처",
"OCM_진료과", "ETC_요양기관명칭_병원명",