feat(mapping): 完成 PHIS 健康档案转换(T-212)

This commit is contained in:
ila
2026-07-16 00:53:16 +08:00
parent d385d6dd3b
commit 8a5550057c
20 changed files with 1078 additions and 124 deletions
+106
View File
@@ -0,0 +1,106 @@
package source
import (
"encoding/json"
"fmt"
"strings"
)
type healthRecordEnvelope struct {
Code int `json:"code"`
Message string `json:"msg"`
Compress bool `json:"compress"`
Data HealthRecordTask `json:"data"`
}
// HealthRecordTask is one PHIS health-record upload task. Credentials returned
// under data.doctor are deliberately absent from this model.
type HealthRecordTask struct {
Doctor Doctor `json:"doctor"`
Record HealthRecord `json:"record"`
ArchID string `json:"archId"`
BusinessID string `json:"businessId"`
EMPIID string `json:"empiId"`
PHRID string `json:"phrId"`
CardNo string `json:"cardNo"`
CardType string `json:"cardType"`
}
type Doctor struct {
RealName string `json:"realName"`
DoctorID string `json:"doctorId"`
}
type HealthRecord struct {
HomePlace string `json:"homePlace"`
DeadFlag string `json:"deadFlag"`
IDCard string `json:"idCard"`
MobileNumber string `json:"mobileNumber"`
InputUser string `json:"inputUser"`
MaritalStatusCode string `json:"maritalStatusCode"`
WorkCode string `json:"workCode"`
Contact string `json:"contact"`
WaterSourceCode string `json:"shhjCheckYS"`
ManaUnitID string `json:"manaUnitId"`
PastDiseaseCode string `json:"diseasetext_radio_jb"`
FamilyMother string `json:"diseasetextCheckMQ"`
FamilySiblings string `json:"diseasetextCheckXDJM"`
InputDate string `json:"inputDate"`
GeneticDisease string `json:"diseasetextRedioYCBS"`
FamilyChildren string `json:"diseasetextCheckZN"`
BloodTypeCode string `json:"bloodTypeCode"`
CreateUnit string `json:"createUnit"`
DrugAllergy string `json:"diseasetext_check_gm"`
Birthday string `json:"birthday"`
SignFlag string `json:"signFlag"`
RhBloodCode string `json:"rhBloodCode"`
IsFillSHHJ string `json:"isFillShhj"`
EducationCode string `json:"educationCode"`
InsuranceCode string `json:"insuranceCode"`
InputUnit string `json:"inputUnit"`
RegionCode string `json:"regionCode"`
FamilyFather string `json:"diseasetext_check_fq"`
ExposureHistory string `json:"diseasetext_check_bl"`
WorkPlace string `json:"workPlace"`
NationCode string `json:"nationCode"`
CreateDate string `json:"createDate"`
RegisteredPermanent string `json:"registeredPermanent"`
Address string `json:"address"`
HomePlaceNumber string `json:"homePlaceNumber"`
CardType string `json:"cardType"`
UpdatedAt string `json:"updateTime"`
CookAirTool string `json:"shhjCheckCFPFSS"`
Disability string `json:"diseasetextCheckCJ"`
PersonName string `json:"personName"`
SexCode string `json:"sexCode"`
Washroom string `json:"shhjCheckCS"`
ManaDoctorID string `json:"manaDoctorId"`
AddressNumber string `json:"adressNumber"`
PastTransfusionCode string `json:"diseasetext_sx"`
CreateTime string `json:"createTime"`
PastSurgeryCode string `json:"diseasetext_ss"`
CreateUser string `json:"createUser"`
ContactPhone string `json:"contactPhone"`
PastTraumaCode string `json:"diseasetext_ws"`
FuelType string `json:"shhjCheckRLLX"`
LivestockColumn string `json:"shhjCheckQCL"`
}
func DecodeHealthRecordTask(raw []byte) (HealthRecordTask, error) {
var envelope healthRecordEnvelope
if err := json.Unmarshal(raw, &envelope); err != nil {
return HealthRecordTask{}, fmt.Errorf("decode PHIS health record: %w", err)
}
if envelope.Code != 200 {
return HealthRecordTask{}, fmt.Errorf("phis response code=%d message=%s", envelope.Code, envelope.Message)
}
envelope.Data.ArchID = strings.TrimSpace(envelope.Data.ArchID)
if envelope.Data.ArchID == "" {
return HealthRecordTask{}, fmt.Errorf("archId is required")
}
return envelope.Data, nil
}
func (t HealthRecordTask) SourceRecordID() string {
return t.ArchID
}
+58
View File
@@ -0,0 +1,58 @@
package source
import (
"encoding/json"
"os"
"strings"
"testing"
)
func TestDecodeHealthRecordTaskKeepsBusinessFieldsAndDropsCredentials(t *testing.T) {
raw, err := os.ReadFile("testdata/health_record.json")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
task, err := DecodeHealthRecordTask(raw)
if err != nil {
t.Fatalf("DecodeHealthRecordTask: %v", err)
}
if task.ArchID != "ARCH-001" || task.BusinessID != "BUSINESS-001" || task.EMPIID != "EMPI-001" || task.PHRID != "PHR-001" {
t.Fatalf("identifiers = %#v", task)
}
if task.SourceRecordID() != "ARCH-001" {
t.Fatalf("SourceRecordID = %q", task.SourceRecordID())
}
if task.Doctor.DoctorID != "DOC001" || task.Doctor.RealName != "测试医生" {
t.Fatalf("doctor = %#v", task.Doctor)
}
if task.Record.IDCard != "440000********1234" || task.Record.AddressNumber != "1号" {
t.Fatalf("record = %#v", task.Record)
}
sanitized, err := json.Marshal(task)
if err != nil {
t.Fatalf("Marshal: %v", err)
}
if strings.Contains(string(sanitized), "sxtAccount") || strings.Contains(string(sanitized), "sxtPassword") || strings.Contains(string(sanitized), "IGNORED_") {
t.Fatalf("credentials survived decoding: %s", sanitized)
}
}
func TestDecodeHealthRecordTaskRejectsMissingStableArchiveID(t *testing.T) {
raw := []byte(`{"code":200,"data":{"record":{"idCard":"440000********1234"},"businessId":"BUSINESS-001"}}`)
_, err := DecodeHealthRecordTask(raw)
if err == nil || !strings.Contains(err.Error(), "archId is required") {
t.Fatalf("error = %v", err)
}
}
func TestDecodeHealthRecordTaskRejectsFailedPHISResponse(t *testing.T) {
raw := []byte(`{"code":500,"msg":"failed","data":{"archId":"ARCH-001"}}`)
_, err := DecodeHealthRecordTask(raw)
if err == nil || !strings.Contains(err.Error(), "phis response code=500") {
t.Fatalf("error = %v", err)
}
}
+73
View File
@@ -0,0 +1,73 @@
{
"code": 200,
"msg": "success",
"compress": false,
"data": {
"doctor": {
"sxtAccount": "IGNORED_ACCOUNT",
"sxtPassword": "IGNORED_PASSWORD",
"realName": "测试医生",
"doctorId": "DOC001"
},
"record": {
"homePlace": "测试户籍地址",
"deadFlag": "n",
"idCard": "440000********1234",
"mobileNumber": "13900000000",
"inputUser": "DOC001",
"maritalStatusCode": "20",
"workCode": "Y",
"contact": "测试联系人",
"shhjCheckYS": "1",
"manaUnitId": "123456789",
"diseasetext_radio_jb": "0201",
"diseasetextCheckMQ": "0801",
"diseasetextCheckXDJM": "0901",
"inputDate": "2026-07-15",
"diseasetextRedioYCBS": "0501",
"diseasetextCheckZN": "1001",
"bloodTypeCode": "5",
"createUnit": "123456789",
"diseasetext_check_gm": "0101",
"birthday": "1980-01-02",
"signFlag": "n",
"rhBloodCode": "3",
"isFillShhj": "y",
"educationCode": "70",
"insuranceCode": "02",
"inputUnit": "123456789",
"regionCode": "441625000000",
"diseasetext_check_fq": "0701",
"diseasetext_check_bl": "1201",
"workPlace": "测试单位",
"nationCode": "01",
"createDate": "2026-07-15",
"registeredPermanent": "1",
"address": "测试现住址",
"homePlaceNumber": "2号",
"cardType": "01",
"updateTime": "2026-07-15 10:20:30",
"shhjCheckCFPFSS": "2",
"diseasetextCheckCJ": "1101",
"personName": "测试居民",
"sexCode": "1",
"shhjCheckCS": "1",
"manaDoctorId": "DOC001",
"adressNumber": "1号",
"diseasetext_sx": "0401",
"createTime": "2026-07-15 09:00:00",
"diseasetext_ss": "0301",
"createUser": "DOC001",
"contactPhone": "13800000000",
"diseasetext_ws": "0601",
"shhjCheckRLLX": "3",
"shhjCheckQCL": "0"
},
"archId": "ARCH-001",
"businessId": "BUSINESS-001",
"empiId": "EMPI-001",
"phrId": "PHR-001",
"cardNo": "",
"cardType": ""
}
}