혈액형·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>
This commit is contained in:
Msystech
2026-08-19 17:16:00 +09:00
co-authored by Claude Fable 5
parent f2f4cf81d3
commit 7f147a3012
7 changed files with 241 additions and 8 deletions
@@ -196,6 +196,28 @@ public sealed class PatientExtraStore
+ " ORDER BY DcpStrDte DESC) WHERE ROWNUM = 1", + " ORDER BY DcpStrDte DESC) WHERE ROWNUM = 1",
("m", comNum)); ("m", comNum));
/// <summary>
/// 혈액형(OCM_혈액형, :11783) — S_BlpInf.BlpAboTyp. 원문 Rows(0) 에 ORDER BY 가 없다 —
/// 갱신일시 최신 행으로 고정한다(마지막 판정이 현재 혈액형이다).
/// BlpChtNum 은 VARCHAR2 라 저장 형태를 확정할 수 없어 두 형태 모두 본다.
/// </summary>
public string BloodType(string chtNum)
=> Scalar(
"SELECT v FROM (SELECT BlpAboTyp v FROM S_BlpInf"
+ " WHERE BlpChtNum IN (:c, RPAD(:c, 10))"
+ " ORDER BY BlpUpdDtm DESC NULLS LAST) WHERE ROWNUM = 1",
("c", chtNum));
/// <summary>
/// 최종 월경일(OCM_LMP, :11813) — O_PrgInf.PrgLmpDte. 원문 Rows(0) 에 ORDER BY 가 없다 —
/// 같은 표를 읽는 임신주기(:11845)가 갱신일시 최신순을 쓰므로 그 정렬로 고정한다.
/// </summary>
public string Lmp(decimal comNum)
=> Scalar(
"SELECT v FROM (SELECT PrgLmpDte v FROM O_PrgInf"
+ " WHERE PrgComNum = :m ORDER BY PrgUpdDtf DESC) WHERE ROWNUM = 1",
("m", comNum));
/// <summary>스칼라 1칸 — 0행이거나 NULL 이면 빈 문자열. 잔여 공백은 걷는다(문맥 행과 같은 규칙)</summary> /// <summary>스칼라 1칸 — 0행이거나 NULL 이면 빈 문자열. 잔여 공백은 걷는다(문맥 행과 같은 규칙)</summary>
private string Scalar(string sql, params (string Name, object Value)[] binds) private string Scalar(string sql, params (string Name, object Value)[] binds)
{ {
+24 -3
View File
@@ -32,16 +32,37 @@ public sealed class VitalStore
/// <param name="orderBy">정렬 절 — 예: "EmdDte ASC, EmdTime ASC"</param> /// <param name="orderBy">정렬 절 — 예: "EmdDte ASC, EmdTime ASC"</param>
public string One(decimal comNum, string selectExpr, string pick, public string One(decimal comNum, string selectExpr, string pick,
string[] notNullColumns, string orderBy) string[] notNullColumns, string orderBy)
=> Fetch("E_EmdInf_VITAL", "EmrComNum = :k", comNum, selectExpr, pick, notNullColumns, orderBy);
/// <summary>
/// BST 는 표가 다르다(E_EmdInf_BST, bzDataInterface.vb:12042-12080·12710-12747) —
/// 같은 조인 틀에 표만 바뀐다.
/// </summary>
public string OneBst(decimal comNum, string orderBy)
=> Fetch("E_EmdInf_BST", "EmrComNum = :k", comNum, "EmdBst", "EmdBst",
new[] { "EmdBst" }, orderBy);
/// <summary>
/// BMI·표준체중·조정체중은 내원이 아니라 <b>차트 전체</b>에서 최신 측정을 집는다
/// (bzDataInterface.vb:12159-12333 — EmrChtNum 조건, 주석 처리된 EmrComNum 이 그 흔적).
/// </summary>
public string OneByChart(string chtNum, string selectExpr, string pick,
string[] notNullColumns, string orderBy)
=> Fetch("E_EmdInf_VITAL", "EmrChtNum = RPAD(:k, 10)", chtNum, selectExpr, pick,
notNullColumns, orderBy);
private string Fetch(string table, string where, object key, string selectExpr, string pick,
string[] notNullColumns, string orderBy)
{ {
var filters = string.Concat(notNullColumns.Select(c => var filters = string.Concat(notNullColumns.Select(c =>
$" AND ({c} IS NOT NULL AND {c} <> ' ') ")); $" AND ({c} IS NOT NULL AND {c} <> ' ') "));
var sql = var sql =
"SELECT * FROM (" "SELECT * FROM ("
+ $" SELECT {selectExpr}" + $" SELECT {selectExpr}"
+ " FROM E_EmdInf_VITAL" + $" FROM {table}"
+ " INNER JOIN E_EmrInf ON EmrKey = EmdEmrKey" + " INNER JOIN E_EmrInf ON EmrKey = EmdEmrKey"
+ " AND (EmrSttFlg IS NULL OR EmrSttFlg <> 'D')" + " AND (EmrSttFlg IS NULL OR EmrSttFlg <> 'D')"
+ " WHERE EmrComNum = :iComNum" + $" WHERE {where}"
+ filters + filters
+ $" ORDER BY {orderBy}" + $" ORDER BY {orderBy}"
+ " ) WHERE ROWNUM = 1"; + " ) WHERE ROWNUM = 1";
@@ -51,7 +72,7 @@ public sealed class VitalStore
using var command = connection.CreateCommand(); using var command = connection.CreateCommand();
command.BindByName = true; command.BindByName = true;
command.CommandText = sql; command.CommandText = sql;
command.Parameters.Add(new OracleParameter("iComNum", comNum)); command.Parameters.Add(new OracleParameter("k", key));
using var reader = command.ExecuteReader(); using var reader = command.ExecuteReader();
if (!reader.Read()) if (!reader.Read())
{ {
+4 -1
View File
@@ -2358,7 +2358,10 @@ public static class DbSmoke
+ $" · 최초내원 {extra.FirstVisit(extraOwner, "999912312359").Length}자" + $" · 최초내원 {extra.FirstVisit(extraOwner, "999912312359").Length}자"
+ $" · 초진일 {extra.FirstVisitByDepartment(extraOwner, "999912312359", "00", false).Length}자" + $" · 초진일 {extra.FirstVisitByDepartment(extraOwner, "999912312359", "00", false).Length}자"
+ (owner2 is null ? "" + (owner2 is null ? ""
: $" · 외출외박 {extra.LeaveOfAbsence(owner2.Value, false).Length}자"); : $" · 외출외박 {extra.LeaveOfAbsence(owner2.Value, false).Length}자"
+ $" · LMP {extra.Lmp(owner2.Value).Length}자"
+ $" · BST {new VitalStore(conn).OneBst(owner2.Value, "EmdDte ASC, EmdTime ASC").Length}자")
+ $" · 혈액형 {extra.BloodType(extraOwner).Length}자";
lines.Add($" 부가정보: {shapes} (값은 기록하지 않는다)"); lines.Add($" 부가정보: {shapes} (값은 기록하지 않는다)");
Check("㉛ 부가정보·내원이력 조회가 예외 없이 돈다", true, ""); Check("㉛ 부가정보·내원이력 조회가 예외 없이 돈다", true, "");
@@ -161,6 +161,25 @@ public static class PatientIdentity
: new VitalStore(connection).One(context.ComNum, expr, pick, notNull, order); : new VitalStore(connection).One(context.ComNum, expr, pick, notNull, order);
} }
/// <summary>BST 공급자 — 표가 달라(E_EmdInf_BST) 바이탈과 분리</summary>
public static Func<string, string> BstSourceFor(PatientContext context)
{
var connection = ConfigService.Current.ConnectionString;
return order => connection.Length == 0
? string.Empty
: new VitalStore(connection).OneBst(context.ComNum, order);
}
/// <summary>차트 전체 기준 바이탈 공급자 — BMI·표준·조정체중이 쓴다</summary>
public static Func<string, string, string[], string, string> ChartVitalSourceFor(PatientContext context)
{
var connection = ConfigService.Current.ConnectionString;
var chtNum = PatientContext.Value(context.ComInf, "ComChtNum");
return (expr, pick, notNull, order) => connection.Length == 0 || chtNum.Length == 0
? string.Empty
: new VitalStore(connection).OneByChart(chtNum, expr, pick, notNull, order);
}
/// <summary>병리판독의·진단검사의 공급자 — 환자와 무관하지만 환자 태그로 노출된다</summary> /// <summary>병리판독의·진단검사의 공급자 — 환자와 무관하지만 환자 태그로 노출된다</summary>
public static Func<IReadOnlyDictionary<string, string>> PathologyDoctorSource() public static Func<IReadOnlyDictionary<string, string>> PathologyDoctorSource()
{ {
@@ -75,7 +75,8 @@ public static class PatientSession
PatientIdentity.VitalSourceFor(picked), PatientIdentity.VitalSourceFor(picked),
PatientIdentity.ScheduledSurgerySourceFor(picked), PatientIdentity.ScheduledSurgerySourceFor(picked),
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(), PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(),
PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource()) PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource(),
PatientIdentity.BstSourceFor(picked), PatientIdentity.ChartVitalSourceFor(picked))
: session; : session;
return (tags, new MDataTableRunner(document, Variables())); return (tags, new MDataTableRunner(document, Variables()));
} }
@@ -239,6 +239,12 @@ public sealed class PatientTagResolver : ITagValueResolver
private readonly Dictionary<string, TagValue> deptAtCache = new(StringComparer.Ordinal); private readonly Dictionary<string, TagValue> deptAtCache = new(StringComparer.Ordinal);
/// <summary>BST 공급자(정렬 → 값) — 표가 달라 바이탈 소스와 분리</summary>
private readonly Func<string, string>? bstSource;
/// <summary>차트 전체 기준 바이탈 공급자 — BMI·표준·조정체중이 쓴다</summary>
private readonly Func<string, string, string[], string, string>? chartVitalSource;
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new(); private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
public PatientTagResolver(PatientContext context, ITagValueResolver next, public PatientTagResolver(PatientContext context, ITagValueResolver next,
@@ -255,7 +261,9 @@ public sealed class PatientTagResolver : ITagValueResolver
Func<IReadOnlyDictionary<string, string>>? pathologyDoctorSource = null, Func<IReadOnlyDictionary<string, string>>? pathologyDoctorSource = null,
Func<IReadOnlyDictionary<string, string>>? labDoctorSource = null, Func<IReadOnlyDictionary<string, string>>? labDoctorSource = null,
Func<PatientExtraStore?>? extraStoreSource = null, Func<PatientExtraStore?>? extraStoreSource = null,
Func<PatientContextStore?>? contextStoreSource = null) Func<PatientContextStore?>? contextStoreSource = null,
Func<string, string>? bstSource = null,
Func<string, string, string[], string, string>? chartVitalSource = null)
{ {
this.context = context; this.context = context;
this.next = next; this.next = next;
@@ -274,6 +282,8 @@ public sealed class PatientTagResolver : ITagValueResolver
this.labDoctorSource = labDoctorSource; this.labDoctorSource = labDoctorSource;
this.extraStoreSource = extraStoreSource; this.extraStoreSource = extraStoreSource;
this.contextStoreSource = contextStoreSource; this.contextStoreSource = contextStoreSource;
this.bstSource = bstSource;
this.chartVitalSource = chartVitalSource;
} }
#endregion #endregion
@@ -333,6 +343,10 @@ public sealed class PatientTagResolver : ITagValueResolver
{ {
return deptAtValue; return deptAtValue;
} }
if (FromBodyDerived(tag) is { } bodyValue)
{
return bodyValue;
}
if (Computed.TryGetValue(tag, out var compute)) if (Computed.TryGetValue(tag, out var compute))
{ {
var made = compute(context); var made = compute(context);
@@ -937,6 +951,147 @@ public sealed class PatientTagResolver : ITagValueResolver
: new TagValue(false, "수술일자 값이 형식에 맞지 않습니다"); : new TagValue(false, "수술일자 값이 형식에 맞지 않습니다");
} }
/// <summary>
/// BST·BMI·체중 파생 6종(bzDataInterface.vb:12042-12420).
///
/// BST 는 바이탈과 표가 다르고(E_EmdInf_BST), BMI·표준체중·조정체중은 내원이 아니라
/// <b>차트 전체</b> 최신 측정을 쓴다(EmrChtNum — 주석 처리된 EmrComNum 이 그 흔적).
/// 비만도만 내원(EmrComNum) 기준이다 — 원문이 그렇다.
///
/// 표준·조정체중의 성별 계수(여 21·남 22)는 주민번호에서 나온다. 성별을 못 정하면
/// 레거시는 계수 0 으로 계산해 <b>"0" 이 종이에 찍힌다</b> — 그대로 옮겼다.
/// 비만도는 그 경우 0 나눗셈이라 레거시도 정상 값을 못 만든다 — 사유로 말한다.
/// </summary>
private TagValue? FromBodyDerived(string tag)
{
if (tag is not ("OCM_BST" or "OCM_BST_LAST" or "OCM_BMI"
or "OCM_표준체중_LAST" or "OCM_조정체중_LAST" or "OCM_비만도"))
{
return null;
}
if (extraCache.TryGetValue(tag, out var cached))
{
return cached;
}
var value = Compute();
extraCache[tag] = value;
return value;
TagValue Compute()
{
try
{
switch (tag)
{
case "OCM_BST":
case "OCM_BST_LAST":
{
if (bstSource is null)
{
return new TagValue(false, "BST 조회가 연결되지 않았습니다");
}
var bst = bstSource(tag == "OCM_BST"
? "EmdDte ASC, EmdTime ASC" : "EmdDte DESC, EmdTime DESC");
return bst.Length > 0
? new TagValue(true, bst)
: new TagValue(false, "이 내원에 BST 기록(E_EmdInf_BST)이 없습니다");
}
case "OCM_비만도":
{
// 내원 기준(EmrComNum) — 키·체중을 따로 최신으로 집는다(:12333-12420)
if (vitalSource is null)
{
return new TagValue(false, "바이탈 조회가 연결되지 않았습니다");
}
var h = Number(vitalSource("EmdHeight", "EmdHeight",
new[] { "EmdHeight" }, "EmdDte DESC, EmdTime DESC"));
var w = Number(vitalSource("EmdWeight", "EmdWeight",
new[] { "EmdWeight" }, "EmdDte DESC, EmdTime DESC"));
if (h is null || w is null)
{
return new TagValue(false, "이 내원에 키·체중 측정이 없습니다");
}
var std = StandardWeight(h.Value);
if (std is null or 0)
{
return new TagValue(false, "성별을 판정할 수 없어 표준체중을 만들 수 없습니다");
}
var pct = Math.Round(w.Value / Math.Round(std.Value, 0) * 100, 1);
return new TagValue(true, pct.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
default:
{
// 차트 전체 최신(EmrChtNum) — BMI·표준체중·조정체중(:12159-12333)
if (chartVitalSource is null)
{
return new TagValue(false, "차트 바이탈 조회가 연결되지 않았습니다");
}
if (tag == "OCM_표준체중_LAST")
{
var h = Number(chartVitalSource("EmdHeight", "EmdHeight",
new[] { "EmdHeight" }, "EmdDte DESC, EmdTime DESC"));
if (h is null)
{
return new TagValue(false, "이 차트에 키 측정이 없습니다");
}
// 성별 불명이면 계수 0 → "0"(레거시 Select Case 의 빈 Case "")
var std = StandardWeight(h.Value) ?? 0;
return new TagValue(true, Math.Round(std, 1)
.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
if (tag == "OCM_조정체중_LAST")
{
// 키와 체중이 <b>같은 행</b>에서 나온다(:12291-12303) — 결합식으로 한 번에
var pair = chartVitalSource("EmdHeight || '/' || EmdWeight AS HW", "HW",
new[] { "EmdWeight", "EmdHeight" }, "EmdDte DESC, EmdTime DESC");
var parts = pair.Split('/');
var h = parts.Length == 2 ? Number(parts[0]) : null;
var w = parts.Length == 2 ? Number(parts[1]) : null;
if (h is null || w is null)
{
return new TagValue(false, "키와 체중이 함께 있는 측정이 없습니다");
}
var std = StandardWeight(h.Value) ?? 0;
var abw = std + (w.Value - std) * 0.25;
return new TagValue(true, Math.Round(abw, 1)
.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
// OCM_BMI — 체중과 키를 따로 최신으로(:12168-12216), 반올림 2자리
var weight = Number(chartVitalSource("EmdWeight", "EmdWeight",
new[] { "EmdWeight" }, "EmdDte DESC, EmdTime DESC"));
var height = Number(chartVitalSource("EmdHeight", "EmdHeight",
new[] { "EmdHeight" }, "EmdDte DESC, EmdTime DESC"));
if (weight is null || height is null || height.Value == 0)
{
return new TagValue(false, "이 차트에 키·체중 측정이 없습니다");
}
var meters = height.Value / 100;
var bmi = Math.Round(weight.Value / (meters * meters), 2);
return new TagValue(true, bmi.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
}
}
catch (Exception ex)
{
return new TagValue(false, $"측정치 조회에 실패했습니다: {ex.GetType().Name}");
}
}
// 표준체중 = (키/100)² × (여 21 / 남 22) — 성별 불명이면 null(:12275-12283)
double? StandardWeight(double heightCm)
=> ResidentNumber.SexOf(residentNumber) switch
{
"F" => heightCm / 100 * (heightCm / 100) * 21,
"M" => heightCm / 100 * (heightCm / 100) * 22,
_ => null,
};
static double? Number(string? value)
=> double.TryParse((value ?? string.Empty).Trim(),
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out var n) ? n : null;
}
/// <summary> /// <summary>
/// 입원과·퇴원과 4종(bzDataInterface.vb:6009-6318) — 문맥의 적용일시와 <b>다른 시점</b>의 /// 입원과·퇴원과 4종(bzDataInterface.vb:6009-6318) — 문맥의 적용일시와 <b>다른 시점</b>의
/// 진료과를 묻는다. 입원과는 <b>접수 시점</b>(전과했으면 처음 과), 퇴원과는 /// 진료과를 묻는다. 입원과는 <b>접수 시점</b>(전과했으면 처음 과), 퇴원과는
@@ -1195,7 +1350,7 @@ public sealed class PatientTagResolver : ITagValueResolver
or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처" or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처"
or "OCM_FollowUp" or "OCM_외출외박신청일" or "OCM_외출외박종료일" or "OCM_FollowUp" or "OCM_외출외박신청일" or "OCM_외출외박종료일"
or "OCM_최초내원일" or "OCM_초진일_발병일" or "OCM_신환_초진일" or "OCM_최초내원일" or "OCM_초진일_발병일" or "OCM_신환_초진일"
or "OCM_입원일자_낮병동")) or "OCM_입원일자_낮병동" or "OCM_혈액형" or "OCM_LMP"))
{ {
return null; return null;
} }
@@ -1292,6 +1447,15 @@ public sealed class PatientTagResolver : ITagValueResolver
? new TagValue(true, Hyphenate(day[..8])) ? new TagValue(true, Hyphenate(day[..8]))
: new TagValue(false, "낮병동 신청(P_DcpInf) 구간이 이 내원을 포함하지 않습니다"); : new TagValue(false, "낮병동 신청(P_DcpInf) 구간이 이 내원을 포함하지 않습니다");
} }
case "OCM_혈액형":
return Wrap(store.BloodType(chtNum), "혈액형 판정(S_BlpInf) 행이 없습니다");
case "OCM_LMP":
{
var comNum = PatientContext.Value(context.ComInf, "ComNum");
return decimal.TryParse(comNum, out var m)
? Wrap(store.Lmp(m), "임신 정보(O_PrgInf)에 최종 월경일이 없습니다")
: new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
case "OCM_FollowUp": case "OCM_FollowUp":
{ {
// 지금보다 뒤의 내원이 없으면 레거시도 "" 다(:1055) — 사유로 말한다 // 지금보다 뒤의 내원이 없으면 레거시도 "" 다(:1055) — 사유로 말한다
@@ -1453,6 +1617,8 @@ public sealed class PatientTagResolver : ITagValueResolver
"OCM_진료과_그룹코드", "OCM_진료과_영어", "OCM_진료과대외명칭", "OCM_진료과약어명칭", "OCM_진료과_그룹코드", "OCM_진료과_영어", "OCM_진료과대외명칭", "OCM_진료과약어명칭",
"OCM_입원과", "OCM_입원과_한글명칭", "OCM_퇴원과", "OCM_퇴원과_한글명칭", "OCM_입원과", "OCM_입원과_한글명칭", "OCM_퇴원과", "OCM_퇴원과_한글명칭",
"OCM_입원의사", "OCM_퇴원의사", "OCM_협진과", "OCM_협진과_협진의", "OCM_입원의사", "OCM_퇴원의사", "OCM_협진과", "OCM_협진과_협진의",
"OCM_혈액형", "OCM_LMP", "OCM_BST", "OCM_BST_LAST",
"OCM_BMI", "OCM_표준체중_LAST", "OCM_조정체중_LAST", "OCM_비만도",
"OCM_입원시간", "OCM_입원일시", "OCM_입원일시_영문", "OCM_입실시간", "OCM_입원시간", "OCM_입원일시", "OCM_입원일시_영문", "OCM_입실시간",
"OCM_의사퇴원예고일시", "OCM_퇴원예정일시", "OCM_퇴원시간", "OCM_퇴원일시", "OCM_의사퇴원예고일시", "OCM_퇴원예정일시", "OCM_퇴원시간", "OCM_퇴원일시",
"OCM_퇴원예고일시", "OCM_재원일수", "OCM_퇴원예고재원일수", "OCM_입원일자_낮병동", "OCM_퇴원예고일시", "OCM_재원일수", "OCM_퇴원예고재원일수", "OCM_입원일자_낮병동",
@@ -232,7 +232,8 @@ public partial class PreviewWindow : Window
PatientIdentity.VitalSourceFor(picked), PatientIdentity.VitalSourceFor(picked),
PatientIdentity.ScheduledSurgerySourceFor(picked), PatientIdentity.ScheduledSurgerySourceFor(picked),
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(), PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(),
PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource()); PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource(),
PatientIdentity.BstSourceFor(picked), PatientIdentity.ChartVitalSourceFor(picked));
// 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아 // 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다. // 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
fields = new MDataTableRunner(designer.Document, fields = new MDataTableRunner(designer.Document,