Files
chis_osi/progress.md
T

309 lines
34 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.
# 执行进度记录
> 只追加的历史流水:每轮做了什么、跑了什么验证、遇到什么阻塞、做了什么决策。
> 当前目录/命令/下一步等可覆盖快照写 [`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 T-000 处理失效测试
- 状态:DONE
- 变更:删除 `tests/`(`test_query_health_record.py` 及 `__pycache__`)——文件从未被 git 跟踪,无需 `git rm`。
- 验证:`git status --short` 确认删除前后均无该目录的跟踪历史;删除后工作区干净。
- 决策:不改写为新契约的最小测试,直接删除——脚本是硬编码的本地联调工具(不入库),暂不需要自动化测试覆盖。
- 下一步:T-001,进入路线图阶段 0(go module + 双子命令骨架)。
## 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` 只保留占位值。
- 下一步: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 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。
- 下一步:T-004(`osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go`)。
## 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` 的输入。
- 下一步:T-005(阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh`)。
## 2026-07-06 T-005 阶段 0 验收:Go 侧真实请求打通 + init.sh
- 状态:DONE
- 变更:新增 `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 ./...`、启动三步命令;补充传输层头名与基础请求头单测。
- 验证:`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 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 复评 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 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 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 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 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 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 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 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 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 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 看板重排(读先行:查询优先、写入后置)
- 状态: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 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 拆分 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 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 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 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 新增 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 登记 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 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 登记 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 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-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-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 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 响应字段。