feat(mapping): 实现健康档案映射基线(T-202)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenerateCheckID(sourceSystem, dataType, sourceRecordID, version string) string {
|
||||
parts := []string{sourceSystem, dataType, sourceRecordID}
|
||||
if version != "" {
|
||||
parts = append(parts, version)
|
||||
}
|
||||
sum := sha1.Sum([]byte(strings.Join(parts, "|")))
|
||||
return hex.EncodeToString(sum[:])[:20]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type ValidationError struct {
|
||||
DictName string
|
||||
Direction Direction
|
||||
Code string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package mapping
|
||||
|
||||
type PHISHealthRecord struct {
|
||||
SourceSystem string
|
||||
SourceRecordID string
|
||||
UpdatedAt string
|
||||
IDCard string
|
||||
PersonName string
|
||||
SexCode string
|
||||
Birthday string
|
||||
MobileNumber string
|
||||
RegionCode string
|
||||
ManaDoctorID string
|
||||
ManaUnitID string
|
||||
NationCode string
|
||||
EducationCode string
|
||||
WorkCode string
|
||||
MaritalStatusCode string
|
||||
BloodTypeCode string
|
||||
RhBloodCode string
|
||||
InsuranceCode string
|
||||
}
|
||||
|
||||
type MappedHealthRecord struct {
|
||||
CheckID string
|
||||
IDCard string
|
||||
PersonName string
|
||||
SexCode string
|
||||
Birthday string
|
||||
MobileNumber string
|
||||
RegionCode string
|
||||
ManaDoctorID string
|
||||
ManaUnitID string
|
||||
NationCode string
|
||||
EducationCode string
|
||||
WorkCode string
|
||||
MaritalStatusCode string
|
||||
BloodTypeCode string
|
||||
RhBloodCode string
|
||||
InsuranceCode string
|
||||
}
|
||||
|
||||
type DictSnapshot struct {
|
||||
Sex Dict
|
||||
Nation Dict
|
||||
Education Dict
|
||||
Work Dict
|
||||
MaritalStatus Dict
|
||||
BloodType Dict
|
||||
RhBlood Dict
|
||||
Insurance Dict
|
||||
}
|
||||
|
||||
type MapContext struct {
|
||||
Dicts DictSnapshot
|
||||
}
|
||||
|
||||
func DefaultDictSnapshot() DictSnapshot {
|
||||
return DictSnapshot{
|
||||
Sex: SexDict,
|
||||
Nation: NationDict,
|
||||
Education: EducationDict,
|
||||
Work: WorkDict,
|
||||
MaritalStatus: MaritalStatusDict,
|
||||
BloodType: BloodTypeDict,
|
||||
RhBlood: RhBloodDict,
|
||||
Insurance: InsuranceDict,
|
||||
}
|
||||
}
|
||||
|
||||
func MapHealthRecord(src PHISHealthRecord, ctx MapContext) (MappedHealthRecord, []ValidationError) {
|
||||
if ctx.Dicts.Sex.Name == "" {
|
||||
ctx.Dicts = DefaultDictSnapshot()
|
||||
}
|
||||
var errs []ValidationError
|
||||
for _, field := range []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{name: "idCard", value: src.IDCard},
|
||||
{name: "personName", value: src.PersonName},
|
||||
{name: "sexCode", value: src.SexCode},
|
||||
{name: "birthday", value: src.Birthday},
|
||||
{name: "mobileNumber", value: src.MobileNumber},
|
||||
{name: "regionCode", value: src.RegionCode},
|
||||
{name: "manaDoctorId", value: src.ManaDoctorID},
|
||||
{name: "manaUnitId", value: src.ManaUnitID},
|
||||
} {
|
||||
if field.value == "" {
|
||||
errs = append(errs, ValidationError{Field: field.name, Reason: "required"})
|
||||
}
|
||||
}
|
||||
|
||||
mapped := MappedHealthRecord{
|
||||
CheckID: GenerateCheckID(src.SourceSystem, "JKDA", src.SourceRecordID, src.UpdatedAt),
|
||||
IDCard: src.IDCard,
|
||||
PersonName: src.PersonName,
|
||||
Birthday: src.Birthday,
|
||||
MobileNumber: src.MobileNumber,
|
||||
RegionCode: src.RegionCode,
|
||||
ManaDoctorID: src.ManaDoctorID,
|
||||
ManaUnitID: src.ManaUnitID,
|
||||
}
|
||||
mapped.SexCode = mapCode(ctx.Dicts.Sex, src.SexCode, &errs)
|
||||
mapped.NationCode = mapCode(ctx.Dicts.Nation, src.NationCode, &errs)
|
||||
mapped.EducationCode = mapCode(ctx.Dicts.Education, src.EducationCode, &errs)
|
||||
mapped.WorkCode = mapCode(ctx.Dicts.Work, src.WorkCode, &errs)
|
||||
mapped.MaritalStatusCode = mapCode(ctx.Dicts.MaritalStatus, src.MaritalStatusCode, &errs)
|
||||
mapped.BloodTypeCode = mapCode(ctx.Dicts.BloodType, src.BloodTypeCode, &errs)
|
||||
mapped.RhBloodCode = mapCode(ctx.Dicts.RhBlood, src.RhBloodCode, &errs)
|
||||
mapped.InsuranceCode = mapCode(ctx.Dicts.Insurance, src.InsuranceCode, &errs)
|
||||
return mapped, errs
|
||||
}
|
||||
|
||||
func mapCode(dict Dict, code string, errs *[]ValidationError) string {
|
||||
if code == "" {
|
||||
return ""
|
||||
}
|
||||
mapped, err := dict.PHISToOSI(code)
|
||||
if err != nil {
|
||||
if validationErr, ok := err.(ValidationError); ok {
|
||||
*errs = append(*errs, validationErr)
|
||||
return ""
|
||||
}
|
||||
*errs = append(*errs, ValidationError{Field: dict.Field, Code: code, Reason: err.Error()})
|
||||
return ""
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package mapping
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMapHealthRecordMapsDirectFieldsDictsAndCheckID(t *testing.T) {
|
||||
src := validPHISHealthRecord()
|
||||
got, errs := MapHealthRecord(src, MapContext{Dicts: DefaultDictSnapshot()})
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("errors = %#v", errs)
|
||||
}
|
||||
if got.IDCard != src.IDCard || got.PersonName != src.PersonName || got.Birthday != src.Birthday || got.MobileNumber != src.MobileNumber {
|
||||
t.Fatalf("direct fields not mapped: %#v", got)
|
||||
}
|
||||
if got.SexCode != "1" || got.NationCode != "01" || got.EducationCode != "31" || got.WorkCode != "Y" {
|
||||
t.Fatalf("dict fields not mapped: %#v", got)
|
||||
}
|
||||
if got.MaritalStatusCode != "20" || got.BloodTypeCode != "4" || got.RhBloodCode != "1" || got.InsuranceCode != "01" {
|
||||
t.Fatalf("dict fields not mapped: %#v", got)
|
||||
}
|
||||
if got.RegionCode != src.RegionCode || got.ManaDoctorID != src.ManaDoctorID || got.ManaUnitID != src.ManaUnitID {
|
||||
t.Fatalf("management fields not mapped: %#v", got)
|
||||
}
|
||||
if got.CheckID == "" || len(got.CheckID) != 20 {
|
||||
t.Fatalf("checkId = %q", got.CheckID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapHealthRecordReportsRequiredFieldErrors(t *testing.T) {
|
||||
src := validPHISHealthRecord()
|
||||
src.IDCard = ""
|
||||
src.PersonName = ""
|
||||
src.ManaUnitID = ""
|
||||
|
||||
_, errs := MapHealthRecord(src, MapContext{Dicts: DefaultDictSnapshot()})
|
||||
if len(errs) != 3 {
|
||||
t.Fatalf("errors = %#v", errs)
|
||||
}
|
||||
assertValidationField(t, errs, "idCard", "required")
|
||||
assertValidationField(t, errs, "personName", "required")
|
||||
assertValidationField(t, errs, "manaUnitId", "required")
|
||||
}
|
||||
|
||||
func TestMapHealthRecordReportsDictValidationErrors(t *testing.T) {
|
||||
src := validPHISHealthRecord()
|
||||
src.SexCode = "bad"
|
||||
|
||||
_, errs := MapHealthRecord(src, MapContext{Dicts: DefaultDictSnapshot()})
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("errors = %#v", errs)
|
||||
}
|
||||
if errs[0].Field != "sexCode" || errs[0].Code != "bad" || errs[0].Direction != DirectionPHISToOSI {
|
||||
t.Fatalf("error = %#v", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapHealthRecordUsesInjectedDictSnapshot(t *testing.T) {
|
||||
src := validPHISHealthRecord()
|
||||
src.SexCode = "M"
|
||||
dicts := DefaultDictSnapshot()
|
||||
dicts.Sex = Dict{Name: "sex", Field: "sexCode", Entries: []Entry{{PHIS: "M", OSI: "1", Name: "男"}}}
|
||||
|
||||
got, errs := MapHealthRecord(src, MapContext{Dicts: dicts})
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("errors = %#v", errs)
|
||||
}
|
||||
if got.SexCode != "1" {
|
||||
t.Fatalf("SexCode = %q", got.SexCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateCheckIDIsDeterministicAndVersionSensitive(t *testing.T) {
|
||||
first := GenerateCheckID("PHIS", "JKDA", "record-001", "20260707010101")
|
||||
second := GenerateCheckID("PHIS", "JKDA", "record-001", "20260707010101")
|
||||
third := GenerateCheckID("PHIS", "JKDA", "record-001", "20260708010101")
|
||||
if first != second {
|
||||
t.Fatalf("checkId not deterministic: %q != %q", first, second)
|
||||
}
|
||||
if len(first) != 20 {
|
||||
t.Fatalf("checkId length = %d, want 20", len(first))
|
||||
}
|
||||
if first == third {
|
||||
t.Fatalf("version change did not affect checkId: %q", first)
|
||||
}
|
||||
}
|
||||
|
||||
func validPHISHealthRecord() PHISHealthRecord {
|
||||
return PHISHealthRecord{
|
||||
SourceSystem: "PHIS",
|
||||
SourceRecordID: "record-001",
|
||||
UpdatedAt: "20260707010101",
|
||||
IDCard: "440000********1234",
|
||||
PersonName: "测试人员",
|
||||
SexCode: "1",
|
||||
Birthday: "1970-01-01",
|
||||
MobileNumber: "13900000000",
|
||||
RegionCode: "441625000000",
|
||||
ManaDoctorID: "D001",
|
||||
ManaUnitID: "123456789",
|
||||
NationCode: "01",
|
||||
EducationCode: "31",
|
||||
WorkCode: "Y",
|
||||
MaritalStatusCode: "20",
|
||||
BloodTypeCode: "4",
|
||||
RhBloodCode: "1",
|
||||
InsuranceCode: "01",
|
||||
}
|
||||
}
|
||||
|
||||
func assertValidationField(t *testing.T, errs []ValidationError, field string, reason string) {
|
||||
t.Helper()
|
||||
for _, err := range errs {
|
||||
if err.Field == field && err.Reason == reason {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("missing validation error field=%s reason=%s in %#v", field, reason, errs)
|
||||
}
|
||||
Reference in New Issue
Block a user