2026-07-06 20:09:54 +08:00
# 执行进度记录
> 只追加的历史流水:每轮做了什么、跑了什么验证、遇到什么阻塞、做了什么决策。
> 当前目录/命令/下一步等可覆盖快照写 [`docs/current-state.md`](docs/current-state.md),不在本文重复。
> 任务状态以 [`tasks.md`](tasks.md) 为准。
## 记录格式
```markdown
## YYYY-MM-DD T-编号 任务名(或事件名)
- 状态:DONE / BLOCKED / PARTIAL
- 变更:改了哪些文件或模块
- 验证:运行的真实命令和结果
- 阻塞:如有,写明原因和需要谁决策
- 决策:如有,记录本轮确定的关键取舍
- 下一步:建议下一个任务 ID 或待确认事项
```
## 执行记录
## 2026-05~06 设计文档集初始化(pre-code)
- 状态:DONE
- 变更:`docs/01~06` 设计文档集、`CLAUDE.md` 、`.gitignore` (源材料不入库)。详见 `git log 3498542..1fd2569` 。
- 决策:扁平目录布局对齐 chis_upload;OSI 为无状态 MD5 签名接口,不引入旧项目逆向复杂度;幂等键用 checkId。
## 2026-07-06 JKDA00002 联调打通 + 文档校准
- 状态:DONE
- 变更:`scripts/query_health_record.py` (本地,不入库)打通真实查询;据实测样本校准 `docs/01` (成功码/信封/机构码分层/坑点坐实)、`docs/04` (新增 §8 实测契约)、`docs/06` ( A1~A9/B3/B6/C3/D2/D3 回填)。
- 验证:沙箱真实请求返回 `code="01" message="操作成功"` , `data` 为档案聚合数组;脚本离线自检(编译 + 信封 + 判码逻辑)通过。
- 决策:**成功码为 `"01"` 非 docx 所示 `"1"` **,判定按去前导零;**查询也走 `uploadinfo` 信封**;请求头 `orgCode` 用 18 位统一社会信用代码(9 位码是 `manaUnitId` ,两者不可混用)。
- 阻塞:`tests/test_query_health_record.py` 测的是旧脚本 API,已失效(import 报错)→ 登记为 T-000。
- 下一步:T-000,然后 T-001 起步阶段 0。
## 2026-07-06 接入 harness coding 文档工件
- 状态:DONE
- 变更:新建 `tasks.md` (看板)、`progress.md` (本文)、`docs/current-state.md` (快照)、`init.sh` (统一验证入口)、`AGENTS.md` (通用 agent 薄入口);更新 `CLAUDE.md` 会话启动流程与文档同步表、`docs/README.md` 导航、`docs/05` 成功码勘误。
- 决策:任务看板放根目录 `tasks.md` ;现有 `docs/01~06` 设计文档不重命名不重排;模板可选增强集(rubric/quality/method-map)暂不引入。
- 下一步:T-000。
2026-07-06 20:46:37 +08:00
## 2026-07-06 T-000 处理失效测试
- 状态:DONE
- 变更:删除 `tests/` ( `test_query_health_record.py` 及 `__pycache__` )——文件从未被 git 跟踪,无需 `git rm` 。
- 验证:`git status --short` 确认删除前后均无该目录的跟踪历史;删除后工作区干净。
- 决策:不改写为新契约的最小测试,直接删除——脚本是硬编码的本地联调工具(不入库),暂不需要自动化测试覆盖。
- 下一步:T-001,进入路线图阶段 0(go module + 双子命令骨架)。
2026-07-06 20:54:23 +08:00
## 2026-07-06 T-001 初始化 Go module 与双子命令骨架
- 状态:DONE
- 变更:新增 `go.mod` /`go.sum` 、`main.go` 、`main_test.go` 、`config/config.go` 、`config/config_test.go` 、`config.yaml.example` ; `tasks.md` 标记 T-001 完成;`docs/05` 勾选阶段 0 第一项。
- 验证:`go test ./...` 通过;`go build ./...` 通过;`go run . -mode server -config config.yaml.example` 输出 `chis_osi mode=server` ; `go run . -mode deliver -config config.yaml.example` 输出 `chis_osi mode=deliver` 。
- 验证补充:`bash init.sh` 在当前 Windows 环境失败,系统提示未安装 WSL;已用等价 Go 命令完成验收。
- 决策:Go module 使用本地模块名 `chis_osi` ; `main.go` 只打印 mode,不打印配置值,避免泄露真实环境信息;`config.yaml.example` 只保留占位值。
2026-07-06 21:52:56 +08:00
- 下一步:T-002( `osi/sign.go` MD5 头签名 + 单测)。
## 2026-07-06 T-002 MD5 头签名
- 状态:DONE
- 变更:新增 `osi/sign.go` 与 `osi/sign_test.go` ; `BuildHeaders` 组装 OSI 必需请求头,`SignPassword` 按 `md5("ts=<ts>&ask=<ask>")` 输出 32 位小写;`tasks.md` 标记 T-002 完成;`docs/05` 勾选阶段 0 第二项。
- 验证:先运行 `go test ./osi` 看到缺少 `SignPassword` /`BuildHeaders` /`HeaderInput` 的预期失败;实现后 `go test ./osi` 通过;`go test ./...` 通过;`go build ./...` 通过。
- 决策:固定测试向量 `ts=1700000000123&ask=secret-key -> 008aceff8247cb42d2a99b2c48d0ac88` 来自 Python `hashlib.md5(...).hexdigest()` ,用于对齐本地联调脚本签名算法;`ask` 只参与签名,不进入 headers。
- 下一步:T-003( `osi/transport.go` 传输层,保留 SOCKS5/超时)。
## 2026-07-06 看板重排(查询先行)
- 状态:DONE(规划调整,无代码)
- 变更:`tasks.md` 把 Phase 1(字典)/Phase 2(档案) 重排为 Phase Q(查询)→M(映射)→D(字典+创建);`osi/jkda.go` 拆为 T-203(Find/FindRqbj) 与 T-206(Create/Update); `docs/05` 加执行顺序注记。任务 ID 不变。
- 决策:查询档案(Find)不依赖字典,映射纯函数单测只需码表+注入假字典快照,只有真实创建闭环才需 T-101/T-103 字典反查——故把字典接口降到创建之前、查询之后。顺带把"Create 依赖字典缓存"从隐性依赖显式化到 T-206。
- 下一步:仍是 T-003( Phase 0 未变),Phase 0 完成后按 Q→M→D 领取。
2026-07-06 22:23:01 +08:00
## 2026-07-06 T-003 OSI 传输层骨架
- 状态:DONE
- 变更:新增 `osi/transport.go` 与 `osi/transport_test.go` ;传输层支持 JSON POST、请求超时、可选 SOCKS5 代理;不设置 Cookie/Origin/Referer/X-Requested-With 等网页拟态头;`tasks.md` 标记 T-003 完成;`docs/05` 勾选阶段 0 传输层项。
- 验证:先运行 `go test ./osi` 看到 `NewTransport` /`TransportConfig` 未定义的预期失败;实现后 `go test ./osi` 通过;`go test ./...` 通过;`go build ./...` 通过。
- 决策:`socks5_proxy` 支持 `host:port` 和 `socks5://host:port` 两种写法;非空时只配置 OSI 客户端代理,不做直连回退;传输层只返回 HTTP status 和原始响应体,业务判码留给 T-004 的 client/codes。
2026-07-06 23:03:14 +08:00
- 下一步:T-004( `osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go` )。
2026-07-06 22:31:02 +08:00
## 2026-07-06 T-004 OSI Client Call + 信封 + 判码
- 状态:DONE
- 变更:新增 `contract/envelope.go` 、`osi/codes.go` 、`osi/client.go` 及对应单测;`Client.Call` 负责拼 `/osi/api` 路径、组装 MD5 签名请求头、构造 `serviceId + uploadinfo{baseInfo,manageInfo}` 信封、发送 JSON、解析 `{code,message,data}` ; `tasks.md` 标记 T-004 完成;`docs/05` 勾选 client/codes/envelope 两项。
- 验证:先运行 `go test ./contract ./osi` 看到 `Envelope` 、`NewClient` 、`IsSuccessCode` 等未定义的预期失败;实现后 `go test -count=1 ./contract ./osi` 通过;`go test ./...` 通过;`go build ./...` 通过。
- 决策:成功码统一按去前导零后等于 `"1"` 判定,兼容实测 `"01"` ; `405` 归类为可重试;`osi.base_url` 按主机地址处理,Client 自动拼 `/osi/api` ,同时兼容已带 `/osi/api` 的输入。
2026-07-06 23:03:14 +08:00
- 下一步:T-005(阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh` )。
## 2026-07-06 T-005 阶段 0 验收:Go 侧真实请求打通 + init.sh
- 状态:DONE
2026-07-06 23:20:42 +08:00
- 变更:新增 `verify_jkda.go` /`verify_jkda_test.go` , `main.go` 增加 JKDA00002 验证入口(T-006 后改为 `-verify-jkda` + `OSI_VERIFY_ID_CARD` );`config` 支持 `OSI_*` 等环境变量覆盖;`osi/transport.go` 改为按原始 HTTP/1.1 写请求以保留 `orgCode/deviceSN/userName` 头名大小写;`init.sh` 配置依赖下载、`go test ./...` 、启动三步命令;补充传输层头名与基础请求头单测。
2026-07-06 23:03:14 +08:00
- 验证:`go test ./...` 通过;`go build ./...` 通过;使用本地环境变量发起 Go 版 JKDA00002 真实请求,返回 `JKDA00002 code=01 message=操作成功 data_count=1` 。
- 验证补充:`init.sh` 三命令已替换;当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,导致脚本运行环境未能完整验收。等价 PowerShell 下直接执行 `go test ./...` 、`go build ./...` 与 Go 真实请求均通过。
- 决策:真实平台/代理对请求头大小写敏感,Go `net/http` 会规范化头名并触发 EOF;传输层因此对 OSI 调用使用原始 HTTP/1.1 写入,保留 Python requests 已验证的头名大小写与基础头语义。
- 下一步:T-201( `contract/jkda.go` 查询响应结构体)。
2026-07-06 23:20:42 +08:00
## 2026-07-06 T-006 Phase 0 review hardening
- 状态:DONE
- 变更:`osi/transport.go` 默认 `Accept-Encoding` 改为 `identity` ,避免声明 gzip/br 却不解压;删除传输层未使用的 `http.Client` /roundTripper 残留;`main.go` 验证入口改为 `-verify-jkda` 并从 `OSI_VERIFY_ID_CARD` 读取身份证;删除 `contract.Envelope` 顶层 `BaseInfo` ,保留 `uploadinfo.baseInfo` ;同步 `docs/03` 、`docs/current-state.md` 与 `tasks.md` 。
- 验证:按 TDD 先看到 `TestTransportRequestsIdentityEncoding` 、`TestEnvelopeDoesNotExposeTopLevelBaseInfo` 、`TestResolveVerifyIDCard*` 失败;实现后目标测试通过;`go test ./...` 通过;`go build ./...` 通过;`git diff --check` 通过。
- 决策:Phase 0 先避免压缩响应风险,不实现 gzip/br 解压;https 支持仍按当前沙箱事实 fail-fast,生产若切 https 需另起任务处理 TLS 下的头名保留策略。
- 下一步:T-201( `contract/jkda.go` 查询响应结构体)。
2026-07-06 23:23:16 +08:00
## 2026-07-06 复评 T-006 + 登记延期任务 T-007
- 状态:DONE(评审与看板维护)
- 变更:复评 T-006 对 `docs/review/2026-07-06-phase0-review.md` 的修复情况;`tasks.md` 新增延期任务 **T-007(传输层 https/TLS 支持,生产前 gate) ** ,落地 review P1-2 的"延期须登记"要求。
- 复评结论:T-006 认领的 4 项(gzip 声明/传输死代码/命令行 PII/Envelope 冗余)均已修复并补测;review 其余项中——P1-2 https 以 T-007 登记延期;P2-2(裸写根因抓包对照)、P3(nil-transport 静默兜底、UA 写死、init.sh 本机不通)仍未处理,作为后续小整改候选。P1-1(未提交即标 DONE)本轮 T-005/T-006 均已补交、不再漂移。
- 决策:https 是否阻断取决于生产地址协议(挂钩 docs/06 A10);沙箱 http 不阻塞当前开发,故 T-007 置于 Backlog 而非当前 Phase。
- 下一步:T-201。
2026-07-07 09:35:03 +08:00
## 2026-07-07 T-201 JKDA00002 查询响应契约
- 状态:DONE
- 变更:新增 `contract/jkda.go` 与 `contract/jkda_test.go` ;按 `docs/04 §8` 建立 `FindHealthRecordResponse` 、`FindHealthRecord` 、`HealthRecord` 、`PastHistory` 、既往疾病/手术/外伤/输血数组和 `FamilyMiddle` 查询响应结构体;保留响应侧错拼字段 `adressNumber` ; `checkId` 、`insuranceType` 、`otherPersonGroup` 等 nullable 字段使用指针。
- 验证:按 TDD 先看到 `go test ./contract -run TestFindHealthRecordDeserializesMeasuredJKDA00002Data -count=1` 因 `FindHealthRecord` 未定义失败;补结构体后通过。随后看到 `go test ./contract -run TestFindHealthRecordResponseDeserializesDataArray -count=1` 因 `FindHealthRecordResponse` 未定义失败;补响应结构体后通过。最终 `go test ./contract -count=1` 通过,`go test ./...` 通过。
- 决策:T-201 只建查询响应契约;创建/更新请求结构体仍按看板留到 T-206,避免提前把未联调字段固化。
- 下一步:T-203( `osi/jkda.go` : Find + FindRqbj)。
2026-07-07 10:14:40 +08:00
## 2026-07-07 T-203 JKDA Find / FindRqbj
- 状态:DONE
- 变更:新增 `osi/jkda.go` 与 `osi/jkda_test.go` ;实现 `Client.FindHealthRecord` ( JKDA00002 `/auto/jkda/find` )和 `Client.FindRqbj` ( JKDA00005 `/jkda/findrqbj` );查询入参按单一 key 校验,支持 `idCard` 、`empiId` 、`phrid` 、`personName` ( Find)以及 `idCard` /`phrid` ( FindRqbj);`verify_jkda.go` 改为复用正式 Find 方法。
- 验证:按 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 做真实请求验收,因为本地已有可查档案样本。
- 下一步:T-102( `mapping/dict.go` 全量码表)。
2026-07-07 14:13:09 +08:00
## 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` )。
2026-07-07 14:36:40 +08:00
## 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 样例 + 联调样本)。
2026-07-07 14:55:16 +08:00
## 2026-07-07 T-205 映射单测基线
- 状态:DONE
- 变更:新增 `mapping/health_record_baseline_test.go` ;补 docx 风格完整样例、联调样本风格脱敏样例、脏数据样例三组映射回归测试,覆盖主数据字段映射、码表转换、必填校验、码表未命中和 checkId 稳定生成。
- 验证:`go test ./mapping -count=1` 通过;`go test ./...` 通过。
- 决策:样本全部脱敏,不提交本地真实身份证/姓名/电话;T-205 只建立映射测试基线,不扩展创建请求结构体。
- 下一步:T-101( `osi/public.go` 四个字典查询)。
2026-07-07 20:58:13 +08:00
## 2026-07-07 T-101 公开字典查询
- 变更:新增 `osi/public.go` ,实现 `QueryGridAddress` 、`QueryDoctors` 、`QueryDrugs` 、`QueryOrgs` 四个公开查询薄封装;补充 `WGDZ00001` 、`ZRYS00001` 、`YPML00001` 、`CXJG00002` serviceId 路由。
- 测试:先补 `osi/public_test.go` ,覆盖四个接口的 path、serviceId、baseInfo 组装与 data 解码。
- RED: `go test ./osi -run "TestQuery(GridAddress|Doctors|Drugs|Orgs)" -count=1` 初次失败,缺少公开查询类型、serviceId 常量与方法。
- GREEN: `go test ./osi -run "TestQuery(GridAddress|Doctors|Drugs|Orgs)" -count=1` 通过。
- 验证:`go test ./osi -count=1` 通过。
- 验证:`go test ./...` 通过。
- 验证:`go build ./...` 通过。
- 联调:使用本地未入库脚本中的沙箱凭据和身份证,仅在临时 Go 程序内读取;先 JKDA00002 取一条档案主数据,再调用公开查询。结果:Find `code=01 count=1` ;责任医生 `code=01 count>0` ;机构 `code=01 count>0` ;网格 `code=01 count>0` 。未输出真实身份证、机构码、医生姓名或密钥。
- 说明:药品目录查询本次完成客户端封装和单测,T-101 验收要求未要求真实药品目录联调;后续需要用药品关键字/拼音码时再补真实样本。
2026-07-07 21:16:52 +08:00
## 2026-07-07 T-103 字典缓存
- 状态:DONE
- 变更:新增 `cache/dictionary.go` 与 `cache/dictionary_test.go` ;建立 `DictionarySnapshot` 内存快照,支持按名称反查 `regionCode` 、`manaDoctorId` 、`manaUnitId` ;新增 `DictionaryService.Refresh` ,从 T-101 公开查询结果刷新内存快照,并通过可选 `PersistentStore` 保存。
- 变更:`mapping.PHISHealthRecord` 增加 `RegionName` 、`ManaDoctorName` 、`ManaUnitName` ; `MapContext` 增加 `MasterDataLookup` ,当 PHIS 未直接提供平台码时,映射层可通过主数据快照反查补齐必填主数据。
- RED: `go test ./cache -count=1` 初次失败,缺少 `NewDictionarySnapshot` 、`NewDictionaryService` 、`RefreshParams` 、`DictionarySnapshot` ; `go test ./mapping -run TestMapHealthRecordUsesMasterDataLookupWhenCodesMissing -count=1` 初次失败,缺少名称字段与 `MasterData` 上下文。
- GREEN:补最小实现后,`go test ./cache -count=1` 通过;`go test ./mapping -run TestMapHealthRecordUsesMasterDataLookupWhenCodesMissing -count=1` 通过。
- 验证:`go test ./cache ./mapping -count=1` 通过。
- 验证:`go test ./...` 通过。
- 验证:`go build ./...` 通过。
- 决策:T-103 不引入 Redis 客户端依赖,只定义可选持久化接口;持久化失败被忽略,内存快照仍刷新,满足“redis 不可用不阻断”。
- 下一步:T-206(健康档案 Create/Update + 创建请求契约)。
2026-07-07 23:11:23 +08:00
## 2026-07-07 T-206 健康档案 Create/Update(代码完成,真实写入受阻)
- 状态:BLOCKED(代码与本地验证完成;真实 create 写入平台待安全测试档案/写入授权)。
- 变更:`contract/jkda.go` 新增 `HealthRecordCreate` 、`HealthRecordBaseInfo` 、`HealthRecordCreateInfo` 、`HealthRecordSaveResult` ;创建请求侧使用 `addressNumber` ,保留查询响应侧 `adressNumber` 。
- 变更:`osi/codes.go` 新增 `JKDA00001` /`JKDA00003` 与 `/jkda/create` 、`/jkda/update` 路由;`osi/client.go` 增加内部 `callUploadInfo` ,支持创建/更新把 `healthRecord/pastHistory/...` 放在 `uploadinfo` 同级节点;`osi/jkda.go` 新增 `CreateHealthRecord` 、`UpdateHealthRecord` 。
- 变更:`mapping.BuildHealthRecordCreate` 将 `MappedHealthRecord` 转为创建请求契约,保留确定性 `checkId` 与 `regionCode/manaDoctorId/manaUnitId` ,并派生 `createUser/createUnit` 。
- RED: `go test ./contract -run TestHealthRecordCreateSerializesCreateFieldNames -count=1` 初次失败,缺少创建请求结构体;`go test ./osi -run "Test(Create|Update)HealthRecord" -count=1` 初次失败,缺少 create/update 方法与 serviceId; `go test ./mapping -run TestBuildHealthRecordCreateUsesMappedFieldsAndCheckID -count=1` 初次失败,缺少构建函数。
- GREEN:补最小实现后上述三组目标测试通过。
- 验证:`go test ./contract ./osi ./mapping -count=1` 通过。
- 验证:`go test ./...` 通过。
- 验证:`go build ./...` 通过。
- 阻塞:未执行真实 `JKDA00001` create。原因是当前只有真实查询样本,没有明确的安全测试居民/身份证和写入授权;直接用现有真实样本创建/更新可能污染平台档案或触发重复建档。解除条件:提供可写入沙箱的测试居民资料,或明确授权使用某条测试数据做 create。
2026-07-08 00:05:23 +08:00
## 2026-07-08 T-208 server 模式健康档案查询端点
- 状态:DONE
- 变更:新增 `handler/health_record.go` ( `GET /api/health-record/find` ,回写平台完整响应,不裁字段)、`handler/health_record_test.go` (完整回写 + 缺标识符 400)、`server.go` ( server 模式起 HTTP 服务 + 路由)、`osi_client.go` (抽出 `buildOSIClient` 共用构造);`verify_jkda.go` 改用共用构造消除重复;`main.go` 加 `-addr` 、server 模式真正启动服务;`tasks.md` /`current-state.md` 同步。
- 验证:用户在 Windows 侧 `go test ./...` 、`go build ./...` 通过;`go run . -mode server` 起服务后 `curl .../find?idCard=<..>` 返回平台完整档案 JSON。(本 WSL 离线无 go 工具链,未在本环境复跑。)
- 决策:查询端点直接回写 `Result.Raw` (平台原始 `{code,message,data}` ),保证未建模字段不丢失;默认监听 `127.0.0.1:8080` 而非 `:8080` ,因端点返回真实档案 PII,避免绑 0.0.0.0 暴露到局域网;身份证经 URL query 传入,仅作本机查看工具,勿反代外网。
- 下一步:解 T-206 阻塞(真实 create),或按需为查询端点加鉴权后再考虑对外。
2026-07-08 00:18:52 +08:00
## 2026-07-08 看板重排(读先行:查询优先、写入后置)
- 状态:DONE(规划调整,无代码)
- 变更:`tasks.md` 新增 Phase Q2(其余业务线查询:T-301 体检查询 / T-302 体检端点 / T-303 老年人查询 / T-304 列表查询),把写入 T-206(BLOCKED)/T-204 明确降到 Q2 之后;里程碑加 M4=体检查询、M5=创建闭环。`docs/06` 顶部加"★ 当前批次优先催办",把写入授权(D3)+缺失 serviceId(B1/B2/B4)+写入规则(C1/C2) 归拢成一封邮件一起催。`docs/current-state.md` 下一步改为 T-301 起。
- 决策(全栈分析):真实 create 被厂家写入授权外部锁死,垂直切片走不通;改按读/写横切、读先行——读路径不被授权阻塞、只读零风险、且各查询响应是将来写入映射的事实侦察。T-206 创建代码保留不作废,授权到位再验收。
- 下一步:T-301 体检查询打通。
2026-07-08 00:45:47 +08:00
## 2026-07-08 T-301 体检查询打通(最近一次)
- 状态:DONE
- 变更:新增 `contract/jktj.go` ( HealthCheckSummary 定位字段)、`osi/jktj.go` ( `LastHealthCheck` JKTJLSJL00002)、`osi/jktj_test.go` (脱敏假数据测路径/解码/Raw/idcard 小写映射/单键校验);`osi/codes.go` 登记 JKTJLSJL00002 + JKTJLIST00002 路由;`docs/04` 新增 §11 体检字段清单(~260 项/7 节点),`docs/01 §5.2` 标注各体检接口联调状态。
- 验证:真实"最近一次体检"查询已通(用户侧 Python 脚本 code=01,返回单个体检对象)。Go 侧因本环境离线无工具链未复跑,需用户 `go test ./...` 确认编译与单测。
- 发现:① 单条 JKTJ00002 `auto/jktj/query` 平台实测"没有url的接口配置"(未部署),`auto/jktj/find` 待确认 → docs/06 B5。② 体检 data 为单对象(档案 find 是数组)。③ 体检身份证字段小写 `idcard` (档案 `idCard` )。④ 体检主键 `checkId` (与档案幂等 checkId 同名不同义),子节点 `healthCheck` 外键指向它。
- 决策:查询只强类型化定位字段,完整内容留 `Result.Raw` ;~260 全量字段建模留到体检 create(写入才需逐字段,避免照单样本猜 null 类型)。
- 下一步:T-302 体检 HTTP 查询端点(复用 T-208 handler 模式);或按 docs/06 催厂家确认单查/列表接口。
2026-07-08 21:02:26 +08:00
## 2026-07-08 拆分 T-305 体检名单查询、收窄 T-304
- 状态:DONE(看板+文档维护)
- 变更:从 docx 扒出 JKTJLIST00002 完整契约(checkYear/page/rows 在 baseInfo,返回人员名单+checkType 状态),记入 `docs/04 §11.4` ; `tasks.md` 拆出 **T-305(体检已检/未检名单,契约齐全仅待沙箱部署验证,TODO 可领)** ,把 T-304 收窄为真正缺 serviceId 的列表(档案/老年人/中医指导);`scripts/query_health_check.py` 的 list 模式改为 docx 正确参数。
- 决策:JKTJLIST00002 契约不缺(docx 完整),不该被 B1/B2 阻塞——只差运行时确认端点是否部署,故独立成可领任务;纠正原 T-304"一刀切 BLOCKED"把能做的和真卡的混在一起。它是名单/进度接口,不含体检明细,非数据源。
- 下一步:跑 list 探针确认部署 → 若通即领 T-305 建 osi 方法;未部署则归 docs/06 B5 催厂家。
2026-07-08 21:11:17 +08:00
## 2026-07-08 T-305 体检已检/未检名单查询打通
- 状态:DONE
- 变更:`contract/jktj.go` 加 `HealthCheckPerson` (名单一行 20 字段,字段名按实测校正);`osi/jktj.go` 加 `ListHealthCheckPeople` ( JKTJLIST00002) + `ListHealthCheckQuery` ( checkYear 必填、page/rows 默认、idCard/checkType 可选);`osi/jktj_test.go` 加名单解码/路径/baseInfo分页/checkYear 校验测试(脱敏假数据);`docs/04 §11.4` 校正实测字段名并标端点已部署;`docs/01 §5.2` 标 JKTJLIST00002 联调可用。
- 验证:Python 探针 `list` 实测 `code=01` ,返回 10 人名单(每人 checkType 状态)。Go 侧待用户 `go test ./...` 复验(本环境离线)。
- 发现:实测字段名与 docx 多处不符——`birthday` (非 birthDay)、`phrId` (非 phrid,驼峰)、`manaDoctorId` (非 manadoctorId),另有 docx 未列的 `manaDocterName` (拼写 Docter)/`empiId` /`manaUnitText` 。再次印证以实测样本为准。
- 决策:名单行字段稳定且均为字符串(20 项),全部强类型化(区别于体检明细 260 项只留 Raw);单查 JKTJ00002 仍未部署,与本条无关。
- 下一步:T-302 体检 HTTP 端点;或按需把名单也暴露成 HTTP 端点。
2026-07-08 21:17:02 +08:00
## 2026-07-08 T-302 体检 HTTP 查询端点
- 状态:DONE
- 变更:新增 `handler/health_check.go` ( `GET /api/health-check/last` 最近一次 + `/list` 年度名单,回写平台完整响应)、`handler/health_check_test.go` (脱敏假 OSI 后端测 last/list 的路径/原样回写/缺参 400);`server.go` 注册两条体检路由并打印;抽 `writeRawOrError` 共用回写。`tasks.md` /`docs/current-state.md` 同步。
- 验证:待用户 `go test ./...` 、`go build ./...` 复验(本环境离线无工具链)。
- 决策:端点直接回写 `Result.Raw` (同档案端点 T-208),完整内容不裁字段;last 按 idCard/phrid/empiId, list 按 checkYear(必填)+idCard+checkType;默认仅绑 127.0.0.1(返回真实档案/名单 PII)。
- 下一步:T-303 老年人查询探针。
2026-07-08 22:14:39 +08:00
## 2026-07-08 JKTJ00002 某人全部体检查询打通
- 状态:DONE
- 变更:`osi/codes.go` 登记 `ServiceIDJKTJQuery=JKTJ00002` → `/auto/jktj/query` ; `contract/jktj.go` 加 `HealthCheckRecordSummary` (驼峰 idCard、checkId 可空);`osi/jktj.go` 加 `QueryHealthChecks` (返回数组);`handler/health_check.go` 加 `/api/health-check/all` 端点;`server.go` 注册路由;`osi/jktj_test.go` /`handler/health_check_test.go` 加测试(含 null checkId 场景);`docs/01 §5.2` 、`docs/04 §11.1` 校准。
- 验证:Python 探针实测 JKTJ00002 `code=01` ,返回 8 条体检历史(2013–2026)。Go 侧待用户 `go test ./...` 。
- 发现(实测校准):① JKTJ00002 身份证字段是**驼峰 `idCard` **(≠ 最近一次小写 `idcard` ;体检三接口 last 独用小写);② ** `checkId` 仅近年记录有值,历史记录为 `null` **(本样本仅最近 2 条有编号)——历史体检只能按 `checkDate` 定位;③ 数组元素结构同 §11.3。
- 决策:数组查询只强类型化定位字段(checkId/checkDate/idCard/personName),完整体检走 Result.Raw;单独建 `HealthCheckRecordSummary` (驼峰 idCard)不复用最近一次的 `HealthCheckSummary` (小写 idcard),因平台字段大小写不一致。
- 下一步:go test 复验后提交;体检查询三接口(最近一次/全部/名单)osi+HTTP 全齐。
2026-07-08 22:21:21 +08:00
## 2026-07-08 新增 docs/07 本项目 HTTP 接口文档
- 状态:DONE
- 变更:新建 `docs/07-本项目HTTP接口.md` ,整理 server 模式 5 个查询端点(档案 find + 体检 last/all/list)的参数/上游 serviceId/返回形态/PII 警示/通用约定;`docs/README.md` 导航登记;`CLAUDE.md` 文档同步表加"HTTP 端点增改 → docs/07"。
- 决策:区分两层接口文档——上游 OSI 契约以 docs/01+04(实测)为准,本项目对外接口以 docs/07 为单一事实来源;docx 反复被证明不可靠,只作参考。
- 下一步:新增端点时同步 docs/07;对外前补鉴权/日志/限流。
2026-07-09 20:35:11 +08:00
## 2026-07-09 登记 T-209 人群分类查询优化
- 状态:DONE(看板+契约文档维护)
- 来源:用户更新 `scripts/query_crow_with_idcard.py` ,并提供本地响应样本 `scripts/query_crow_with_idcard.json` (含真实身份证/phrId,不入库)。
- 发现:JKDA00005 实测请求路径为 `/osi/api/auto/jkda/findrqbj` ,响应 `data` 是对象,字段含 `personSign` 、`idCard` 、`phrId` ;当前 Go 侧只建模 `personSign` ,且路径仍是早期 `/jkda/findrqbj` 。
- 变更:`tasks.md` 新增 T-209; `docs/01` 、`docs/04 §12` 登记实测契约和待改点。
- 下一步:实现 T-209,修正 `osi` 路由/响应结构,并补 server 模式人群分类查询端点。
2026-07-09 20:38:27 +08:00
## 2026-07-09 T-209 人群分类查询优化
- 状态:DONE
- 变更:`osi/codes.go` 修正 JKDA00005 路由为 `/auto/jkda/findrqbj` ; `osi.PersonSignResult` 补 `idCard` 、`phrId` 字段;`handler.HealthRecordHandler` 新增 `GET /api/health-record/crowd` ,原样回写平台响应;`server.go` 注册新端点。
- RED: `go test ./osi -run TestFindRqbjCallsJKDA00005AndDecodesPersonSign -count=1` 初次失败,缺少 `idCard/phrId` 字段;`go test ./handler -run TestHealthRecordCrowdReturnsRawPlatformResponse -count=1` 初次失败,缺少 `Crowd` handler。
- GREEN:补实现后上述目标测试通过。
- 验证:`go test ./...` 通过。
- 验证:`go build ./...` 通过。
- 验证:`git diff --check` 通过。
- 决策:HTTP 端点继续采用查询类统一策略,直接回写 `Result.Raw` ,避免裁掉平台后续新增字段;真实样本 JSON 仍只本地留存,不提交。
2026-07-09 21:17:16 +08:00
## 2026-07-09 登记 T-210 公开查询 HTTP API
- 状态:DONE(看板+文档维护)
- 变更:`tasks.md` 新增 T-210,范围为把人群分类、网格地址、责任医生、药品目录、机构查询统一暴露为 server 模式 HTTP API;同步登记 `docs/openapi.yaml` 作为本项目 HTTP API 的机器可读文档。
- 决策:T-210 依赖 T-101(四类公开查询 OSI 客户端已完成)与 T-209(人群分类 HTTP 端点已存在)。实现时不再重复写 OSI 客户端,只补 handler/router、HTTP 测试、`docs/07` 与 OpenAPI。项目后续以内网部署为目标,但接口仍返回 PII/主数据,需在文档中明确绑定地址和鉴权边界。
- 验证:文档登记阶段运行 `git diff --check` ;代码实现阶段再跑 `go test ./...` 、`go build ./...` 。
- 下一步:开始 T-210,实现公开查询 HTTP API。
2026-07-09 21:24:09 +08:00
## 2026-07-09 T-210 公开查询 HTTP API
- 状态:DONE
- 变更:新增 `handler/public.go` 与 `handler/public_test.go` ,暴露 `GET /api/dictionaries/grid-addresses` 、`/doctors` 、`/drugs` 、`/orgs` ; `server.go` 注册四个公开查询路由并打印启动提示;既有人群分类端点 `/api/health-record/crowd` 保持不变。
- 变更:同步 `docs/07-本项目HTTP接口.md` 与 `docs/openapi.yaml` ,补网格地址、责任医生、药品目录、机构查询;更新内网部署说明,明确服务本身不内置鉴权。
- RED: `go test ./handler -run TestPublic -count=1` 初次失败,缺少 `NewPublicHandler` 。
- GREEN:补最小实现后 `go test ./handler -run TestPublic -count=1` 通过。
- 验证:`go test ./...` 通过;`go build ./...` 通过;`python -c "import yaml; yaml.safe_load(open('docs/openapi.yaml', encoding='utf-8'))"` 通过;`git diff --check` 通过。
- 决策:公开查询端点继续原样回写平台 JSON,不裁字段;`pageNo` 做 HTTP 层整数校验,其余查询条件按上游查询透传。项目按内网部署推进,但 API 返回主数据/PII 相关上下文,生产暴露前仍需网关鉴权和日志脱敏。
- 下一步:T-303 老年人查询探针;或继续补内网部署鉴权/访问控制任务。
2026-07-09 22:11:06 +08:00
2026-07-13 19:49:52 +08:00
## 2026-07-13 登记 T-303 老年人生活自理能力评估查询
- 状态:DONE(任务拆分与契约文档维护)
- 来源:厂家新增《老年人生活自理能力评估查询服务.docx》。参数表给出 `POST /osi/api/auto/lnr/query` 、`serviceId=LNRZLPG00002` 、`idCard` 必填及评估数组响应字段。
- 变更:将 T-303 收窄为生活自理能力评估查询;新增 T-307 跟踪仍缺契约的中医体质辨识查询;同步 docs/01、docs/04 §13、docs/06 B1/B4 与当前快照。
- 风险:docx 请求样例错误写 `LNR00004` 且缺 `uploadinfo/manageInfo` ,仅能以参数表和既有查询信封实施;服务路径、serviceId、字段仍须真实请求验收后确认。
- 下一步:领取 T-303,先做 `contract/lnr.go` 、`osi/lnr.go` 、HTTP handler 与回归测试,再以安全测试居民探针联调。
2026-07-13 19:53:49 +08:00
## 2026-07-13 T-303 老年人生活自理能力评估查询
- 状态:BLOCKED(代码、测试、HTTP 文档完成;真实联调缺安全测试身份证)
- 变更:新增 `contract/lnr.go` (评估响应字段)、`osi/lnr.go` ( `QueryElderlySelfCare` )、`osi/lnr_test.go` ;在 `osi/codes.go` 登记 `LNRZLPG00002 -> /auto/lnr/query` 。
- 变更:新增 `handler/elderly.go` 、`handler/elderly_test.go` ,提供 `GET /api/elderly/self-care?idCard=...&phrId=...&checkId=...` ,查询响应继续原样回写;`server.go` 注册路由;同步 docs/07、OpenAPI 与当前快照。
- RED: `go test ./osi ./handler -run "Test(QueryElderlySelfCare|ElderlySelfCare)" -count=1` 初次失败,缺少查询方法、查询类型、serviceId 与 handler。
- GREEN:补最小实现后上述目标测试通过。
- 验证:`go test ./...` 、`go build ./...` 、OpenAPI YAML 解析与 `git diff --check` 通过。
- 阻塞:本机存在 `config.yaml` ,但没有 `OSI_VERIFY_ID_CARD` ,未提供可用于该接口的安全测试身份证;不可把厂家 docx 契约当作真实平台已确认。
- 下一步:提供安全测试身份证后,以 Go 客户端做只读查询,记录脱敏后的 `code/message/data_count` ,据结果确认或修正 serviceId、路径与字段。
2026-07-09 22:11:06 +08:00
## 2026-07-09 T-211 药品目录分页契约修正(登记)
- 状态:DOING
- 背景:docx YPML00001 药品目录查询是**分页列表**——`pageNo` 必填、有 `pageSize` 、返回药品数组(ypxh/ypmc/ypdw/ypgg/ypjl/ycjl/jldw)。当前实现把 `pageNo` 当可选(`DrugQuery.PageNo` 带 omitempty、handler `parseOptionalInt` 、docs/07 标"否"),且缺 `pageSize` ——`pageNo=0` 会被 omitempty 丢弃,平台可能报必填错。
- 计划:`osi.DrugQuery` 去 pageNo omitempty + 加 PageSize; handler 缺 pageNo 返回 400 + 读 pageSize; `docs/07 §8` +`openapi.yaml` 标 pageNo 必填并补 pageSize;docx 契约未联调,全程注明待厂家样本核对。
- 注意:YPML00001 从未真实联调(current-state 自述),本次按 docx 校准,pageNo 是否真必填、响应字段名待联调确认;不扩 `Drug` 响应结构体(避免照单样本猜类型),HTTP 走 Raw 不受影响。
2026-07-09 22:22:55 +08:00
## 2026-07-09 T-211 药品目录分页契约修正(完成)
- 状态:DONE
- 变更:`osi/public.go` `DrugQuery.PageNo` 去 omitempty(确保 pageNo=0 也上送)+ 加 `PageSize` ; `handler/public.go` Drugs 改 pageNo 必填(新增 `parseRequiredInt` ,缺则 400) + 读 pageSize; `handler/public_test.go` 加"缺 pageNo→400"测试;`docs/07 §8` 标 pageNo 必填+补 pageSize+分页说明+未联调警示;`docs/openapi.yaml` drugs 内联 pageNo(required)+pageSize(不动共享 pageNo,网格地址仍可选)。
- 验证:openapi YAML 合法、drugs pageNo required=true、grid-addresses 共享 pageNo 仍 false; Go 静态自检通过;现有 drugs 测试(pageNo=1/pageNo=bad)不受影响。待用户 `go test ./...` 复验。
- 决策:只修请求侧分页契约(清晰的 bug);不扩 `Drug` 响应结构体(缺 ypxh/ypjl/ycjl),因 YPML00001 未真实联调、避免照 docx 猜类型,且 HTTP 走 Raw 不影响输出。全程注明 docx 契约待联调。
- 下一步:联调 YPML00001 拿真实分页样本后,确认 pageNo/pageSize 真契约并补 Drug 响应字段。
2026-07-15 22:08:45 +08:00
## 2026-07-15 T-212/T-213 任务卡评审与校准(全栈视角 review codex 落卡)
- 状态:任务卡已按评审修订,T-212/T-213 均保持 TODO
- 评审结论:拆分方向正确(纯转换 vs upsert 编排、依赖链诚实),upsert 7 条防御规则(查询失败禁降级创建、多条转人工、失败不互相回退)与凭据隔离要求保留不动。
- 修订 1( checkId):T-212 的"更新时间不参与稳定标识"实为对已 DONE 代码的行为修改——现有 `mapping/health_record.go` 把 `src.UpdatedAt` 传入 `GenerateCheckID` 。卡片补注:改现有调用+同步 T-205 基线单测+落 ADR( CLAUDE.md checkId 保守处理项;依据 T-206"Update 沿用 checkId")。
- 修订 2( contract 范围):删除"扩至 docx 写入字段全集"——违反"不照搬 docx 字段表"铁律且写入路径无联调样本兜底;收窄为只扩 PHIS 实际提供+映射需要的字段。
- 修订 3( adressNumber):`adressNumber→addressNumber` 改名仅有 docx 依据(查询响应实测是错拼 adressNumber),卡片标注为首次真实创建后的联调必核对项。
- 修订 4( T-213 边界收窄):`source/` 与 PHIS 回写契约(阶段 5)尚不存在——T-213 只定义回写 interface+假实现验收,真实回写拆出 T-214 登记到延期任务表;幂等/状态存储注明与阶段 3 pipeline 共用一套。
- 附带:真实样本 `businessId == archId` 同值,回退分支需人工构造 fixture(已注入卡片)。
- 安全修复:`payloads/` (文件名即真实身份证号,内容含住址/手机号/医生账密)未被 gitignore,已补 `/payloads/` 并以 `git check-ignore` 验证生效。
- 下一步:提交 tasks.md/.gitignore/progress.md;领取 T-212 时按修订后的卡片执行。
## 2026-07-15 T-212/T-213 评审建议二次校准
- 状态:任务与依赖已重排,未领取实现任务;T-212/T-213/T-204 保持 TODO, T-215 因真实写入授权 BLOCKED。
- 决策:把原 T-206 拆为“本地写入能力”和“真实平台验收”。既有 T-206 代码与本地测试证据满足前者,状态改 DONE;新增 T-215 承接授权后的 create→query→update→query 验收,使 T-213 可在假依赖下先开发。
- 决策:不再根据单个样例假定 `businessId` 优先。T-212 先确认 `archId/businessId/phrId/empiId` 的稳定性,再通过 ADR 固化 `ResolveSourceRecordKey` 与 checkId 兼容策略。
- 决策:补充逐档案 `operateUser` 、更新目标/机构/状态校验、PHIS 空值不覆盖平台值、既往史“无”代码、`isFillShhj` 条件组装和脱敏 fixture 位置等验收边界。
- 决策:T-213 只实现可复用 upsert 应用服务和注入接口,用内存假实现验收;持久化幂等/report、重试调度和熔断归路线图阶段 3,PHIS 真实状态适配归 T-214, HTTP 入口归 T-204。
- 安全:保留 `/payloads/` 整体忽略规则,并清理新增规则的异常行尾;真实样本不进入 Git。
- 验证:`go test ./...` 通过;`go build ./...` 通过;`git diff --check` 通过。
- 验证:`git check-ignore -v payloads\\441625198611255416_phis_health_record.json` 命中 `.gitignore:50:/payloads/` ,真实 PHIS 样本未进入 Git。
2026-07-16 00:53:16 +08:00
## 2026-07-16 T-212 PHIS 健康档案真实结构与转换器
- 状态:DONE。
- RED: `go test ./source -count=1` 初次因缺少 `DecodeHealthRecordTask` 失败;`go test ./contract ./mapping -count=1` 初次因写入字段、稳定 checkId API 和 `MapHealthRecordTask` 缺失失败;`go test ./cache ./osi ./mapping -count=1` 初次命中字典无 ID 校验、映射未校验主数据、OSI 覆盖逐档案操作人三项旧行为。
- GREEN:新增脱敏 `source/testdata/health_record.json` 与 PHIS DTO;完成主体、既往史四数组、生活环境转换和结构化校验;扩充 JKDA 写入契约;字典快照支持医生/机构 ID 校验;OSI 写入保留请求级 `operateUser` 。
- 决策:参考 `chis_upload` 的 PHIS 回调契约及多业务样例,健康档案稳定源键使用 `archId` , `businessId` 只用于 trace/回写,`empiId/phrId` 属于 CHIS 标识;`archId` 缺失不回退。ADR: `docs/decisions/001-phis-health-record-source-key.md` 。
- 安全:真实 PHIS payload 未复制入仓库;fixture 使用占位身份信息和假凭据,生产 DTO 不声明 `sxtAccount/sxtPassword` 。
- 验证:`go test ./source -count=1` 、`go test ./contract ./mapping -count=1` 、`go test ./cache ./osi ./mapping -count=1` 、`go test ./... -count=1` 、`go vet ./...` 均通过。
- 下一步:T-213 健康档案 query-first upsert 应用编排。
2026-07-16 01:06:43 +08:00
## 2026-07-16 T-213 健康档案 query-first upsert 应用编排
- 状态:DONE。
- RED: `go test ./pipeline -count=1` 初次因 `HealthRecordUpsertService` 、状态/动作、事件和注入端口均不存在而编译失败。
- GREEN:新增 `pipeline/health_record_upsert.go` ;按身份证查询后仅在明确 0 条时创建,单条记录通过身份证、状态、机构、责任医生和 `phrId` 校验后更新,多条及不安全目标转 `manual_review` ;查询/创建/更新失败按 `osi.Result.Retryable` 分类且不互相回退。
- 幂等:键为 `sha256(checkId + "|" + idCard)` ;接口采用带 owner token 的原子 `Acquire/Complete/Release` ,并发处理中直接 `retry/skip` ,完成/释放校验 token,写入失败释放租约,明确成功后完成租约。
- 更新语义:查询目标单独建模 `UpdateTarget` ,更新请求仅合并平台 `phrId` ,源侧稳定 `checkId` 不被查询记录覆盖。
- 安全与边界:普通报告事件只含哈希 `trace_id` 、业务状态、serviceId、响应码及脱敏 `phrId` ,原始源标识只交给受控 PHIS 回写端口;`data:null` /缺失不得视为 0 条;幂等完成失败不发送 `done` ,副作用失败通过 `NotificationError` 区分租约/报告/PHIS 回写错误并只暴露失败端补偿数据。持久化、重试调度、HTTP 和 PHIS 真实回写不在 T-213 实现。
- 验证:`go test ./pipeline -count=1` 、`go test ./... -count=1` 、`go build ./...` 、`go vet ./...` 、`git diff --check` 均通过。`bash ./init.sh` 在当前 PowerShell 调起的 Bash 环境失败(`go: command not found` ),未误报通过;其内部等价 Go 命令已在 PowerShell 单独验证。
- 下一步:T-204 `POST /api/health-record/upsert` HTTP 适配;T-215 继续等待安全测试档案和写入授权。
2026-07-16 02:00:56 +08:00
## 2026-07-16 T-204 健康档案 upsert HTTP 适配
- 状态:DONE(真实 curl 写入验收仍归 T-215,未使用真实居民调用)。
- RED: `go test ./handler -run HealthRecordUpsert -count=1` 初次因 `NewHealthRecordUpsertHandler` 不存在而编译失败;`go test ./pipeline -run MemoryIdempotency -count=1` 初次因无进程内租约实现失败;根包运行时测试初次因 `newServerHealthRecordUpserter/newServerMux` 不存在失败。
- GREEN:新增 `POST /api/health-record/upsert` ,接收最大 1 MiB 的 PHIS 健康档案响应信封,复用 T-213 服务并按 `done/manual_review/retry/failed` 返回 200/409/503/422 或上游 502;内部错误不回显代理/平台细节。
- 运行时:先做无网络字段/码表预校验,再按 `manaUnitId` 查询责任医生和机构字典并注入 mapping;新增 owner-token 进程内并发租约,防同进程并发和 stale token,成功后释放而不缓存 completed,避免稳定 `checkId` 阻断后续更新;HTTP 模式不做 PHIS 状态回调。
- 安全:若 CHIS 已成功但幂等完成/通知失败,502 响应保留 `outcome.status=done` 且 `retrySafe=false` ,调用方不得重放整个 upsert;普通响应只含脱敏 `phrIdHint` 。
- 文档:更新 `docs/03` 、`docs/07` 和 `docs/openapi.yaml` , OpenAPI 版本升至 0.2.0,增加 PHIS 请求信封、outcome、错误与 HTTP 状态定义。
- 验证:`go test ./... -count=1` 、`go build ./...` 、`go vet ./...` 、OpenAPI YAML 解析与端点断言、`git diff --check` 均通过;`go test -race ./pipeline ./handler -count=1` 因当前 Go 环境未启用 CGO 无法执行;`bash ./init.sh` 在当前 Bash 环境因找不到 `go` 失败,其内部等价 Go 命令已在 PowerShell 验证,均未误报通过。
- 下一步:取得安全写入授权后做 T-215;否则拆分路线图阶段 3 的持久化幂等和补偿任务。