docs(reports): 更新报表接口接入文档与实现说明,报表分析接口接入

This commit is contained in:
yelan
2026-09-02 16:51:35 +08:00
parent e52f203123
commit 612e43aeff
10 changed files with 1474 additions and 795 deletions
+53 -209
View File
@@ -1,228 +1,72 @@
# 报表分析后端接口交付需求
# 报表分析后端联调与验收说明
> 事实基准:用户于 2026-09-02 导出的 default_OpenAPI.json,OpenAPI 3.1.0,MEDISIGN 患者电子签 API v1。
> 事实基准:用户于 2026-09-02 提供的 default_OpenAPI.json,OpenAPI 3.1.0,MEDISIGN 患者电子签 API v1。
>
> 本文的所有“建议新增”接口均不在当前 OpenAPI 中。它们是后端交付需求,前端在接口实际发布前不得直接调用。
> 本文记录当前契约的前端接入要求和联调检查项,不再把已经存在的报表接口标记为“建议新增”。
## 1. 交付目标
## 1. 当前契约与页面使用
当前 API 只提供签署任务、模板、院区和科室等原始数据。报表页为了展示筛选、四张指标卡、签署量趋势、科室签署分布和明细列表,需要在浏览器中循环读取全部有权限的签署任务后自行聚合。
| 接口 | 页面用途 | 状态 |
| -------------------------------------------------------- | ------------------------------------ | ------------------------ |
| GET /api/v1/reports/signing/options | 权限范围内的院区、科室、模板分类字典 | 前端已接入 |
| GET /api/v1/reports/signing/overview | 指标、签署趋势、科室分布 | 前端已接入 |
| GET /api/v1/reports/signing/tasks | 脱敏明细和服务端分页 | 前端已接入 |
| POST /api/v1/reports/signing/export-jobs | 创建 CSV/XLSX 异步导出任务 | 前端已接入,页面默认 CSV |
| GET /api/v1/reports/signing/export-jobs/{jobId} | 查询导出状态 | 前端已接入并轮询 |
| GET /api/v1/reports/signing/export-jobs/{jobId}/download | 下载已完成文件 | 前端已接入 |
后端需要提供权限内聚合、分页明细和正式导出的报表能力,使前端不再全量读取历史任务,也不再自行定义时间、状态、分母和时区口径。
页面真实模式通过统一 request 单例发送 /v1/...,由 /api base URL 形成完整路径,并自动携带 X-Token。Mock 模式不调用以上接口,继续使用报表页面自己的演示数据。
## 2. 当前 OpenAPI 已有能力与不足
## 2. 后端必须保持的统计口径
| 已有接口 | 可复用能力 | 不能解决的问题 |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| GET /api/v1/sign-tasks | 任务状态、createdAt、signedAt、expiredAt、院区/科室 ID、患者和就诊脱敏快照;支持 page、size、keyword、status、signMethod、campusId、departmentId、createdFrom、createdTo | 没有服务端指标、趋势、科室分布、按 signedAt/expiredAt 查询或真实签署耗时 |
| GET /api/v1/templates/available-versions | 可用模板版本、模板名称和分类 | 不是历史任务的不可变模板快照;历史模板不可见时无法可靠补齐名称和类别 |
| GET /api/v1/campuses | 院区分页查询 | 默认分页大小为 20,不能直接作为完整筛选字典 |
| GET /api/v1/departments | 科室分页查询,可按 campusId 筛选 | 默认分页大小为 20,不能直接作为完整筛选字典 |
- 时间区间为左闭右开 [from,to),页面传 timezone=Asia/Shanghai;
- overview 的 createdFrom/To 只约束发起统计,signedFrom/To 约束签署完成和趋势,expiredFrom/To 约束超时统计;
- 页面明细和导出只传 createdFrom/To,以便待签署任务仍能出现在明细中;
- trend 必须按 signedAt 的业务时区自然日聚合,并包含零值日期;
- departments 必须按任务就诊科室返回,不能改用模板归属科室;
- onTimeRate 是 0-1 小数;无可靠样本返回 null;
- averageSignDurationSeconds 必须基于可靠的 signStartedAt/signedAt 或等价审计字段;无样本返回 null;
- CREATED、WAITING_SIGN、SIGNED、EXPIRED、VOIDED、GENERATING、FAILED 均需保持可区分。
当前导出的 OpenAPI 中没有 reports、analytics、statistics 或 export 资源。
## 3. 明细分页验收
## 3. 页面五个区域的后端交付范围
tasks 请求使用 page、size、timeDimension=CREATED、sort=createdAt、direction=desc。后端应保证:
| 页面区域 | 后端应返回的结果 | 当前前端临时实现 |
| ------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| 搜索/筛选栏 | 统一筛选参数、可用院区/科室字典、明确时间维度 | 前端过滤已加载任务;无任务 keyword 搜索框 |
| 四张指标卡 | 发起数、签署完成数、按时签署率、真实签署耗时及样本量 | 前端按 createdAt、signedAt、expiredAt 聚合;耗时暂以 signedAt - createdAt 代替 |
| 签署量趋势图 | 按 signedAt 和指定时区的自然日聚合 | 前端扫描所有已加载任务 |
| 科室签署分布 | 科室 ID、名称和按相同筛选条件聚合的数量 | 前端按 visitSnapshot.departmentName 聚合 |
| 签署明细列表 | 服务端分页、稳定排序、历史模板和组织展示快照 | 前端拉取全部任务后一次性渲染 |
- page 从 1 开始,size 在 1-200 范围内;
- records、page、size、total、pages 在权限过滤后保持一致;
- 默认或指定排序稳定,不能在翻页时重复或遗漏;
- taskId、templateVersionId、campusId 等标识与返回展示字段属于同一条任务快照;
- patientNameMasked、patientDisplayId、patientMobileMasked 只能是报表级脱敏字段;
- templateName、templateCategory、campusName、departmentName 和 deadlineAt/signedAt/signDurationSeconds 能够覆盖历史任务,不能要求前端再查当前可见模板列表补齐。
## 4. P0:报表概览聚合接口
## 4. 导出任务验收
**建议新增:GET /api/v1/reports/signing/overview**
创建导出任务时页面传当前筛选条件和 format=CSV,不传 page/size,因此结果应覆盖全部匹配记录,而不是当前明细页。后端需要:
接口只返回四张指标卡、签署趋势和科室分布,不返回大量明细。
- 创建、查询、下载时均重新检查操作人有效性、报表权限和数据范围;
- 支持 PENDING、RUNNING、SUCCEEDED、FAILED、EXPIRED;
- 文件生成完成后返回有效期和 resultCount;文件过期后拒绝下载;
- 下载响应返回文件流,不暴露 storage key;
- 导出列只包含当前角色允许查看的脱敏字段;
- 记录操作者、筛选条件摘要、结果数量、生成时间、过期时间和 traceId,但不记录身份证号、完整手机号、完整病历或签名原图。
所有请求使用现有 X-Token 鉴权方式,并通过统一 ApiResponse 返回。
## 5. 联调阻塞项与前端边界
### 查询参数
代码已经按当前 OpenAPI 完成接入,以下事项需要实际后端环境确认:
| 参数 | 类型 | 说明 |
| ----------------------- | ----------------- | -------------------------------------------------- |
| createdFrom / createdTo | ISO-8601 datetime | 发起任务和创建时间口径的闭区间 |
| signedFrom / signedTo | ISO-8601 datetime | 签署完成、趋势和完成分布的闭区间 |
| expiredFrom / expiredTo | ISO-8601 datetime | 超时统计的闭区间 |
| campusId | UUID | 院区筛选;不传表示调用人权限范围内的全部院区 |
| departmentId | UUID | 科室筛选 |
| templateCategory | string | 模板稳定分类编码,不能用中文名称关键词推断 |
| templateVersionId | UUID | 模板版本筛选 |
| signMethod | PAD 或 SMS | 签署方式筛选 |
| timezone | IANA timezone | 默认建议固定为 Asia/Shanghai,并由服务端校验白名单 |
- 六个报表路径已部署且当前角色有查询/导出权限;
- 统一响应的成功 code 为 0 或 "0",业务数据放在 data;
- 对象 query 参数按 OpenAPI 默认 form/explode 方式接收为平铺字段;
- 401、403、400、404、429 和 500 的 HTTP/业务错误能够被网关正确转发;
- download 接口的响应类型为文件流,且允许当前前端通过同源 /api 访问。
### data 返回字段
当前页面没有新增报表详情接口:明细“查看”继续跳转签署工作台,由工作台已有任务详情和签署文件能力处理。页面筛选栏只暴露 CSV,若需要 XLSX 选择器,仅需增加前端控件并复用同一导出接口。
| 字段 | 说明 |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| timezone | 本次统计实际使用的时区 |
| metrics.initiatedCount | 创建时间在范围内的任务数 |
| metrics.signedCount | 状态为 SIGNED 且 signedAt 在范围内的任务数 |
| metrics.onTimeSignedCount | signedAt 小于等于 expiredAt 的已签署任务数 |
| metrics.onTimeSampleCount | 有明确终态时间的 SIGNED 与 EXPIRED 任务数;VOIDED、FAILED 和未完成任务不进入分母 |
| metrics.onTimeRate | 分子除以分母;无样本时返回 null |
| metrics.averageSignDurationSeconds | 真实签署耗时均值;无有效样本时返回 null |
| metrics.durationSampleCount | 参与耗时计算的样本数 |
| trend | 数组,每项包含 date 与 signedCount |
| departments | 数组,每项包含 departmentId、departmentName、initiatedCount、signedCount、onTimeSignedCount、onTimeSampleCount、onTimeRate |
## 6. 建议联调用例
### 成功响应示例
```json
{
"code": "0",
"message": "OK",
"data": {
"timezone": "Asia/Shanghai",
"metrics": {
"initiatedCount": 132,
"signedCount": 108,
"onTimeSignedCount": 101,
"onTimeSampleCount": 115,
"onTimeRate": 0.8783,
"averageSignDurationSeconds": 1860,
"durationSampleCount": 103
},
"trend": [{ "date": "2026-08-01", "signedCount": 18 }],
"departments": [
{
"departmentId": "550e8400-e29b-41d4-a716-446655440001",
"departmentName": "心内科",
"initiatedCount": 25,
"signedCount": 18,
"onTimeSignedCount": 17,
"onTimeSampleCount": 19,
"onTimeRate": 0.8947
}
]
},
"traceId": "report-overview-example",
"timestamp": "2026-09-02T08:00:00Z"
}
```
统计口径必须由服务端固定。特别是趋势必须按 signedAt 聚合,超时必须按 expiredAt 聚合,不能用 createdAt 或 updatedAt 代替。
## 5. P0:报表明细分页接口
**建议新增:GET /api/v1/reports/signing/tasks**
该接口复用第 4 节全部筛选参数,并增加下列分页参数:
| 参数 | 类型 | 说明 |
| ------------- | -------------------------- | ----------------------------------------------------------- |
| page | integer | 页码,从 1 开始,默认 1 |
| size | integer | 每页条数,范围 1–200,默认 20 |
| sort | string | 仅允许白名单字段;默认 createdAt,DESC,id,DESC,保证稳定排序 |
| timeDimension | CREATED、SIGNED 或 EXPIRED | 明细列表的主时间维度;默认 CREATED |
每条明细至少返回:
- taskId、status、signMethod、createdAt、signedAt、expiredAt、voidedAt;
- 脱敏患者展示字段,不返回身份证号、完整手机号或完整病历;
- templateVersionId、templateName、templateCategory;
- campusId、campusName、departmentId、departmentName;
- signDurationSeconds;没有可靠样本时返回 null。
模板、院区和科室展示字段应是任务快照,或由服务端在完成权限过滤后关联返回。前端不能只依赖当前可用模板版本来补历史明细。
### 成功响应示例
分页结构沿用当前契约的 records、page、size、total、pages 字段:
```json
{
"code": "0",
"message": "OK",
"data": {
"records": [
{
"taskId": "650e8400-e29b-41d4-a716-446655440001",
"status": "SIGNED",
"signMethod": "SMS",
"createdAt": "2026-08-01T01:00:00Z",
"signedAt": "2026-08-01T01:23:40Z",
"expiredAt": "2026-08-02T01:00:00Z",
"voidedAt": null,
"patient": { "name": "张*", "mobileMasked": "138****0000" },
"templateVersionId": "750e8400-e29b-41d4-a716-446655440001",
"templateName": "诊疗知情同意书",
"templateCategory": "INFORMED_CONSENT",
"campusId": "850e8400-e29b-41d4-a716-446655440001",
"campusName": "示例院区",
"departmentId": "550e8400-e29b-41d4-a716-446655440001",
"departmentName": "心内科",
"signDurationSeconds": 1420
}
],
"page": 1,
"size": 20,
"total": 132,
"pages": 7
},
"traceId": "report-tasks-example",
"timestamp": "2026-09-02T08:00:00Z"
}
```
## 6. P1:正式导出接口
**建议新增:**
- POST /api/v1/reports/signing/export-jobs
- GET /api/v1/reports/signing/export-jobs/{jobId}
- GET /api/v1/reports/signing/export-jobs/{jobId}/download
创建任务时复用概览和明细的筛选条件,可附带 format 为 CSV 或 XLSX 以及可导出列集。响应返回 jobId、任务状态和文件有效期。
导出任务必须:
- 在服务端重新执行调用人的数据范围、院区/科室权限和模板 ACL;
- 记录操作人、筛选条件摘要、生成时间、结果数量、文件过期时间和 traceId;
- 异步处理大数据量,提供 PENDING、RUNNING、SUCCEEDED、FAILED、EXPIRED 等状态;
- 只返回当前角色允许查看的脱敏字段,不导出 Token、身份证号、完整手机号、完整病历或签名原图。
创建任务成功时,可按当前统一响应包装返回:
```json
{
"code": "0",
"message": "OK",
"data": {
"jobId": "950e8400-e29b-41d4-a716-446655440001",
"status": "PENDING",
"expiresAt": "2026-09-09T08:00:00Z"
},
"traceId": "report-export-example",
"timestamp": "2026-09-02T08:00:00Z"
}
```
## 7. 过渡方案:补强原始签署任务接口
如果 P0 报表资源暂时无法排期,至少扩展现有 GET /api/v1/sign-tasks:
- 增加 signedFrom、signedTo、expiredFrom、expiredTo,并明确它们与 createdFrom、createdTo 的组合语义;
- 返回不可变的 templateName、templateCategory、campusName、departmentName 快照;
- 返回 signStartedAt、signDurationSeconds 或等价审计耗时字段;
- 固定默认排序,并保证 page、size、total、pages 在权限过滤后稳定一致;
- 院区和科室接口应支持完整可用字典,或明确允许 size=200 和稳定排序,避免筛选项只获得默认第一页。
## 8. 权限、错误与审计要求
- 所有建议接口沿用 X-Token、统一 ApiResponse、traceId 和 UTC timestamp。
- 聚合和分页前必须在服务端执行数据范围、模板 ACL、院区和科室权限;前端不能承担授权过滤。
- 时间边界按 timezone 计算;若不支持动态时区,应在契约中固定 Asia/Shanghai。
- 建议错误语义:400 参数或日期范围错误,401 未登录或令牌失效,403 无报表权限,404 导出任务不存在或已过期,429 导出频率受限,500 聚合或文件生成失败。
- 指标分母、状态排除规则和时区属于契约的一部分;变更时需要可追溯的版本说明。
## 9. 联调验收标准
- 同一筛选条件下,四张指标卡、趋势、科室分布和明细具有可解释且一致的时间口径。
- 延迟签署任务只落入 signedAt 对应的趋势日期;过期任务只按 expiredAt 统计。
- 无有效签署耗时时返回 null,前端显示为 —,不填固定分钟数。
- 明细分页总数和排序稳定,翻页不重复、不遗漏。
- 服务端返回和导出文件均已完成权限过滤,不含敏感明文。
- 导出任务可以查询状态、审计、过期和受权限保护的下载。
1. 全院近 14 天:校验 overview、tasks 的请求参数和三类时间统计。
2. 选择一个院区/科室/模板分类/签署方式并输入关键词:校验 options 联动和所有接口参数一致。
3. 构造跨创建日完成签署的任务:确认趋势落在 signedAt 日期,而不是 createdAt 日期。
4. 翻页、改变每页条数:确认 total/pages、排序和记录不重复。
5. 创建导出任务并等待完成:确认状态轮询、文件下载、resultCount 和过期下载错误。
6. 用无权限角色访问:确认查询和导出均得到 403,前端不显示越权数据。