환자 부가정보 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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
adb3572e40
commit
4642439ffd
@@ -178,6 +178,13 @@ public static class PatientIdentity
|
||||
: new PatientContextStore(connection).LabDoctor();
|
||||
}
|
||||
|
||||
/// <summary>환자 부가정보(자기 SQL 7종) 공급자 — 연결이 없으면 null(해석기가 사유로 말한다)</summary>
|
||||
public static Func<PatientExtraStore?> ExtraStoreSource()
|
||||
{
|
||||
var connection = ConfigService.Current.ConnectionString;
|
||||
return () => connection.Length == 0 ? null : new PatientExtraStore(connection);
|
||||
}
|
||||
|
||||
/// <summary>수술 일정(S_OprInf) 공급자 — 최신순 여부별 1회</summary>
|
||||
public static Func<bool, IReadOnlyDictionary<string, string>> ScheduledSurgerySourceFor(PatientContext context)
|
||||
{
|
||||
|
||||
@@ -74,7 +74,8 @@ public static class PatientSession
|
||||
PatientIdentity.ReviewDiagnosisSourceFor(picked),
|
||||
PatientIdentity.VitalSourceFor(picked),
|
||||
PatientIdentity.ScheduledSurgerySourceFor(picked),
|
||||
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource())
|
||||
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(),
|
||||
PatientIdentity.ExtraStoreSource())
|
||||
: session;
|
||||
return (tags, new MDataTableRunner(document, Variables()));
|
||||
}
|
||||
|
||||
@@ -220,6 +220,11 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
|
||||
private readonly Dictionary<string, IReadOnlyDictionary<string, string>> specialDoctorCache = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>환자 부가정보(자기 SQL 7종) 공급자 — 태그가 물을 때 만든다</summary>
|
||||
private readonly Func<PatientExtraStore?>? extraStoreSource;
|
||||
|
||||
private readonly Dictionary<string, TagValue> extraCache = new(StringComparer.Ordinal);
|
||||
|
||||
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
|
||||
|
||||
public PatientTagResolver(PatientContext context, ITagValueResolver next,
|
||||
@@ -234,7 +239,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
Func<string, string, string[], string, string>? vitalSource = null,
|
||||
Func<bool, IReadOnlyDictionary<string, string>>? scheduledSurgerySource = null,
|
||||
Func<IReadOnlyDictionary<string, string>>? pathologyDoctorSource = null,
|
||||
Func<IReadOnlyDictionary<string, string>>? labDoctorSource = null)
|
||||
Func<IReadOnlyDictionary<string, string>>? labDoctorSource = null,
|
||||
Func<PatientExtraStore?>? extraStoreSource = null)
|
||||
{
|
||||
this.context = context;
|
||||
this.next = next;
|
||||
@@ -251,6 +257,7 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
this.scheduledSurgerySource = scheduledSurgerySource;
|
||||
this.pathologyDoctorSource = pathologyDoctorSource;
|
||||
this.labDoctorSource = labDoctorSource;
|
||||
this.extraStoreSource = extraStoreSource;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -298,6 +305,10 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
{
|
||||
return specialValue;
|
||||
}
|
||||
if (FromExtra(tag) is { } extraValue)
|
||||
{
|
||||
return extraValue;
|
||||
}
|
||||
if (Computed.TryGetValue(tag, out var compute))
|
||||
{
|
||||
var made = compute(context);
|
||||
@@ -874,6 +885,111 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
: new TagValue(false, "수술일자 값이 형식에 맞지 않습니다");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 환자 부가정보 7종 — 태그별 자기 SQL(<see cref="PatientExtraStore"/>).
|
||||
/// 전부 지연 + 태그별 캐시다: 이 태그가 없는 서식이 대부분이라 미리 당기지 않고,
|
||||
/// 같은 태그가 한 장에 여러 번 있어도 왕복은 한 번이다.
|
||||
/// </summary>
|
||||
private TagValue? FromExtra(string tag)
|
||||
{
|
||||
if (tag is not ("PAT_실제생년월일" or "PAT_국적" or "PAT_장애등급" or "PAT_건보세대주명"
|
||||
or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (extraStoreSource is null)
|
||||
{
|
||||
return new TagValue(false, "부가정보 조회가 연결되지 않았습니다");
|
||||
}
|
||||
if (extraCache.TryGetValue(tag, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
var chtNum = PatientContext.Value(context.ComInf, "ComChtNum");
|
||||
var value = Compute();
|
||||
extraCache[tag] = value;
|
||||
return value;
|
||||
|
||||
TagValue Compute()
|
||||
{
|
||||
if (chtNum.Length == 0)
|
||||
{
|
||||
return new TagValue(false, "이 내원에 차트번호가 없습니다");
|
||||
}
|
||||
try
|
||||
{
|
||||
var store = extraStoreSource();
|
||||
if (store is null)
|
||||
{
|
||||
return new TagValue(false, "부가정보 조회가 연결되지 않았습니다");
|
||||
}
|
||||
switch (tag)
|
||||
{
|
||||
case "PAT_실제생년월일":
|
||||
{
|
||||
// 레거시(:251-269)는 8자리이고 연·월·일 어느 조각도 "0000"/"00" 이
|
||||
// 아닐 때만 돌려준다 — YYYYMMDD 원문 그대로, 하이픈을 붙이지 않는다.
|
||||
var day = store.ActualBirthDate(chtNum);
|
||||
if (day.Length == 0)
|
||||
{
|
||||
return new TagValue(false, "부가정보(P_PatEtcInf)에 실제생년월일이 없습니다");
|
||||
}
|
||||
return day.Length == 8 && day[..4] != "0000" && day[4..6] != "00" && day[6..8] != "00"
|
||||
? new TagValue(true, day)
|
||||
: new TagValue(false, $"실제생년월일이 온전한 날짜가 아닙니다({day.Length}자)");
|
||||
}
|
||||
case "PAT_국적":
|
||||
return Wrap(store.Nationality(chtNum), "국적 코드가 없거나 코드표(NATCOD)에 이름이 없습니다");
|
||||
case "PAT_장애등급":
|
||||
{
|
||||
// 레거시(:721)는 PdsoGrd 의 <b>두 번째 글자</b>만 뽑는다(Substring(1,1)).
|
||||
// 두 글자가 안 되면 레거시는 예외→"" 였다 — 여기서는 사유로 말한다.
|
||||
var grade = store.DisabilityGrade(chtNum);
|
||||
if (grade.Length == 0)
|
||||
{
|
||||
return new TagValue(false, "장애 등록(P_pdsoInf, PdsoFlg='E') 행이 없습니다");
|
||||
}
|
||||
return grade.Length >= 2
|
||||
? new TagValue(true, grade[1..2])
|
||||
: new TagValue(false, $"장애등급 값이 두 글자가 안 됩니다({grade.Length}자)");
|
||||
}
|
||||
case "PAT_건보세대주명":
|
||||
{
|
||||
var comNum = PatientContext.Value(context.ComInf, "ComNum");
|
||||
return decimal.TryParse(comNum, out var m)
|
||||
? Wrap(store.HouseholderName(chtNum, m), "건보(PisInsCod='1') 세대주 행이 없습니다")
|
||||
: new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
|
||||
}
|
||||
case "PAT_건보_급여_세대주명":
|
||||
{
|
||||
var insSeq = PatientContext.Value(context.CoiInf, "CoiInsSeq");
|
||||
var insCod = PatientContext.Value(context.CoiInf, "CoiInsCod");
|
||||
return insSeq.Length == 0 && insCod.Length == 0
|
||||
? new TagValue(false, "이 내원에 보험 자격(P_CoiInf) 행이 없습니다")
|
||||
: Wrap(store.HouseholderNameByInsurance(chtNum, insSeq, insCod),
|
||||
"이 자격의 세대주 행이 없습니다");
|
||||
}
|
||||
case "PAT_협력업체":
|
||||
{
|
||||
var comNum = PatientContext.Value(context.ComInf, "ComNum");
|
||||
return decimal.TryParse(comNum, out var m)
|
||||
? Wrap(store.Cooperator(m), "협력업체 코드가 없거나 코드표에 이름이 없습니다")
|
||||
: new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
|
||||
}
|
||||
default:
|
||||
return Wrap(store.GuardianPhone(chtNum), "부가정보에 보호자연락처가 없습니다");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TagValue(false, $"부가정보 조회에 실패했습니다: {ex.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
static TagValue Wrap(string value, string reasonWhenEmpty)
|
||||
=> value.Length > 0 ? new TagValue(true, value) : new TagValue(false, reasonWhenEmpty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 병리판독의·진단검사의 4종 — 환자와 무관한 원내 의사 조회지만 레거시가 환자 태그로 노출한다.
|
||||
/// 이름과 번호가 <b>같은 행</b>에서 나온다(레거시는 태그마다 따로 조회해 다른 사람이 될 수 있었다).
|
||||
@@ -965,6 +1081,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
{
|
||||
"PAT_주민번호", "PAT_주민번호_Dash", "PAT_성별", "PAT_한글성별",
|
||||
"PAT_성별_남", "PAT_성별_여",
|
||||
"PAT_실제생년월일", "PAT_국적", "PAT_장애등급", "PAT_건보세대주명",
|
||||
"PAT_건보_급여_세대주명", "PAT_협력업체", "PAT_보호자연락처",
|
||||
"PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소",
|
||||
"OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처",
|
||||
"OCM_진료과", "ETC_요양기관명칭_병원명",
|
||||
|
||||
Reference in New Issue
Block a user