feat(mapping): 实现健康档案映射基线(T-202)
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
## 阶段 2 · 健康档案闭环(第一条业务线)
|
## 阶段 2 · 健康档案闭环(第一条业务线)
|
||||||
|
|
||||||
- [x] `contract/jkda.go` 查询响应结构体(T-201;创建/更新请求结构体留到 T-206)。
|
- [x] `contract/jkda.go` 查询响应结构体(T-201;创建/更新请求结构体留到 T-206)。
|
||||||
- [ ] `mapping/health_record.go` + `mapping/checkid.go`。
|
- [x] `mapping/health_record.go` + `mapping/checkid.go`。
|
||||||
- [x] `osi/jkda.go`:Find/FindRqbj(T-203;Find 已真实请求验收,FindRqbj 已按文档契约单测覆盖)。
|
- [x] `osi/jkda.go`:Find/FindRqbj(T-203;Find 已真实请求验收,FindRqbj 已按文档契约单测覆盖)。
|
||||||
- [ ] `osi/jkda.go`:Create/Update(T-206,依赖映射与字典缓存)。
|
- [ ] `osi/jkda.go`:Create/Update(T-206,依赖映射与字典缓存)。
|
||||||
- [ ] `handler` + `router`:暴露 `/api/health-record/save`(server 模式联调用)。
|
- [ ] `handler` + `router`:暴露 `/api/health-record/save`(server 模式联调用)。
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-07-07
|
- 日期:2026-07-07
|
||||||
- 阶段:**Phase M 映射基线已起步**;T-001~T-006、T-201、T-203、T-102 已验收,下一步进入 T-202
|
- 阶段:**Phase M 映射层主体已起步**;T-001~T-006、T-201、T-203、T-102、T-202 已验收,下一步进入 T-205
|
||||||
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find 与 JKDA00005 FindRqbj
|
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find 与 JKDA00005 FindRqbj
|
||||||
- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`osi/sign.go`、`osi/transport.go`、`osi/codes.go`、`osi/client.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`contract/jkda.go` 已建立 JKDA00002 查询响应契约;`osi/jkda.go` 已实现 Find/FindRqbj;`mapping/dict.go` 已建立码表基线;`pipeline/` 等业务模块仍待后续任务建立
|
- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`osi/sign.go`、`osi/transport.go`、`osi/codes.go`、`osi/client.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`contract/jkda.go` 已建立 JKDA00002 查询响应契约;`osi/jkda.go` 已实现 Find/FindRqbj;`mapping/dict.go` 已建立码表基线;`mapping/health_record.go` 与 `mapping/checkid.go` 已建立映射纯函数基线;`pipeline/` 等业务模块仍待后续任务建立
|
||||||
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;Python 联调样本和实测契约沉淀在 `docs/04 §8`
|
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;Python 联调样本和实测契约沉淀在 `docs/04 §8`
|
||||||
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00002 Find、JKDA00005 FindRqbj、映射码表双向查找、民族 01~56 完整性、JKDA00002 验证入口
|
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00002 Find、JKDA00005 FindRqbj、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、checkId 确定性、JKDA00002 验证入口
|
||||||
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
|
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
|
||||||
- 标准验证路径:`go test ./...`、`go build ./...`
|
- 标准验证路径:`go test ./...`、`go build ./...`
|
||||||
- 当前 blocker:无硬 blocker。软限制:当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,`init.sh` 无法完整跑完;PowerShell 下等价 Go 命令和真实请求通过。厂家侧 B1/B2/B4/B5 契约缺口只影响阶段 4,不阻塞 Phase Q/M/D 的当前路径
|
- 当前 blocker:无硬 blocker。软限制:当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,`init.sh` 无法完整跑完;PowerShell 下等价 Go 命令和真实请求通过。厂家侧 B1/B2/B4/B5 契约缺口只影响阶段 4,不阻塞 Phase Q/M/D 的当前路径
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
||||||
| `config.yaml` | 已有·不入库 | 真实凭据;不要提交 |
|
| `config.yaml` | 已有·不入库 | 真实凭据;不要提交 |
|
||||||
| `mapping/dict.go` | 已有 | 性别/民族/血型/RH/文化程度/职业/婚姻/医保/personSign 双向码表;未命中返回 `ValidationError` |
|
| `mapping/dict.go` | 已有 | 性别/民族/血型/RH/文化程度/职业/婚姻/医保/personSign 双向码表;未命中返回 `ValidationError` |
|
||||||
|
| `mapping/health_record.go` `mapping/checkid.go` | 已有 | 健康档案映射草稿、字典快照注入、必填/码表校验、确定性 checkId |
|
||||||
| `pipeline/` 等 | 待建 | 后续任务 |
|
| `pipeline/` 等 | 待建 | 后续任务 |
|
||||||
|
|
||||||
## 已验证事实(写代码时直接依赖)
|
## 已验证事实(写代码时直接依赖)
|
||||||
@@ -71,8 +72,8 @@ python3 scripts/query_health_record.py
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. T-202:`mapping/health_record.go` + `mapping/checkid.go`,基于码表和 JKDA 查询契约建立映射纯函数。
|
1. T-205:映射单测基线,补 docx 样例 + 联调样本风格的回归用例。
|
||||||
2. T-205:映射单测基线。
|
2. T-101:映射基线完成后进入真实字典服务。
|
||||||
|
|
||||||
## 维护规则
|
## 维护规则
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
DictName string
|
||||||
Direction Direction
|
Direction Direction
|
||||||
Code string
|
Code string
|
||||||
|
Reason string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ValidationError) Error() 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)
|
||||||
|
}
|
||||||
@@ -133,3 +133,10 @@
|
|||||||
- 验证:按 TDD 先运行 `go test ./mapping -count=1`,因 `SexDict`/`Dict`/`ValidationError` 等未定义失败;实现后 `go test ./mapping -count=1` 通过;补充民族 `01~56` 连续码检查后再次通过。最终 `go test ./...` 通过,`go build ./...` 通过。
|
- 验证:按 TDD 先运行 `go test ./mapping -count=1`,因 `SexDict`/`Dict`/`ValidationError` 等未定义失败;实现后 `go test ./mapping -count=1` 通过;补充民族 `01~56` 连续码检查后再次通过。最终 `go test ./...` 通过,`go build ./...` 通过。
|
||||||
- 决策:当前文档未给 PHIS 侧差异码值,T-102 先以 PHIS 与 OSI 同码作为静态基线;保留 `Entry{PHIS, OSI}` 结构,后续 T-202 若发现 PHIS 码值差异可局部覆盖,不改调用方。
|
- 决策:当前文档未给 PHIS 侧差异码值,T-102 先以 PHIS 与 OSI 同码作为静态基线;保留 `Entry{PHIS, OSI}` 结构,后续 T-202 若发现 PHIS 码值差异可局部覆盖,不改调用方。
|
||||||
- 下一步:T-202(`mapping/health_record.go` + `mapping/checkid.go`)。
|
- 下一步:T-202(`mapping/health_record.go` + `mapping/checkid.go`)。
|
||||||
|
## 2026-07-07 T-202 健康档案映射与 checkId
|
||||||
|
|
||||||
|
- 状态:DONE
|
||||||
|
- 变更:新增 `mapping/health_record.go`、`mapping/checkid.go` 与 `mapping/health_record_test.go`;建立 PHIS 健康档案输入、映射层 `MappedHealthRecord` 草稿、`MapContext`/`DictSnapshot` 注入点;主数据直传字段、码表字段、必填校验、结构化 `ValidationError` 与确定性 `GenerateCheckID` 已覆盖。
|
||||||
|
- 验证:按 TDD 先运行 `go test ./mapping -run "TestMapHealthRecord|TestGenerateCheckID" -count=1`,因 `MapHealthRecord`/`MapContext`/`GenerateCheckID` 未定义失败;实现后目标测试通过。最终 `go test ./mapping -count=1`、`go test ./...`、`go build ./...`、`git diff --check` 均通过。
|
||||||
|
- 决策:创建请求结构体仍留到 T-206;T-202 只输出映射层草稿结构,先把纯函数、校验、字典注入和 checkId 稳定性打牢。
|
||||||
|
- 下一步:T-205(映射单测基线:docx 样例 + 联调样本)。
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| T-102 | `mapping/dict.go` 全量码表(含 56 项民族) | T-001 | 双向查表;未命中显式 ValidationError;单测覆盖(静态枚举,不走网络) | DONE |
|
| T-102 | `mapping/dict.go` 全量码表(含 56 项民族) | T-001 | 双向查表;未命中显式 ValidationError;单测覆盖(静态枚举,不走网络) | DONE |
|
||||||
| T-202 | `mapping/health_record.go` + `mapping/checkid.go` | T-102, T-201 | 映射纯函数 + 结构化校验错误;主数据经 `MapContext` 字典快照注入(单测塞假快照);checkId 确定性生成单测 | TODO |
|
| T-202 | `mapping/health_record.go` + `mapping/checkid.go` | T-102, T-201 | 映射纯函数 + 结构化校验错误;主数据经 `MapContext` 字典快照注入(单测塞假快照);checkId 确定性生成单测 | DONE |
|
||||||
| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO |
|
| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO |
|
||||||
|
|
||||||
## Phase D · 字典服务与创建闭环(真实主数据反查)
|
## Phase D · 字典服务与创建闭环(真实主数据反查)
|
||||||
|
|||||||
Reference in New Issue
Block a user