환자 부가정보 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:
Msystech
2026-08-19 16:45:03 +09:00
co-authored by Claude Fable 5
parent adb3572e40
commit 4642439ffd
6 changed files with 338 additions and 3 deletions
@@ -2314,6 +2314,66 @@ public static class DbSmoke
Check("㉚ 병리·진단검사 의사 조회가 예외 없이 돈다", false,
$"{ex.GetType().Name}: {ex.Message.Split('\n')[0]}");
}
// ㉛ 부가정보 7종(자기 SQL) — P_PatEtcInf 에 행이 있는 차트로 돌려
// 컬럼·조인 이름이 실제 DB 와 맞는지 본다. 0값은 실패가 아니라
// 그 환자에 그 정보가 없는 것이다(태그는 사유로 완결된다).
try
{
var extraOwner = FirstText(conn,
"SELECT TRIM(PatEtcChtNum) FROM P_PatEtcInf WHERE ROWNUM = 1");
// 패딩 조사 — P_pdsoInf 는 VARCHAR2 라 저장 형태를 확정할 수 없다.
// IN (:c, RPAD(:c,10)) 이 실제로 필요한지 근거를 남긴다.
var padded = FirstText(conn,
"SELECT COUNT(*) FROM P_pdsoInf WHERE PdsoChtNum <> TRIM(PdsoChtNum)");
var multiEtc = FirstText(conn,
"SELECT NVL(MAX(cnt),0) FROM (SELECT COUNT(*) cnt FROM P_PatEtcInf"
+ " GROUP BY PatEtcChtNum)");
lines.Add($" [조사] P_pdsoInf 패딩 행={padded},"
+ $" P_PatEtcInf 차트당 최대 행수={multiEtc}");
if (extraOwner.Length == 0)
{
lines.Add("SKIP ㉛ 부가정보 — P_PatEtcInf 가 비어 있어 확인 불가");
}
else
{
var extra = new PatientExtraStore(conn);
var owner2 = FirstComNum(conn, "P_ComInf", "ComNum", "ComNum");
var shapes =
$"실제생년월일 {extra.ActualBirthDate(extraOwner).Length}자"
+ $" · 국적 {extra.Nationality(extraOwner).Length}자"
+ $" · 장애등급 {extra.DisabilityGrade(extraOwner).Length}자"
+ $" · 보호자연락처 {extra.GuardianPhone(extraOwner).Length}자"
+ $" · 급여세대주 {extra.HouseholderNameByInsurance(extraOwner, "0", "1").Length}자"
+ (owner2 is null ? ""
: $" · 건보세대주 {extra.HouseholderName(extraOwner, owner2.Value).Length}자"
+ $" · 협력업체 {extra.Cooperator(owner2.Value).Length}자");
lines.Add($" 부가정보: {shapes} (값은 기록하지 않는다)");
Check("㉛ 부가정보 7종 조회가 예외 없이 돈다", true, "");
// "돌지만 항상 0행"인 매칭 실수(RPAD·조인)를 가른다 —
// 값이 있는 차트를 골라서 물으면 반드시 값이 나와야 한다.
var natOwner = FirstText(conn,
"SELECT TRIM(PatChtNum) FROM P_PatInf"
+ " INNER JOIN M_DtlMst ON DtlCod = PatNatCod AND DtlTblCod = 'NATCOD'"
+ " WHERE ROWNUM = 1");
if (natOwner.Length == 0)
{
lines.Add("SKIP ㉜ 국적 값 — 이 DB 에 국적 코드가 잡힌 환자가 없어 판정 불가");
}
else
{
Check("㉜ 국적 코드가 있는 차트에서는 값이 나온다",
extra.Nationality(natOwner).Length > 0,
"RPAD 매칭이나 NATCOD 조인을 의심할 것");
}
}
}
catch (Exception ex)
{
Check("㉛ 부가정보 7종 조회가 예외 없이 돈다", false,
$"{ex.GetType().Name}: {ex.Message.Split('\n')[0]}");
}
}
}
}
@@ -2336,6 +2396,24 @@ public static class DbSmoke
/// 최근 것을 고른다(오래된 내원은 구간이 이미 닫혀 있어 판정이 덜 대표적이다).
/// <b>표 이름·컬럼 이름은 호출부의 리터럴만 들어온다</b> — 사용자 입력이 닿는 경로가 아니다.
/// </summary>
/// <summary>스칼라 한 칸을 문자열로 — 0행이거나 죽으면 빈 문자열(판정은 호출부가 한다)</summary>
private static string FirstText(string connectionString, string sql)
{
try
{
using var connection = new Oracle.ManagedDataAccess.Client.OracleConnection(connectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = sql;
var value = command.ExecuteScalar();
return value is null or DBNull ? string.Empty : value.ToString()!.Trim();
}
catch
{
return string.Empty;
}
}
private static decimal? FirstComNum(string connectionString, string table, string key, string stamp)
{
try