Files
chis_osi/tasks.md
T

163 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务看板(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,则关闭本任务并在传输层注释说明。