요양기관 12종을 옮긴다 — 왕복 12회를 1회로 접었다. 63종 → 75종

ETC_요양기관명칭_* 계열(bzDataInterface.vb:15656-16709). 12종 전부
<b>같은 조인(M_DepMst⨝M_HspMst)의 다른 컬럼</b>이라는 것을 본문에서 확인했다 —
병원명(이미 이식)·병원명_영문·병원대표자·병원장·병원장귀하·전화번호·팩스번호·
전화_팩스·영문_전화_팩스·주소·주소_영문·사업자등록번호·요양기관기호.

레거시는 태그마다 이 조인을 따로 돌린다(12종 = 왕복 12회).
컬럼 9개를 한 번에 뽑아 행으로 들고, 값 선택은 해석기가 한다.
HospitalName(문자열) → HospitalRow(행) 로 일반화했다.

접미·결합도 본문 그대로다:
  병원장 = 병원명+"장" / 병원장귀하 = +"장 귀하"
  전화_팩스 = "Tel : … FAX : …" / 영문판 = "Tel : +82 - … FAX : +82 - …"
접두·접미가 붙는 태그는 <b>재료 컬럼</b>으로 빈 값을 판정한다 —
병원명이 비면 "장"만 찍히는 것을 값으로 세지 않는다.

실DB 확인: --db-patient ㉔ 가 9열·병원명 6자를 돌려줬다.

## 게이트

- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 75종)
- --db-patient 전건 통과 · --db-render P062 md5 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-19 15:42:26 +09:00
co-authored by Claude Opus 5
parent ef4f36419f
commit 26d9606d1e
6 changed files with 85 additions and 23 deletions
+11 -7
View File
@@ -198,26 +198,30 @@ public sealed class PatientContextStore
} }
/// <summary> /// <summary>
/// 요양기관 — <c>ETC_요양기관명칭_병원명</c>(bzDataInterface.vb:15792-15860)의 조회. /// 요양기관 — <c>ETC_요양기관명칭_*</c> 12종(bzDataInterface.vb:15656-16709)의 공통 조회.
/// <c>M_DepMst ⨝ M_HspMst(DepHspCod=HspCod)</c> 에서 <c>HspInsNam</c> 하나를 뽑아 Trim. /// <c>M_DepMst ⨝ M_HspMst(DepHspCod=HspCod)</c> 에서 병원 컬럼들을 뽑는다.
/// 진료과 코드로 병원을 찾는 구조다 — 다부지(병원 코드가 다른 진료과) 구성 때문으로 보인다. /// 진료과 코드로 병원을 찾는 구조다 — 다부지(병원 코드가 다른 진료과) 구성 때문으로 보인다.
///
/// 레거시는 태그마다 이 조인을 <b>따로</b> 돌린다(12종 = 왕복 12회).
/// 컬럼만 다르므로 한 번에 다 뽑아 행으로 돌려준다 — 값 선택은 해석기가 한다.
/// 컬럼 9개는 전부 각 태그 본문의 SELECT 에서 확인한 것이다.
/// </summary> /// </summary>
public string HospitalName(string? depCod, string? day) public IReadOnlyDictionary<string, string> HospitalRow(string? depCod, string? day)
{ {
var code = (depCod ?? string.Empty).Trim(); var code = (depCod ?? string.Empty).Trim();
var date = (day ?? string.Empty).Trim(); var date = (day ?? string.Empty).Trim();
if (code.Length == 0 || date.Length < 8) if (code.Length == 0 || date.Length < 8)
{ {
return string.Empty; return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
using var connection = new OracleConnection(connectionString); using var connection = new OracleConnection(connectionString);
connection.Open(); connection.Open();
var row = Row(connection, return Row(connection,
"SELECT HspInsNam FROM (SELECT HspInsNam FROM M_DepMst" "SELECT * FROM (SELECT HspInsNam, HspOwnNam, HspTelNum, HspFaxNum,"
+ " HspKorAdr, HspEngNam, HspEngAdr, HspRgnNum, HspInsNum FROM M_DepMst"
+ " INNER JOIN M_HspMst ON DepHspCod = HspCod AND :y BETWEEN HspStrDte AND HspEndDte" + " INNER JOIN M_HspMst ON DepHspCod = HspCod AND :y BETWEEN HspStrDte AND HspEndDte"
+ " WHERE TRIM(DepCod) = :d AND :y BETWEEN DepStrDte AND DepEndDte) WHERE ROWNUM = 1", + " WHERE TRIM(DepCod) = :d AND :y BETWEEN DepStrDte AND DepEndDte) WHERE ROWNUM = 1",
("d", code), ("y", date[..8])); ("d", code), ("y", date[..8]));
return PatientContext.Value(row, "HspInsNam");
} }
/// <summary> /// <summary>
+3 -2
View File
@@ -2200,8 +2200,9 @@ public static class DbSmoke
Check("㉓ 진료과 마스터가 이름을 돌려준다", korNam.Trim().Length > 0, Check("㉓ 진료과 마스터가 이름을 돌려준다", korNam.Trim().Length > 0,
$"코드 '{depCod}' 로 M_DepMst 에서 이름을 못 찾았다"); $"코드 '{depCod}' 로 M_DepMst 에서 이름을 못 찾았다");
var hosp = contexts.HospitalName(depCod, octx.AdpDtm); var hospRow = contexts.HospitalRow(depCod, octx.AdpDtm);
lines.Add($" 요양기관명: {hosp.Length}자"); var hosp = PatientContext.Value(hospRow, "HspInsNam");
lines.Add($" 요양기관: {hospRow.Count}열, 병원명 {hosp.Length}자");
Check("㉔ 요양기관명이 나온다", hosp.Trim().Length > 0, Check("㉔ 요양기관명이 나온다", hosp.Trim().Length > 0,
"M_DepMst⨝M_HspMst 조인이 0행 — 유효기간이나 DepHspCod 를 의심할 것"); "M_DepMst⨝M_HspMst 조인이 0행 — 유효기간이나 DepHspCod 를 의심할 것");
@@ -154,25 +154,26 @@ public static class PatientIdentity
} }
/// <summary> /// <summary>
/// 요양기관 — 못 읽으면 빈 문자열. /// 요양기관 — 못 읽으면 빈 사전.
/// 판정 일자가 진료과와 <b>다르다</b>(bzDataInterface.vb:15815-15825): /// 판정 일자가 진료과와 <b>다르다</b>(bzDataInterface.vb:15815-15825):
/// 입원·퇴원(ILV)은 퇴원일시, 입원 중은 지금, 외래는 접수일시 — 문맥의 적용일시가 그 판정과 같다. /// 입원·퇴원(ILV)은 퇴원일시, 입원 중은 지금, 외래는 접수일시 — 문맥의 적용일시가 그 판정과 같다.
/// </summary> /// </summary>
public static string HospitalNameOf(PatientContext context) public static IReadOnlyDictionary<string, string> HospitalRowOf(PatientContext context)
{ {
var connection = ConfigService.Current.ConnectionString; var connection = ConfigService.Current.ConnectionString;
var empty = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (connection.Length == 0) if (connection.Length == 0)
{ {
return string.Empty; return empty;
} }
try try
{ {
return new PatientContextStore(connection).HospitalName( return new PatientContextStore(connection).HospitalRow(
PatientContext.Value(context.CodInf, "CodDepCod"), context.AdpDtm); PatientContext.Value(context.CodInf, "CodDepCod"), context.AdpDtm);
} }
catch catch
{ {
return string.Empty; return empty;
} }
} }
@@ -67,7 +67,7 @@ public static class PatientSession
? new PatientTagResolver(picked, session, ? new PatientTagResolver(picked, session,
PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked), PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked),
PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked), PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked),
PatientIdentity.HospitalNameOf(picked), PatientIdentity.HospitalRowOf(picked),
PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.DiagnosisSourceFor(picked),
PatientIdentity.SurgerySourceFor(picked), PatientIdentity.SurgerySourceFor(picked),
PatientIdentity.ReviewDiagnosisSourceFor(picked)) PatientIdentity.ReviewDiagnosisSourceFor(picked))
@@ -160,8 +160,8 @@ public sealed class PatientTagResolver : ITagValueResolver
/// <summary>이 내원의 진료과 마스터 행(M_DepMst) — 없으면 빈 사전</summary> /// <summary>이 내원의 진료과 마스터 행(M_DepMst) — 없으면 빈 사전</summary>
private readonly IReadOnlyDictionary<string, string> department; private readonly IReadOnlyDictionary<string, string> department;
/// <summary>요양기관명(HspInsNam) — 못 읽었으면 빈 문자열</summary> /// <summary>요양기관 행(M_HspMst 컬럼들) — 없으면 빈 사전</summary>
private readonly string hospitalName; private readonly IReadOnlyDictionary<string, string> hospital;
/// <summary> /// <summary>
/// 상병 행 공급자 — 주/부/전체 별로 <b>물을 때 한 번만</b> 읽는다. /// 상병 행 공급자 — 주/부/전체 별로 <b>물을 때 한 번만</b> 읽는다.
@@ -187,7 +187,8 @@ public sealed class PatientTagResolver : ITagValueResolver
public PatientTagResolver(PatientContext context, ITagValueResolver next, public PatientTagResolver(PatientContext context, ITagValueResolver next,
string residentNumber = "", IReadOnlyDictionary<string, string>? address = null, string residentNumber = "", IReadOnlyDictionary<string, string>? address = null,
IReadOnlyDictionary<string, string>? doctor = null, IReadOnlyDictionary<string, string>? doctor = null,
IReadOnlyDictionary<string, string>? department = null, string hospitalName = "", IReadOnlyDictionary<string, string>? department = null,
IReadOnlyDictionary<string, string>? hospital = null,
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? diagnosisSource = null, Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? diagnosisSource = null,
Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null, Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null,
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource = null) Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource = null)
@@ -198,7 +199,7 @@ public sealed class PatientTagResolver : ITagValueResolver
this.address = address ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); this.address = address ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.doctor = doctor ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); this.doctor = doctor ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.department = department ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); this.department = department ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.hospitalName = hospitalName; this.hospital = hospital ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.diagnosisSource = diagnosisSource; this.diagnosisSource = diagnosisSource;
this.surgerySource = surgerySource; this.surgerySource = surgerySource;
this.reviewSource = reviewSource; this.reviewSource = reviewSource;
@@ -401,10 +402,59 @@ public sealed class PatientTagResolver : ITagValueResolver
? "이 내원의 진료과를 찾지 못했습니다" ? "이 내원의 진료과를 찾지 못했습니다"
: "진료과 마스터에 이름이 비어 있습니다"); : "진료과 마스터에 이름이 비어 있습니다");
} }
// ── 요양기관 12종 — 같은 조인의 다른 컬럼(bzDataInterface.vb:15656-16709) ──
// 접미·결합도 본문 그대로: 병원장 = 병원명+"장", 귀하 = +"장 귀하",
// 전화_팩스 = "Tel : … FAX : …", 영문판은 "+82 - " 접두가 붙는다.
case "ETC_요양기관명칭_병원명": case "ETC_요양기관명칭_병원명":
return hospitalName.Trim().Length > 0 case "ETC_요양기관명칭_병원명_영문":
? new TagValue(true, hospitalName.Trim()) case "ETC_요양기관명칭_병원대표자":
: new TagValue(false, "요양기관명을 찾지 못했습니다(M_DepMst·M_HspMst 조인)"); case "ETC_요양기관명칭_병원장":
case "ETC_요양기관명칭_병원장귀하":
case "ETC_요양기관명칭_병원전화번호":
case "ETC_요양기관명칭_병원팩스번호":
case "ETC_요양기관명칭_병원전화_팩스번호":
case "ETC_요양기관명칭_영문_병원전화_팩스번호":
case "ETC_요양기관명칭_병원주소":
case "ETC_요양기관명칭_병원주소_영문":
case "ETC_요양기관명칭_사업자등록번호":
case "ETC_요양기관기호":
{
if (hospital.Count == 0)
{
return new TagValue(false, "요양기관 정보를 찾지 못했습니다(M_DepMst·M_HspMst 조인)");
}
string Hsp(string column) => hospital.TryGetValue(column, out var v) ? v.Trim() : string.Empty;
var hspValue = tag switch
{
"ETC_요양기관명칭_병원명" => Hsp("HspInsNam"),
"ETC_요양기관명칭_병원명_영문" => Hsp("HspEngNam"),
"ETC_요양기관명칭_병원대표자" => Hsp("HspOwnNam"),
"ETC_요양기관명칭_병원장" => Hsp("HspInsNam") + "장",
"ETC_요양기관명칭_병원장귀하" => Hsp("HspInsNam") + "장 귀하",
"ETC_요양기관명칭_병원전화번호" => Hsp("HspTelNum"),
"ETC_요양기관명칭_병원팩스번호" => Hsp("HspFaxNum"),
"ETC_요양기관명칭_병원전화_팩스번호" =>
$"Tel : {Hsp("HspTelNum")} FAX : {Hsp("HspFaxNum")}",
"ETC_요양기관명칭_영문_병원전화_팩스번호" =>
$"Tel : +82 - {Hsp("HspTelNum")} FAX : +82 - {Hsp("HspFaxNum")}",
"ETC_요양기관명칭_병원주소" => Hsp("HspKorAdr"),
"ETC_요양기관명칭_병원주소_영문" => Hsp("HspEngAdr"),
"ETC_요양기관명칭_사업자등록번호" => Hsp("HspRgnNum"),
_ => Hsp("HspInsNum"),
};
// 병원장·전화_팩스는 접두·접미가 붙어 "장"만 남을 수 있다 — 재료 컬럼으로 빈 값을 판정한다
var material = tag switch
{
"ETC_요양기관명칭_병원장" or "ETC_요양기관명칭_병원장귀하" => Hsp("HspInsNam"),
"ETC_요양기관명칭_병원전화_팩스번호" or "ETC_요양기관명칭_영문_병원전화_팩스번호"
=> Hsp("HspTelNum") + Hsp("HspFaxNum"),
_ => hspValue,
};
return material.Trim().Length > 0
? new TagValue(true, hspValue)
: new TagValue(false, "요양기관 행은 있는데 그 값이 비어 있습니다");
}
// ── User 계열 — 로그인 사용자가 의사면 <b>사용자의</b> 과·이름, 아니면 환자의 것 ── // ── User 계열 — 로그인 사용자가 의사면 <b>사용자의</b> 과·이름, 아니면 환자의 것 ──
// (bzDataInterface.vb:5697-5826). 개원일 조건(HspStrDte>=20220701)은 세션에 없어 // (bzDataInterface.vb:5697-5826). 개원일 조건(HspStrDte>=20220701)은 세션에 없어
@@ -642,6 +692,12 @@ public sealed class PatientTagResolver : ITagValueResolver
"PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소", "PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소",
"OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처", "OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처",
"OCM_진료과", "ETC_요양기관명칭_병원명", "OCM_진료과", "ETC_요양기관명칭_병원명",
"ETC_요양기관명칭_병원명_영문", "ETC_요양기관명칭_병원대표자",
"ETC_요양기관명칭_병원장", "ETC_요양기관명칭_병원장귀하",
"ETC_요양기관명칭_병원전화번호", "ETC_요양기관명칭_병원팩스번호",
"ETC_요양기관명칭_병원전화_팩스번호", "ETC_요양기관명칭_영문_병원전화_팩스번호",
"ETC_요양기관명칭_병원주소", "ETC_요양기관명칭_병원주소_영문",
"ETC_요양기관명칭_사업자등록번호", "ETC_요양기관기호",
"OCM_담당의사_영문", "OCM_담당의사면허번호", "OCM_담당의사전문의번호", "OCM_담당의사_영문", "OCM_담당의사면허번호", "OCM_담당의사전문의번호",
"OCM_User진료과", "OCM_User진료과_한글명칭", "OCM_User진료의사명", "OCM_User진료과", "OCM_User진료과_한글명칭", "OCM_User진료의사명",
"OCM_상병명_가로", "OCM_상병명_세로", "OCM_상병명_영어_가로", "OCM_상병명_영어_세로", "OCM_상병명_가로", "OCM_상병명_세로", "OCM_상병명_영어_가로", "OCM_상병명_영어_세로",
@@ -224,7 +224,7 @@ public partial class PreviewWindow : Window
: new PatientTagResolver(picked, session, : new PatientTagResolver(picked, session,
PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked), PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked),
PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked), PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked),
PatientIdentity.HospitalNameOf(picked), PatientIdentity.HospitalRowOf(picked),
PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.DiagnosisSourceFor(picked),
PatientIdentity.SurgerySourceFor(picked), PatientIdentity.SurgerySourceFor(picked),
PatientIdentity.ReviewDiagnosisSourceFor(picked)); PatientIdentity.ReviewDiagnosisSourceFor(picked));