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
+38
View File
@@ -402,3 +402,41 @@ Go 侧契约应从只承接 `personSign` 优化为完整承接 `personSign/idCar
- 定位和审计字段:`phrId`、`checkId`、`createUnit`、`createUser`、`createDate`、`inputDate`、`inputUser`、`inputUnit`。
- docx 仍误写成功码为 `"1"`;项目统一按现有 `IsSuccessCode` 兼容处理,实测成功基线仍为 `"01"`。
- docx 请求样例错误写为 `"serviceId":" LNR00004"`,不能用作实现依据。
---
## 14. PHIS 健康档案 → JKDA 写入映射(T-212)
输入契约来自本地 PHIS 待上报响应的脱敏结构;真实样本含居民信息和医生凭据,不入库。`source.HealthRecordTask` 只承接业务字段,解码时忽略 `doctor.sxtAccount/sxtPassword`。
PHIS `record.createDate` 映射到 CHIS 写入 `healthRecord.createDate`;该字段已在 JKDA00002 查询响应中实测存在,但写入端是否接受仍需 T-215 创建后回查确认。空的 `pastHistory` 不上送,`isFillShhj` 在校验与输出前统一去除空白并转为小写。
### 14.1 标识与管理字段
| PHIS | CHIS | 规则 |
| --- | --- | --- |
| `archId` | `healthRecord.checkId` | 稳定源档案键;生成规则见 ADR 001,缺失即拒绝 |
| `businessId` | trace/PHIS 回写 | 不进入 OSI 请求,不参与 checkId |
| `record.manaDoctorId` | `healthRecord.manaDoctorId/createUser`、`manageInfo.operateUser` | 必须命中 CHIS 责任医生字典 |
| `record.manaUnitId` | `healthRecord.manaUnitId/createUnit` | 必须命中 CHIS 机构字典 |
| 配置 `userName/orgCode` | `manageInfo.DSFMC/operateUnit` | 由 OSI 客户端强制覆盖,不能由 PHIS 注入 |
### 14.2 主体与嵌套节点
| PHIS | JKDA 写入字段 |
| --- | --- |
| `idCard/personName/sexCode/birthday/workPlace/mobileNumber/contact/contactPhone/registeredPermanent/regionCode/address/homePlace/homePlaceNumber/cardType` | `healthRecord` 同名字段 |
| `adressNumber` | `healthRecord.addressNumber`(写入拼写;T-215 真实回查校准) |
| `nationCode/bloodTypeCode/rhBloodCode/educationCode/workCode/maritalStatusCode/insuranceCode` | 码表校验后写入 `healthRecord` |
| `diseasetext_check_gm/check_bl/check_fq/CheckMQ/CheckXDJM/CheckZN/RedioYCBS/CheckCJ` | `pastHistory.ywgms/bls/jzsfqn/jzsmq/jzsxdjm/jzszn/ycbs/cjqk` |
| `diseasetext_radio_jb/ss/ws/sx` | `jwsjb/jwsss/jwsws/jwssx` 数组;非空代码(含“无”代码)各生成一项 |
| `shhjCheckCFPFSS/RLLX/YS/CS/QCL` | `familyMiddle.cookAirTool/fuelType/waterSourceCode/washroom/livestockColumn` |
多选兼容英文逗号、中文逗号、顿号和分号,输出统一为英文逗号。`isFillShhj=n` 时省略 `familyMiddle`;为 `y` 时才校验生活环境单选码。PHIS 未提供的名称、确诊日期和可选地址编码不伪造。
### 14.3 投递前校验
- CHIS 必填字段、身份证 18 位、网格码 12 位、生日 `yyyy-MM-dd`、docx 长度上限。
- 主体码表、既往史多选码和生活环境单选码必须合法;未知码返回结构化 `ValidationError`。
- `data.doctor.doctorId` 与 `record.manaDoctorId` 必须一致;医生和机构 ID 必须命中字典快照。
- T-215 真实写入后仍需确认“无”代码数组、`addressNumber`、更新目标标识和逐档案 `operateUser` 的平台最终规则。
+7 -8
View File
@@ -5,10 +5,10 @@
## 当前快照
- 日期:2026-07-15
- 阶段:**T-206 创建/更新本地能力已完成;进入 Phase U 的 PHIS 档案转换与 upsert,真实写入验收由 T-215 单独受阻**;T-303 老年人生活自理能力评估查询代码完成、真实联调受阻
- 日期:2026-07-16
- 阶段:**T-212 PHIS 健康档案转换器已完成;下一步 T-213 upsert 应用编排,真实写入验收由 T-215 单独受阻**;T-303 老年人生活自理能力评估查询代码完成、真实联调受阻
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find、JKDA00005 FindRqbj(人群分类,已按实测 auto 路径校准)、JKDA00001 Create、JKDA00003 Update、LNRZLPG00002 老年人生活自理能力查询(待真实联调),以及 WGDZ/ZRYS/YPML/CXJG 四个公开查询薄封装
- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`contract/jkda.go`、`contract/lnr.go`、`osi/` 薄客户端、`cache/dictionary.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`mapping/dict.go`、`mapping/health_record.go`、`mapping/checkid.go` 已建立映射纯函数、码表基线、主数据反查接入点和创建请求组装;`handler/health_record.go`+`handler/elderly.go`+`handler/public.go`+`server.go` 提供 server 模式档案查询、人群分类查询、老年人自理能力查询、网格地址、责任医生、药品目录、机构查询端点;`pipeline/` 等业务模块仍待后续任务建立
- 生产代码:新增 `source/health_record.go` 承接 PHIS 档案 DTO 并剥离医生凭据;`mapping/health_record.go` 已能把 PHIS 主体、既往史和生活环境转换为完整 JKDA 写入请求,校验稳定 archId、格式/码表及医生/机构主数据;OSI 写入保留逐档案 `operateUser`,配置继续控制 DSFMC/operateUnit;`pipeline/` 等业务模块仍待 T-213 建立
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;**公开查询 WGDZ00001/ZRYS00001/CXJG00002 已用真实档案主数据验证通过**,均返回 `code="01"` 且数组非空;药品目录 YPML00001 已完成客户端封装和单测,尚未做真实药品关键字样本验证;**JKDA00001 create 未跑真实请求**,避免在没有安全测试居民/写入授权时污染平台档案
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00001/00003 创建更新请求契约与客户端方法、JKDA00002 Find、JKDA00005 FindRqbj(personSign/idCard/phrId)、公开查询四接口、公开查询 HTTP API、字典缓存快照与持久化失败不阻断、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、主数据名称反查、创建请求组装、checkId 确定性、docx/联调风格映射样本基线、JKDA00002 验证入口
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
@@ -100,11 +100,10 @@ python3 scripts/query_health_record.py
> 2026-07-15 已把本地能力与真实验收拆开:T-206 标记本地 DONE,T-215 承接授权后的真实写入验收。
1. **T-212**:确认 PHIS 四类标识语义,建立脱敏 DTO/fixture、完整字段转换器和逐档案操作上下文。
2. **T-213**:用假 OSI、幂等、report、PHIS 回写接口完成 upsert 应用编排;不等待写入授权。
3. **T-204**:复用 T-213 暴露 `POST /api/health-record/upsert`,handler 不复制业务逻辑。
4. 并行催厂家/PHIS 维护方:写入授权与可写测试档案、稳定源主键语义、更新目标标识、逐档案 `operateUser` 规则。
5. 授权到位后执行 T-215 真实 create→query→update→query 验收。
1. **T-213**:用假 OSI、幂等、report、PHIS 回写接口完成 upsert 应用编排;不等待写入授权。
2. **T-204**:复用 T-213 暴露 `POST /api/health-record/upsert`,handler 不复制业务逻辑。
3. 并行催厂家:写入授权与可写测试档案、更新目标标识、逐档案 `operateUser` 最终规则。
4. 授权到位后执行 T-215 真实 create→query→update→query 验收。
## 维护规则
@@ -0,0 +1,21 @@
# 001 · PHIS 健康档案稳定源主键
## 背景
PHIS 待上报响应同时给出 `archId`、`businessId`、`empiId`、`phrId`。项目需要一个跨重试、跨更新时间稳定的源记录键生成 OSI `checkId`,否则同一档案更新会产生新的第三方流水号。
本地真实健康档案样例中 `archId == businessId`,单凭这一条数据无法判断两者语义。参考项目 `chis_upload/worker/phis_poll_worker.go` 的 PHIS 回调会同时原样回传 `archId` 和 `businessId`;其他业务样例中二者不同,说明 `businessId` 是业务记录/任务标识,不能作为跨业务稳定的居民档案标识。`empiId/phrId` 属于 CHIS 侧主键链路,可由创建或查询获得,不适合作为第三方源键。
## 决策
- 健康档案的 `SourceRecordID` 固定使用 PHIS `archId`。
- `businessId` 保留用于 trace 和 PHIS 状态回写,不参与 `checkId`。
- `empiId/phrId` 保留为上游携带的参考值;upsert 时以 CHIS 查询结果为准,不参与 `checkId`。
- `archId` 缺失时返回校验错误,不回退到 `businessId`,避免把一次业务任务误当成稳定档案。
- `checkId = sha1("PHIS|JKDA|<archId>")` 的前 20 位小写十六进制;`updateTime` 不参与计算。
## 影响
- 同一 PHIS 档案重试或内容更新时保持相同 `checkId`。
- 现有 `GenerateCheckID` 移除版本参数;原 T-205 中“更新时间改变 checkId”的基线同步调整。
- 若 PHIS 后续正式契约说明 `archId` 不是稳定档案键,必须新建 ADR 迁移,不能静默改变已投递记录的 checkId。
@@ -0,0 +1,88 @@
# PHIS Health Record Upsert Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Convert one PHIS health-record task into a validated CHIS JKDA write request, then provide a reusable query-first upsert application service.
**Architecture:** `source` owns the PHIS JSON contract and strips credentials, `mapping` performs pure PHIS-to-CHIS conversion, `contract` owns the JKDA write shape, and `pipeline` orchestrates query/create/update through injected interfaces. HTTP exposure and persistent retry/report stores remain in T-204 and Phase 3.
**Tech Stack:** Go 1.24 standard library, existing `contract`/`mapping`/`osi` packages, table-driven tests.
---
### Task 1: T-212 PHIS DTO and source identity
**Files:**
- Create: `source/health_record.go`
- Create: `source/health_record_test.go`
- Create: `source/testdata/health_record.json`
- Create: `docs/decisions/001-phis-health-record-source-key.md`
- [ ] Write failing tests that decode the PHIS envelope, retain `archId/businessId/empiId/phrId`, expose only doctor ID/name, and reject a missing `archId`.
- [ ] Run `go test ./source -count=1` and verify failure because the package/API does not exist.
- [ ] Implement the DTO and `DecodeHealthRecordTask`; use `archId` as the health-record source key and retain `businessId` only for tracing.
- [ ] Run `go test ./source -count=1` and verify pass.
- [ ] Record the source-key evidence and compatibility decision in the ADR.
### Task 2: T-212 complete mapping and write contract
**Files:**
- Modify: `contract/jkda.go`
- Modify: `contract/jkda_test.go`
- Modify: `mapping/health_record.go`
- Modify: `mapping/health_record_test.go`
- Modify: `mapping/health_record_baseline_test.go`
- Modify: `mapping/checkid.go`
- [ ] Write failing contract/mapping tests for all PHIS direct fields, `adressNumber` to `addressNumber`, stable checkId across `updateTime`, past-history nodes, four history arrays, conditional `familyMiddle`, required/date/length/code validation, and per-record `operateUser`.
- [ ] Run targeted `go test ./contract ./mapping -count=1` and verify expected failures.
- [ ] Expand only fields supplied by PHIS and implement pure conversion helpers; normalize multi-select separators and preserve explicit no-history codes.
- [ ] Run targeted tests until green, then run `go test ./contract ./mapping -count=1`.
### Task 3: T-212 per-record operation context
**Files:**
- Modify: `osi/jkda.go`
- Modify: `osi/jkda_test.go`
- Modify: `cache/dictionary.go`
- Modify: `cache/dictionary_test.go`
- [ ] Write failing tests proving a mapped doctor ID survives as `manageInfo.operateUser`, while `DSFMC/operateUnit` still come from trusted client config; add doctor-ID membership validation to dictionary snapshots.
- [ ] Run targeted tests and verify expected failures.
- [ ] Implement minimal request-context merge and dictionary membership lookup.
- [ ] Run `go test ./cache ./osi ./mapping -count=1` and verify pass.
### Task 4: Complete and commit T-212
**Files:**
- Modify: `tasks.md`
- Modify: `progress.md`
- Modify: `docs/current-state.md`
- Modify: `docs/04-字段与接口映射.md`
- [ ] Update mapping documentation, mark T-212 DONE, append RED/GREEN/full validation evidence, and set T-213 as next.
- [ ] Run `gofmt`, `go test ./...`, `go build ./...`, and `git diff --check`.
- [ ] Commit T-212 as one logical commit.
### Task 5: T-213 query-first upsert service
**Files:**
- Create: `pipeline/health_record_upsert.go`
- Create: `pipeline/health_record_upsert_test.go`
- [ ] Write failing tests for zero-result create, one-result update, multiple/cross-unit/inactive manual review, query failure without create fallback, create/update failure classification, idempotent skip, and PHIS/report events.
- [ ] Run `go test ./pipeline -count=1` and verify expected failure because the service does not exist.
- [ ] Implement `HealthRecordUpsertService` with injected OSI, converter, idempotency, report, and PHIS status interfaces; keep persistence and retry scheduling out of scope.
- [ ] Run `go test ./pipeline -count=1` and then `go test ./...`.
### Task 6: Complete and commit T-213
**Files:**
- Modify: `tasks.md`
- Modify: `progress.md`
- Modify: `docs/current-state.md`
- Modify: `docs/03-目标架构设计.md`
- [ ] Document the reusable application-service boundary and mark T-213 DONE; leave T-204 TODO and T-215 BLOCKED.
- [ ] Run `gofmt`, `go test ./...`, `go build ./...`, and `git diff --check`.
- [ ] Commit T-213 as a second logical commit.