feat(mapping): 建立码表映射基线(T-102)
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
## 阶段 1 · 字典服务打通
|
## 阶段 1 · 字典服务打通
|
||||||
|
|
||||||
- [ ] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。
|
- [ ] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。
|
||||||
- [ ] `mapping/dict.go` 落地全部码表(含 56 项民族)。
|
- [x] `mapping/dict.go` 落地全部码表(含 56 项民族)。
|
||||||
- [ ] 字典缓存(内存 + redis 可选),供映射层反查 `regionCode/manaDoctorId/manaUnitId`。
|
- [ ] 字典缓存(内存 + redis 可选),供映射层反查 `regionCode/manaDoctorId/manaUnitId`。
|
||||||
- **验收**:能用真实机构码查到下级网格、责任医生、机构树。
|
- **验收**:能用真实机构码查到下级网格、责任医生、机构树。
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-07-07
|
- 日期:2026-07-07
|
||||||
- 阶段:**Phase Q 查询档案已打通**;T-001~T-006、T-201、T-203 已验收,下一步进入 Phase M 的 T-102
|
- 阶段:**Phase M 映射基线已起步**;T-001~T-006、T-201、T-203、T-102 已验收,下一步进入 T-202
|
||||||
- 技术栈: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/`、`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` 已建立码表基线;`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、JKDA00002 验证入口
|
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00002 Find、JKDA00005 FindRqbj、映射码表双向查找、民族 01~56 完整性、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 的当前路径
|
||||||
@@ -35,7 +35,8 @@
|
|||||||
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
||||||
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
||||||
| `config.yaml` | 已有·不入库 | 真实凭据;不要提交 |
|
| `config.yaml` | 已有·不入库 | 真实凭据;不要提交 |
|
||||||
| `mapping/` `pipeline/` 等 | 待建 | 后续任务 |
|
| `mapping/dict.go` | 已有 | 性别/民族/血型/RH/文化程度/职业/婚姻/医保/personSign 双向码表;未命中返回 `ValidationError` |
|
||||||
|
| `pipeline/` 等 | 待建 | 后续任务 |
|
||||||
|
|
||||||
## 已验证事实(写代码时直接依赖)
|
## 已验证事实(写代码时直接依赖)
|
||||||
|
|
||||||
@@ -70,8 +71,8 @@ python3 scripts/query_health_record.py
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. T-102:`mapping/dict.go` 全量码表(含 56 项民族)。
|
1. T-202:`mapping/health_record.go` + `mapping/checkid.go`,基于码表和 JKDA 查询契约建立映射纯函数。
|
||||||
2. T-202:码表完成后进入健康档案映射与 checkId。
|
2. T-205:映射单测基线。
|
||||||
|
|
||||||
## 维护规则
|
## 维护规则
|
||||||
|
|
||||||
|
|||||||
+244
@@ -0,0 +1,244 @@
|
|||||||
|
package mapping
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Direction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectionPHISToOSI Direction = "phis_to_osi"
|
||||||
|
DirectionOSIToPHIS Direction = "osi_to_phis"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidationError struct {
|
||||||
|
Field string
|
||||||
|
DictName string
|
||||||
|
Direction Direction
|
||||||
|
Code string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ValidationError) Error() string {
|
||||||
|
return fmt.Sprintf("mapping %s %s unknown code %q for %s", e.DictName, e.Direction, e.Code, e.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
PHIS string
|
||||||
|
OSI string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dict struct {
|
||||||
|
Name string
|
||||||
|
Field string
|
||||||
|
Entries []Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dict) PHISToOSI(code string) (string, error) {
|
||||||
|
for _, entry := range d.Entries {
|
||||||
|
if entry.PHIS == code {
|
||||||
|
return entry.OSI, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ValidationError{Field: d.Field, DictName: d.Name, Direction: DirectionPHISToOSI, Code: code}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dict) OSIToPHIS(code string) (string, error) {
|
||||||
|
for _, entry := range d.Entries {
|
||||||
|
if entry.OSI == code {
|
||||||
|
return entry.PHIS, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ValidationError{Field: d.Field, DictName: d.Name, Direction: DirectionOSIToPHIS, Code: code}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dict) EntryByOSI(code string) (Entry, bool) {
|
||||||
|
for _, entry := range d.Entries {
|
||||||
|
if entry.OSI == code {
|
||||||
|
return entry, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Entry{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dict) Len() int {
|
||||||
|
return len(d.Entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dict) Validate() error {
|
||||||
|
phisSeen := make(map[string]bool, len(d.Entries))
|
||||||
|
osiSeen := make(map[string]bool, len(d.Entries))
|
||||||
|
for _, entry := range d.Entries {
|
||||||
|
if entry.PHIS == "" || entry.OSI == "" {
|
||||||
|
return fmt.Errorf("%s contains empty code: %#v", d.Name, entry)
|
||||||
|
}
|
||||||
|
if phisSeen[entry.PHIS] {
|
||||||
|
return fmt.Errorf("%s duplicate PHIS code %q", d.Name, entry.PHIS)
|
||||||
|
}
|
||||||
|
if osiSeen[entry.OSI] {
|
||||||
|
return fmt.Errorf("%s duplicate OSI code %q", d.Name, entry.OSI)
|
||||||
|
}
|
||||||
|
phisSeen[entry.PHIS] = true
|
||||||
|
osiSeen[entry.OSI] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameCodeDict(name, field string, entries ...Entry) Dict {
|
||||||
|
for i := range entries {
|
||||||
|
if entries[i].PHIS == "" {
|
||||||
|
entries[i].PHIS = entries[i].OSI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Dict{Name: name, Field: field, Entries: entries}
|
||||||
|
}
|
||||||
|
|
||||||
|
var SexDict = sameCodeDict("sex", "sexCode",
|
||||||
|
Entry{OSI: "0", Name: "未知"},
|
||||||
|
Entry{OSI: "1", Name: "男"},
|
||||||
|
Entry{OSI: "2", Name: "女"},
|
||||||
|
Entry{OSI: "9", Name: "未说明"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var BloodTypeDict = sameCodeDict("bloodType", "bloodTypeCode",
|
||||||
|
Entry{OSI: "1", Name: "A"},
|
||||||
|
Entry{OSI: "2", Name: "B"},
|
||||||
|
Entry{OSI: "3", Name: "O"},
|
||||||
|
Entry{OSI: "4", Name: "AB"},
|
||||||
|
Entry{OSI: "5", Name: "不详"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var RhBloodDict = sameCodeDict("rhBlood", "rhBloodCode",
|
||||||
|
Entry{OSI: "1", Name: "阳性"},
|
||||||
|
Entry{OSI: "2", Name: "阴性"},
|
||||||
|
Entry{OSI: "3", Name: "不详"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var EducationDict = sameCodeDict("education", "educationCode",
|
||||||
|
Entry{OSI: "10", Name: "研究生"},
|
||||||
|
Entry{OSI: "20", Name: "本科"},
|
||||||
|
Entry{OSI: "31", Name: "大专"},
|
||||||
|
Entry{OSI: "41", Name: "中专"},
|
||||||
|
Entry{OSI: "47", Name: "技校"},
|
||||||
|
Entry{OSI: "60", Name: "高中"},
|
||||||
|
Entry{OSI: "70", Name: "初中"},
|
||||||
|
Entry{OSI: "80", Name: "小学"},
|
||||||
|
Entry{OSI: "90", Name: "文盲"},
|
||||||
|
Entry{OSI: "91", Name: "不详"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var WorkDict = sameCodeDict("work", "workCode",
|
||||||
|
Entry{OSI: "0", Name: "负责人"},
|
||||||
|
Entry{OSI: "1", Name: "专业技术人员"},
|
||||||
|
Entry{OSI: "2", Name: "专业技术人员"},
|
||||||
|
Entry{OSI: "3", Name: "办事人员"},
|
||||||
|
Entry{OSI: "4", Name: "商业服务业人员"},
|
||||||
|
Entry{OSI: "5", Name: "农林牧渔人员"},
|
||||||
|
Entry{OSI: "9", Name: "生产运输人员"},
|
||||||
|
Entry{OSI: "X", Name: "军人"},
|
||||||
|
Entry{OSI: "Y", Name: "其他"},
|
||||||
|
Entry{OSI: "8", Name: "无职业"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var MaritalStatusDict = sameCodeDict("maritalStatus", "maritalStatusCode",
|
||||||
|
Entry{OSI: "10", Name: "未婚"},
|
||||||
|
Entry{OSI: "20", Name: "已婚"},
|
||||||
|
Entry{OSI: "30", Name: "丧偶"},
|
||||||
|
Entry{OSI: "40", Name: "离婚"},
|
||||||
|
Entry{OSI: "90", Name: "未说明"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var InsuranceDict = sameCodeDict("insurance", "insuranceCode",
|
||||||
|
Entry{OSI: "01", Name: "城镇职工"},
|
||||||
|
Entry{OSI: "02", Name: "城乡居民"},
|
||||||
|
Entry{OSI: "04", Name: "贫困救助"},
|
||||||
|
Entry{OSI: "05", Name: "商业"},
|
||||||
|
Entry{OSI: "06", Name: "全公费"},
|
||||||
|
Entry{OSI: "07", Name: "全自费"},
|
||||||
|
Entry{OSI: "99", Name: "其他"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var PersonSignDict = sameCodeDict("personSign", "personSign",
|
||||||
|
Entry{OSI: "PU", Name: "普通"},
|
||||||
|
Entry{OSI: "GRQY", Name: "已签约"},
|
||||||
|
Entry{OSI: "LAO", Name: "老年"},
|
||||||
|
Entry{OSI: "GAO", Name: "高血压"},
|
||||||
|
Entry{OSI: "TANG", Name: "糖尿病"},
|
||||||
|
Entry{OSI: "FU", Name: "孕产妇"},
|
||||||
|
Entry{OSI: "ER", Name: "儿童"},
|
||||||
|
Entry{OSI: "FEI", Name: "肺结核"},
|
||||||
|
Entry{OSI: "JING", Name: "精神障碍"},
|
||||||
|
Entry{OSI: "CAN", Name: "残疾"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var NationDict = sameCodeDict("nation", "nationCode",
|
||||||
|
Entry{OSI: "01", Name: "汉族"},
|
||||||
|
Entry{OSI: "02", Name: "蒙古族"},
|
||||||
|
Entry{OSI: "03", Name: "回族"},
|
||||||
|
Entry{OSI: "04", Name: "藏族"},
|
||||||
|
Entry{OSI: "05", Name: "维吾尔族"},
|
||||||
|
Entry{OSI: "06", Name: "苗族"},
|
||||||
|
Entry{OSI: "07", Name: "彝族"},
|
||||||
|
Entry{OSI: "08", Name: "壮族"},
|
||||||
|
Entry{OSI: "09", Name: "布依族"},
|
||||||
|
Entry{OSI: "10", Name: "朝鲜族"},
|
||||||
|
Entry{OSI: "11", Name: "满族"},
|
||||||
|
Entry{OSI: "12", Name: "侗族"},
|
||||||
|
Entry{OSI: "13", Name: "瑶族"},
|
||||||
|
Entry{OSI: "14", Name: "白族"},
|
||||||
|
Entry{OSI: "15", Name: "土家族"},
|
||||||
|
Entry{OSI: "16", Name: "哈尼族"},
|
||||||
|
Entry{OSI: "17", Name: "哈萨克族"},
|
||||||
|
Entry{OSI: "18", Name: "傣族"},
|
||||||
|
Entry{OSI: "19", Name: "黎族"},
|
||||||
|
Entry{OSI: "20", Name: "傈僳族"},
|
||||||
|
Entry{OSI: "21", Name: "佤族"},
|
||||||
|
Entry{OSI: "22", Name: "畲族"},
|
||||||
|
Entry{OSI: "23", Name: "高山族"},
|
||||||
|
Entry{OSI: "24", Name: "拉祜族"},
|
||||||
|
Entry{OSI: "25", Name: "水族"},
|
||||||
|
Entry{OSI: "26", Name: "东乡族"},
|
||||||
|
Entry{OSI: "27", Name: "纳西族"},
|
||||||
|
Entry{OSI: "28", Name: "景颇族"},
|
||||||
|
Entry{OSI: "29", Name: "柯尔克孜族"},
|
||||||
|
Entry{OSI: "30", Name: "土族"},
|
||||||
|
Entry{OSI: "31", Name: "达斡尔族"},
|
||||||
|
Entry{OSI: "32", Name: "仫佬族"},
|
||||||
|
Entry{OSI: "33", Name: "羌族"},
|
||||||
|
Entry{OSI: "34", Name: "布朗族"},
|
||||||
|
Entry{OSI: "35", Name: "撒拉族"},
|
||||||
|
Entry{OSI: "36", Name: "毛南族"},
|
||||||
|
Entry{OSI: "37", Name: "仡佬族"},
|
||||||
|
Entry{OSI: "38", Name: "锡伯族"},
|
||||||
|
Entry{OSI: "39", Name: "阿昌族"},
|
||||||
|
Entry{OSI: "40", Name: "普米族"},
|
||||||
|
Entry{OSI: "41", Name: "塔吉克族"},
|
||||||
|
Entry{OSI: "42", Name: "怒族"},
|
||||||
|
Entry{OSI: "43", Name: "乌孜别克族"},
|
||||||
|
Entry{OSI: "44", Name: "俄罗斯族"},
|
||||||
|
Entry{OSI: "45", Name: "鄂温克族"},
|
||||||
|
Entry{OSI: "46", Name: "德昂族"},
|
||||||
|
Entry{OSI: "47", Name: "保安族"},
|
||||||
|
Entry{OSI: "48", Name: "裕固族"},
|
||||||
|
Entry{OSI: "49", Name: "京族"},
|
||||||
|
Entry{OSI: "50", Name: "塔塔尔族"},
|
||||||
|
Entry{OSI: "51", Name: "独龙族"},
|
||||||
|
Entry{OSI: "52", Name: "鄂伦春族"},
|
||||||
|
Entry{OSI: "53", Name: "赫哲族"},
|
||||||
|
Entry{OSI: "54", Name: "门巴族"},
|
||||||
|
Entry{OSI: "55", Name: "珞巴族"},
|
||||||
|
Entry{OSI: "56", Name: "基诺族"},
|
||||||
|
Entry{OSI: "99", Name: "其他"},
|
||||||
|
)
|
||||||
|
|
||||||
|
func AllDicts() []Dict {
|
||||||
|
return []Dict{
|
||||||
|
SexDict,
|
||||||
|
BloodTypeDict,
|
||||||
|
RhBloodDict,
|
||||||
|
EducationDict,
|
||||||
|
WorkDict,
|
||||||
|
MaritalStatusDict,
|
||||||
|
InsuranceDict,
|
||||||
|
NationDict,
|
||||||
|
PersonSignDict,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package mapping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDictTranslatesPHISToOSIAndOSIToPHIS(t *testing.T) {
|
||||||
|
got, err := SexDict.PHISToOSI("1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PHISToOSI: %v", err)
|
||||||
|
}
|
||||||
|
if got != "1" {
|
||||||
|
t.Fatalf("PHISToOSI = %q, want 1", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err = SexDict.OSIToPHIS("2")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OSIToPHIS: %v", err)
|
||||||
|
}
|
||||||
|
if got != "2" {
|
||||||
|
t.Fatalf("OSIToPHIS = %q, want 2", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictReturnsValidationErrorForUnknownCode(t *testing.T) {
|
||||||
|
_, err := SexDict.PHISToOSI("bad")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("PHISToOSI accepted unknown code")
|
||||||
|
}
|
||||||
|
var validationErr ValidationError
|
||||||
|
if !errors.As(err, &validationErr) {
|
||||||
|
t.Fatalf("error type = %T, want ValidationError", err)
|
||||||
|
}
|
||||||
|
if validationErr.Field != "sexCode" || validationErr.Code != "bad" || validationErr.Direction != DirectionPHISToOSI {
|
||||||
|
t.Fatalf("validation error = %#v", validationErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNationDictContains56EthnicGroupsAndOther(t *testing.T) {
|
||||||
|
if got := NationDict.Len(); got != 57 {
|
||||||
|
t.Fatalf("NationDict.Len = %d, want 57 including 99 other", got)
|
||||||
|
}
|
||||||
|
checks := map[string]string{
|
||||||
|
"01": "汉族",
|
||||||
|
"02": "蒙古族",
|
||||||
|
"10": "朝鲜族",
|
||||||
|
"56": "基诺族",
|
||||||
|
"99": "其他",
|
||||||
|
}
|
||||||
|
for code, wantName := range checks {
|
||||||
|
entry, ok := NationDict.EntryByOSI(code)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("NationDict missing code %s", code)
|
||||||
|
}
|
||||||
|
if entry.Name != wantName {
|
||||||
|
t.Fatalf("NationDict[%s].Name = %q, want %q", code, entry.Name, wantName)
|
||||||
|
}
|
||||||
|
got, err := NationDict.PHISToOSI(code)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Nation PHISToOSI(%s): %v", code, err)
|
||||||
|
}
|
||||||
|
if got != code {
|
||||||
|
t.Fatalf("Nation PHISToOSI(%s) = %q", code, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNationDictContainsAllCodesFrom01To56(t *testing.T) {
|
||||||
|
for i := 1; i <= 56; i++ {
|
||||||
|
code := fmt.Sprintf("%02d", i)
|
||||||
|
if _, ok := NationDict.EntryByOSI(code); !ok {
|
||||||
|
t.Fatalf("NationDict missing code %s", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestCoreDictsContainDocumentedValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dict Dict
|
||||||
|
code string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "blood", dict: BloodTypeDict, code: "4", want: "AB"},
|
||||||
|
{name: "rh", dict: RhBloodDict, code: "2", want: "阴性"},
|
||||||
|
{name: "education", dict: EducationDict, code: "31", want: "大专"},
|
||||||
|
{name: "work", dict: WorkDict, code: "Y", want: "其他"},
|
||||||
|
{name: "marital", dict: MaritalStatusDict, code: "40", want: "离婚"},
|
||||||
|
{name: "insurance", dict: InsuranceDict, code: "99", want: "其他"},
|
||||||
|
{name: "personSign", dict: PersonSignDict, code: "GRQY", want: "已签约"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
entry, ok := tt.dict.EntryByOSI(tt.code)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("missing code %s", tt.code)
|
||||||
|
}
|
||||||
|
if entry.Name != tt.want {
|
||||||
|
t.Fatalf("name = %q, want %q", entry.Name, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllDictsRejectDuplicateCodes(t *testing.T) {
|
||||||
|
for _, dict := range AllDicts() {
|
||||||
|
if err := dict.Validate(); err != nil {
|
||||||
|
t.Fatalf("%s Validate: %v", dict.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,3 +126,10 @@
|
|||||||
- 验证:按 TDD 先运行 `go test ./osi -run "TestFind(HealthRecord|Rqbj)" -count=1`,因 `FindHealthRecord`/`FindRqbj` 与查询类型未定义失败;实现后目标测试通过。随后 `go test . -run TestRunJKDAFindCheckUsesConfiguredOSIClient -count=1` 通过,`go test ./...` 通过;使用本地环境变量发起正式 Go Find 真实请求,返回 `JKDA00002 code=01 message=操作成功 data_count=1`。
|
- 验证:按 TDD 先运行 `go test ./osi -run "TestFind(HealthRecord|Rqbj)" -count=1`,因 `FindHealthRecord`/`FindRqbj` 与查询类型未定义失败;实现后目标测试通过。随后 `go test . -run TestRunJKDAFindCheckUsesConfiguredOSIClient -count=1` 通过,`go test ./...` 通过;使用本地环境变量发起正式 Go Find 真实请求,返回 `JKDA00002 code=01 message=操作成功 data_count=1`。
|
||||||
- 决策:FindRqbj 已有 serviceId/path 与 `personSign` 契约,先以单测覆盖;当前只对 JKDA00002 Find 做真实请求验收,因为本地已有可查档案样本。
|
- 决策:FindRqbj 已有 serviceId/path 与 `personSign` 契约,先以单测覆盖;当前只对 JKDA00002 Find 做真实请求验收,因为本地已有可查档案样本。
|
||||||
- 下一步:T-102(`mapping/dict.go` 全量码表)。
|
- 下一步:T-102(`mapping/dict.go` 全量码表)。
|
||||||
|
## 2026-07-07 T-102 映射码表基线
|
||||||
|
|
||||||
|
- 状态:DONE
|
||||||
|
- 变更:新增 `mapping/dict.go` 与 `mapping/dict_test.go`;建立通用 `Dict`/`Entry` 双向查表结构和 `ValidationError`;落地性别、民族(01~56 + 99 其他)、血型、RH、文化程度、职业、婚姻、医保、人群标记 `personSign` 初始码表。
|
||||||
|
- 验证:按 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 码值差异可局部覆盖,不改调用方。
|
||||||
|
- 下一步:T-202(`mapping/health_record.go` + `mapping/checkid.go`)。
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| T-102 | `mapping/dict.go` 全量码表(含 56 项民族) | T-001 | 双向查表;未命中显式 ValidationError;单测覆盖(静态枚举,不走网络) | TODO |
|
| 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 确定性生成单测 | TODO |
|
||||||
| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO |
|
| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user