163 lines
19 KiB
Markdown
163 lines
19 KiB
Markdown
# 任务看板(Tasks)
|
||
|
||
> 把 `docs/05-实施路线图.md` 的阶段拆成小步、可独立交付、可验收的任务。
|
||
> 每轮只领取**一个**状态为 `TODO` 且依赖均 `DONE` 的任务(取最靠前的)。
|
||
|
||
## 使用规则
|
||
|
||
1. 开工前读 `docs/current-state.md`(当前快照)与 `CLAUDE.md`(硬规则)。
|
||
2. 领取任务时把状态改为 `DOING`(同一时间最多 1 个)。
|
||
3. 标 `DONE` 前必须有**可运行证据**:验证命令和结果追加到 [`progress.md`](progress.md);只有"代码已写"不算完成。
|
||
4. 完成后:更新本文状态 → 追加 `progress.md` → 覆盖更新 `docs/current-state.md`。
|
||
5. 代码现实与看板冲突时,先说明冲突,不擅自跳步。
|
||
|
||
状态图例:`TODO` 待开始 · `DOING` 进行中 · `DONE` 完成并验收 · `BLOCKED` 受阻(注明原因)
|
||
|
||
---
|
||
|
||
## Phase B · 基线(接入 harness,先于一切新功能)
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-000 | 处理失效测试 `tests/test_query_health_record.py` | - | 它 import 的旧脚本 API 已删除(脚本已重写为硬编码版):要么删除,要么改写为校验新信封结构的最小测试;`pytest tests/` 通过或目录清空 | DONE |
|
||
|
||
## Phase 0 · 脚手架与契约骨架(路线图阶段 0)
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-001 | 初始化 go module + `main.go` 双子命令骨架 + `config/`(viper) | - | `go build ./...` 通过;`-mode server\|deliver` 可解析;`config.yaml.example` 占位值就绪 | DONE |
|
||
| T-002 | `osi/sign.go` MD5 头签名 + 单测 | T-001 | 单测校验 `password=md5("ts=<ts>&ask=<ask>")` 32 位小写;与 Python 脚本(scripts/,本地)产出比对一致 | DONE |
|
||
| T-003 | `osi/transport.go`:从 chis_upload 移植传输层 | T-001 | 保留 SOCKS5/超时;去掉 cookiejar 与网页拟态头;单测或最小连通验证 | DONE |
|
||
| T-004 | `osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go` | T-002, T-003 | 信封为 `serviceId`+`uploadinfo{baseInfo,manageInfo,...}`(docs/04 §8);成功码按去前导零 == `"1"` 判定(实测 `"01"`,docs/01 §1);`405` 归类可重试 | DONE |
|
||
| T-005 | 阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh` | T-004 | 用 JKDA00002(Python 已验证的同一查询)发真实请求拿到 `code="01"`;`./init.sh` 三命令替换完成,本机 Git Bash 运行限制见 progress 记录 | DONE |
|
||
| T-006 | Phase 0 review hardening | T-005 | 处理审核指出的 gzip 声明、传输层死代码、命令行 PII、Envelope 冗余;`go test ./...` 通过 | DONE |
|
||
|
||
> **执行顺序说明(2026-07-06 重排,查询先行)**:原路线图"阶段1字典 → 阶段2档案"的顺序假设了先建字典。
|
||
> 但**查询档案(Find)根本不依赖字典**,**映射纯函数单测**也只需码表 + 注入假字典快照。
|
||
> 真正需要字典查询接口(网格/责任医生/机构反查)的只有**真实创建闭环**。
|
||
> 故按能力重排为 Q(查询)→ M(映射)→ D(字典+创建);ID 保持不变,`osi/jkda.go` 的 Find 与 Create 拆成 T-203 / T-206。
|
||
> 字母命名的执行阶段(B/0/Q/M/D)区别于 Backlog 里的"路线图阶段 3~6"。
|
||
|
||
## Phase Q · 查询档案打通(Find,不依赖字典)
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-201 | `contract/jkda.go`:以 docs/04 §8 实测契约建**查询响应**结构体 | T-004 | Find 响应能完整反序列化本地联调样本(`data` 数组、null 字段用指针/omitempty、`adressNumber` 坑点拼写);创建请求结构体延到 T-206 补 | DONE |
|
||
| T-203 | `osi/jkda.go`:**Find + FindRqbj** | T-201 | 用 JKDA00002 真实请求打通,拿到 `code="01"` 与档案数据,与 Python 脚本结果一致 | DONE |
|
||
| T-208 | `server` 模式健康档案查询端点 `GET /api/health-record/find` | T-203 | server 模式起 HTTP 服务,curl 按 idCard/phrid/personName/empiId 查询返回平台完整档案 JSON;默认仅绑本机 | DONE |
|
||
| T-209 | 优化人群分类查询 JKDA00005(路径/契约/HTTP 端点) | T-203, T-208 | 按实测 `auto/jkda/findrqbj` 查询,响应结构含 `personSign/idCard/phrId`;server 模式提供人群分类查询端点并原样回写平台 JSON | DONE |
|
||
|
||
## Phase M · 映射层(码表 + 纯函数,注入假字典快照)
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 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 确定性生成单测 | DONE |
|
||
| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | DONE |
|
||
|
||
## Phase D · 字典服务与创建闭环(真实主数据反查)
|
||
|
||
> **读先行调整(2026-07-08)**:档案**创建/更新真实验收**(现拆为 T-215)依赖厂家写入授权 + 安全测试档案(docs/06 D3),当前锁死。
|
||
> 故把**其余业务线的查询**(Phase Q2)提到写入之前先做——读路径不被授权阻塞,且顺带摸清各业务线响应字段,为将来写入去风险。
|
||
> T-206 本地契约、客户端和映射组装代码已完成并通过测试;真实平台验收不再阻塞后续本地编排开发。
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-101 | `osi/public.go` 四个字典查询(网格/责任医生/药品/机构) | T-005 | 真实机构码能查到下级网格、责任医生、机构树 | DONE |
|
||
| T-103 | 字典缓存(内存 + redis 可选) | T-101, T-102 | 映射层能反查 `regionCode/manaDoctorId/manaUnitId`;redis 不可用不阻断 | DONE |
|
||
| T-210 | 公开查询 HTTP API(人群分类、网格地址、责任医生、药品目录、机构) | T-101, T-209 | server 模式暴露人群分类与四类公开查询 HTTP API;原样回写平台 JSON;同步 `docs/07` 与 `docs/openapi.yaml`;按内网部署场景明确绑定地址/鉴权边界 | DONE |
|
||
| T-211 | 药品目录查询按分页契约修正 YPML00001 | T-210 | 依 docx:`pageNo` 必填(去 `omitempty`+handler 缺则 400)、补 `pageSize`;`docs/07 §8`+`openapi.yaml` 标 pageNo 必填并补 pageSize;docx 契约未联调,注明待厂家样本核对(尤其 pageNo 是否真必填、响应 `ypxh/ypjl/ycjl` 字段)| DONE |
|
||
| T-206 | `osi/jkda.go`:**Create/Update 本地能力** + `contract/jkda.go` 写入请求结构体 | T-201, T-202, T-103 | JKDA00001/00003 serviceId、路径、uploadinfo 同级节点、响应解码和映射组装均有单测;`go test ./contract ./osi ./mapping` 通过 | DONE(真实写入验收拆至 T-215) |
|
||
| T-204 | `handler`+`router`:`POST /api/health-record/upsert` | T-213 | server 模式接收 PHIS 档案 DTO,调用同一 upsert 应用服务并返回结构化结果;handler 假依赖测试覆盖 create/update/manual_review/error,默认仍只绑定本机 | TODO(真实 curl 验收待 T-215) |
|
||
|
||
## Phase Q2 · 其余业务线查询(读先行)
|
||
|
||
> 每条查询任务都顺带把响应真实字段记入 `docs/04`(像 §8),作为将来对应业务线 create 映射的事实基线。
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-301 | 体检查询打通:`contract/jktj.go` + `osi/jktj.go`(最近一次 JKTJLSJL00002) | T-004 | 真实请求拿到 `code="01"` 与体检数据;响应字段(~260 项/7 节点)记入 docs/04 §11 | DONE(单条 JKTJ00002 平台未部署,见 §11) |
|
||
| T-302 | 体检 HTTP 查询端点(复用 T-208 handler 模式) | T-301 | server 模式 `GET /api/health-check/last`+`/all`+`/list` curl 返回完整 JSON;默认仅绑本机 | DONE |
|
||
| T-306 | 某人全部体检查询 JKTJ00002(`osi.QueryHealthChecks` + `/api/health-check/all`)| T-004 | 实测已部署,按 idCard 返回全部体检数组;驼峰 idCard、历史记录 checkId 可空,契约见 docs/04 §11.1 | DONE |
|
||
| T-303 | 老年人生活自理能力评估查询(LNRZLPG00002) | T-004 | 按厂家查询文档实现 `/auto/lnr/query`;按 idCard 查询返回评估数组;真实请求确认 serviceId/路径/字段 | BLOCKED(代码/单测/HTTP 文档完成;缺安全测试身份证做真实联调) |
|
||
| T-307 | 老年人中医体质辨识查询(LNRZYTZ00002) | T-004 | 取得厂家查询契约后实现并真实请求打通 | BLOCKED(待 docs/06 B4 补体质查询文档) |
|
||
| T-305 | 体检已检/未检名单查询 JKTJLIST00002(`osi` 方法 + 契约)| T-004 | `auto/jktjlist/query` 探针实测已部署(code=01 返回名单);`osi.ListHealthCheckPeople` 按 checkYear+idCard 返回名单(含 checkType 状态),契约见 docs/04 §11.4 | DONE |
|
||
| T-304 | 列表类查询(**档案 / 老年人自理·体质 / 中医指导** 列表,serviceId docx 缺漏)| T-004 | 各列表路径+serviceId 到位后返回分页数组 | BLOCKED(待 docs/06 B1/B2 厂家回填)|
|
||
|
||
## Phase U · PHIS 健康档案转换与 upsert
|
||
|
||
> 输入事实来源为本地 `payloads/441625198611255416_phis_health_record.json`,目标契约来源为
|
||
> `docs/统一对外服务接口文档.docx` 中 JKDA00001/JKDA00003,以及 `docs/01`、`docs/04` 的已整理契约。
|
||
> 两份本地原始材料均可能包含个人信息或凭据,只用于分析和生成脱敏测试夹具,不得提交 Git。
|
||
|
||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-212 | PHIS 健康档案真实结构建模与 PHIS→CHIS 转换器 | T-202, T-103 | 建模 PHIS `data.doctor/data.record/archId/businessId/empiId/phrId`;先确认稳定源主键语义并落 ADR;完整转换 JKDA 主体、既往史和生活环境;校验必填/长度/日期/码表;脱敏 fixture 回归;按档案责任医生生成请求操作上下文 | DONE |
|
||
| T-213 | 健康档案 upsert 应用编排(外部能力均接口注入) | T-212, T-203, T-206 | 按身份证查询 CHIS:0 条创建、1 条合规档案更新、多条/跨机构/不可更新状态转人工;查询失败不得降级创建;用假 OSI/幂等/report/PHIS 回写实现验收,不在本任务实现持久化 | DONE |
|
||
| T-215 | JKDA 健康档案真实 create/update 验收 | T-212, T-213 | 经明确授权的安全测试档案完成 create→query 回查→update→query 回查;确认 `phrId`、checkId、更新目标标识、`addressNumber`、责任医生/机构及重复提交语义,并回填 docs/03 §8、docs/04 §10 | BLOCKED(待写入授权和可写测试档案) |
|
||
|
||
### T-212 需求与解决方案
|
||
|
||
**需求**
|
||
|
||
- 接收 PHIS 健康档案响应,保留业务主键和档案数据;`doctor.sxtAccount/sxtPassword` 等凭据不得进入领域模型、日志、OSI 请求或测试夹具。
|
||
- 把 PHIS 扁平字段转换为 CHIS `uploadinfo` 下同级的 `healthRecord`、`pastHistory`、`jwsjb`、`jwsss`、`jwsws`、`jwssx`、`familyMiddle`。
|
||
- 支持字段改名:PHIS `adressNumber` → CHIS 写入字段 `addressNumber`。⚠ 该拼写仅有 docx 依据(查询响应实测是错拼 `adressNumber`,见 `contract/jkda.go` 查询结构体),**列为首次真实创建后的联调必核对项**:创建成功后回查该字段是否落库,若平台写入端实际收 `adressNumber` 则改名方向反转。
|
||
- 支持结构转换:`diseasetext_*` → 既往史对象/数组,`shhjCheck*` → 生活环境对象;多选值统一输出英文逗号。
|
||
- PHIS 未提供且 CHIS 非必填的 `phoneNumber/addressCode/homePlaceCode/insuranceType/personGroup` 不伪造值,按空值省略;后续若取得独立人群分类数据,再单独补充 `personGroup`。
|
||
- 不根据单个样例预设 `businessId` 或 `archId` 的优先级。先向 PHIS 契约/维护方确认 `archId/businessId/phrId/empiId` 的语义、生命周期和更新时是否稳定,再由 `ResolveSourceRecordKey` 统一选择稳定源主键。
|
||
- `checkId` 的同一源档案多次重试和多版本更新必须稳定;更新时间只用于版本审计。该调整会改变现有 `GenerateCheckID(..., src.UpdatedAt)` 行为,落地时必须同步修改调用、T-205 基线单测,并在 `docs/decisions/` 记录主键事实、选择规则、回退规则和兼容影响;主键语义未确认前不得把策略标记为最终完成。
|
||
- `manageInfo.DSFMC/operateUnit` 来自可信配置;`operateUser` 使用经责任医生字典验证的档案 `manaDoctorId`。若平台只允许固定配置医生,则必须校验二者一致,不得由客户端静默覆盖。
|
||
|
||
**解决方案**
|
||
|
||
- 在 `source/` 定义只含业务字段的 PHIS DTO,并提供 JSON 解码;对外层 `code/msg/compress` 和业务 `data` 分层建模。未知的医生账号/密码字段由解码器忽略,脱敏 fixture 放在 `source/testdata/` 或 `mapping/testdata/`,不放入整体忽略的 `payloads/`。
|
||
- 在 `mapping/` 保持纯函数转换:直传字段显式赋值,码表字段统一走 `dict.go`,主数据通过 `MapContext.MasterData` 校验/反查,嵌套结构由专用小函数组装。
|
||
- 扩充 `contract.HealthRecordCreateInfo`、`PastHistory` 和四类既往史结构,**范围只到 PHIS 实际提供 + 本期映射需要的字段**——不照搬 docx 写入字段全集(CLAUDE.md 铁律:docx 字段名/类型有前科,写入路径尚无联调样本兜底);docx 独有且 PHIS 给不出的字段不进结构体,确需预留的逐个标"待联调核对"。创建和更新复用同一业务数据契约,仅 serviceId/path 不同。
|
||
- 返回结构化 `ValidationError`;身份证、日期、字段长度、CHIS 必填项、枚举和多选码未通过时禁止调用 OSI。
|
||
- 建立逐字段映射表并由表驱动测试覆盖:主体直传字段;`diseasetext_check_gm/check_bl/check_fq/CheckMQ/CheckXDJM/CheckZN/RedioYCBS/CheckCJ` → `pastHistory`;`diseasetext_radio_jb/ss/ws/sx` → 四类数组;`shhjCheckCFPFSS/RLLX/YS/CS/QCL` → `familyMiddle`。
|
||
- 既往史代码非空时生成对应节点(包括“无”代码),空值省略;名称/日期未提供时不伪造。`isFillShhj=n` 时省略 `familyMiddle`,为 `y` 时才校验并组装生活环境。真实写入后由 T-215 校准平台对“无”代码节点的最终要求。
|
||
- 从真实 payload 派生脱敏 fixture,覆盖完整转换、可选字段省略、未知码值、缺失必填、空既往史、生活环境未填写、稳定主键跨版本不变,以及人工构造的主键缺失/回退场景。
|
||
|
||
**T-212 落地结论(2026-07-16)**:参考 PHIS worker 回调契约与多业务样例后,确认健康档案使用 `archId` 作为稳定源键,`businessId` 只用于追踪/回写;`archId` 缺失直接失败,不做危险回退。详见 `docs/decisions/001-phis-health-record-source-key.md`。
|
||
|
||
### T-213 upsert 规则
|
||
|
||
1. 用 PHIS `record.idCard` 调用 JKDA00002 查询,只有明确成功响应才允许判断记录数量。
|
||
2. 查询结果为 0 条:构造 JKDA00001 创建请求;查询结果为 1 条时,先校验身份证精确一致、档案状态可更新、目标机构/责任医生符合授权范围,再构造 JKDA00003 更新请求。
|
||
3. 查询结果超过 1 条、跨机构或状态不可更新时停止投递并标记人工处理,禁止任选一条更新。
|
||
4. 查询网络错误、超时、平台业务错误或响应无法解析时进入可重试/失败流程,禁止按“不存在”执行创建。
|
||
5. 更新失败不得自动回退创建;创建失败也不得自动改走更新,避免平台状态不明时产生重复档案。
|
||
6. 更新目标单独建模为 `UpdateTarget`,保留查询返回的 `phrId/empiId/checkId/manaUnitId/status`;仅把 T-215 真实契约确认允许的标识写入更新请求,不用 PHIS 空值覆盖平台已有值。
|
||
7. 以稳定 `checkId` 和身份证建立幂等/并发保护;成功事件只记录脱敏后的 `phrId`、serviceId、响应码和 trace_id,禁止把原始 payload、OSI 完整请求/响应写入普通日志。
|
||
8. 内部状态定义为 `done/retry/failed/manual_review`,只有 CHIS 明确成功码才标 `done`。T-213 只定义并注入 `IdempotencyStore/ReportSink/PHISStatusWriter` 接口,用内存假实现验证编排;Redis/文件持久化、重试调度和熔断统一留给路线图阶段 3。
|
||
9. PHIS 真实回写由 T-214 实现;内部状态与 PHIS 实际支持状态分开建模,由适配器完成映射,不能预设 PHIS 原生支持 `manual_review`。
|
||
10. T-213 提供可被 HTTP handler 和未来 `deliver-worker` 复用的单一应用服务;T-204 只做 HTTP 适配,不复制 upsert 逻辑。
|
||
|
||
**T-213 落地结论(2026-07-16)**:`pipeline.HealthRecordUpsertService` 已实现 query-first 编排;幂等接口采用带 owner token 的原子 `Acquire/Complete/Release`,并发处理中返回 `retry/skip`,避免两个 worker 同时创建和过期 worker 释放新租约。JKDA 查询只有明确返回非 nil 空数组才创建,`data:null`/缺失拒绝写入。单条更新会单独建模 `UpdateTarget`,仅合并查询返回的 `phrId`,保留源侧稳定 `checkId`;状态、机构、责任医生、身份证或 `phrId` 不安全时进入 `manual_review`。持久化、HTTP 入口和 PHIS 真实状态适配仍分别归阶段 3、T-204、T-214。
|
||
|
||
## 里程碑
|
||
|
||
- M1 = T-005:Go 客户端与平台真实握手成功(签名/信封/判码全对)。
|
||
- M2 = T-203:**Go 版查询档案打通**(Find,不碰字典)。
|
||
- M3 = T-205:映射层就绪 + 单测基线(假字典快照)。
|
||
- M4 = T-301:**体检查询打通**(其余业务线读先行第一条)。
|
||
- M5 = T-215:真实 upsert 闭环(授权到位后,PHIS 转换 → create/query/update → 回查确认)。
|
||
|
||
## 待办池(Backlog,按路线图阶段 3~6 展开,进入时再拆小任务)
|
||
|
||
- 阶段 3:投递流水线(retry/idempotency/circuit/report + `-mode deliver`)。
|
||
- 阶段 4:其余业务线(体检 JKTJ 字段最多、老年人自理、中医体质、中医指导——B1/B2/B5 契约到位后拆)。
|
||
- 阶段 5:PHIS 真实接入与状态回写。
|
||
- 阶段 6:加固与交接(完整度定论、密钥环境变量化、运维文档、全绿)。
|
||
- 联调依赖跟踪见 `docs/06-厂家联调清单.md`(B 组契约缺口会阻塞阶段 4)。
|
||
|
||
### 已登记的延期任务(带 ID,条件满足即提升到对应 Phase)
|
||
|
||
| ID | 任务 | 触发条件 | 验收要点 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| T-007 | 传输层支持 https/TLS | **生产环境地址确认为 https 时必须先做**(沙箱是 http,不阻塞当前开发) | `osi/transport.go` 裸 HTTP/1.1 写目前仅支持 http(明文 TCP、`scheme != "http"` 直接报错)。需在 TLS 下同样保留 `orgCode/deviceSN/userName` 头名大小写策略;对 https 目标能发起真实请求并拿到 `code`;单测覆盖 https 路径 | TODO(生产前 gate) |
|
||
| T-214 | PHIS 状态真实回写(替换 T-213 假实现) | PHIS 回写接口契约到位(路线图阶段 5,回写接口/字段/状态码确认后启动) | 实现 T-213 的 `PHISStatusWriter`:把内部 `done/retry/failed/manual_review` 映射为 PHIS 实际支持的状态/字段;回写失败不覆盖本地最终状态并可补偿重试;日志脱敏 | TODO(依赖 PHIS 侧契约) |
|
||
|
||
> 来源:`docs/review/2026-07-06-phase0-review.md` P1-2。向厂家确认生产地址协议(见 `docs/06` A10)后,若为 https 则本任务提升为阻断项;若确认生产仍是 http,则关闭本任务并在传输层注释说明。
|