docs(mcp): 重写人均用纸统计设计文档对齐已发布实现 (REQ-20260922-0004)
This commit is contained in:
@@ -1,47 +1,51 @@
|
||||
# 人均用纸统计 MCP 工具设计
|
||||
|
||||
需求:REQ-20260922-0004
|
||||
来源:老系统 `StatisticsReportController`(xiaoqu-franchisee)5 个接口的 MCP 化。
|
||||
|
||||
工具:`district.paper.percapita.query`
|
||||
|
||||
## 1. 覆盖范围
|
||||
|
||||
一个 MCP 工具通过 `groupBy` 与 `daily` 覆盖老 `StatisticsReportController` 的五个接口:
|
||||
|
||||
| 老接口 | groupBy | daily |
|
||||
| 老接口 | 分组维度 | 图表形态 |
|
||||
|---|---|---|
|
||||
| perPaperLengthStatistics | gender | false |
|
||||
| perPaperLengthStatisticsDetail | gender | true |
|
||||
| perPaperLengthStatisticsByFloor | floor | false |
|
||||
| perPaperLengthStatisticsDetailByFloor | floor | true |
|
||||
| perPaperLengthStatisticsByToilet | floorGender | false |
|
||||
| perPaperLengthStatistics | gender | 柱图 |
|
||||
| perPaperLengthStatisticsDetail | 日期 × gender | 折线 |
|
||||
| perPaperLengthStatisticsByFloor | floor | 柱图 |
|
||||
| perPaperLengthStatisticsDetailByFloor | 日期 × floor | 折线 |
|
||||
| perPaperLengthStatisticsByToilet | floor × gender | 分组柱图(名为 ByToilet,实际维度是楼层×卫生间类型) |
|
||||
|
||||
`floorGender+daily=true` 不在老接口范围内,直接返回参数错误。
|
||||
## 1. 数据源
|
||||
|
||||
## 2. 数据与口径
|
||||
- **纯 MySQL**,m0 库 `monthly_statistics` 日汇总表(Mapper 已存在:`mapper/m0/MonthlyStatisticsMapper.java`,实体 `entity/m0/MonthlyStatistics.java`)。**不查 ES**——ES 只在老系统离线写入管道里(XXL-Job 从 paperuselog 聚合落表)。
|
||||
- 聚合口径(与老系统一致):`sum(use_count)`、`sum(paper_length)/100`(表内 paper_length 单位为厘米,/100 转米),人均 = paper_length(米) / use_count,保留 2 位小数;use_count=0 时人均取 paperLength 原值(沿用老逻辑)。
|
||||
- 过滤条件:`soft_delete=0`、`statistics_time` 起止(含当天)、`district_id` 必填、可选 `device_type`、可选位置筛选 `idStrList`(`buildingId_floor_toiletId` 三段各自独立 IN)。
|
||||
- 天数上限:Redis `Dictionary` hash 的 `StatisticsDays`,超限报错(与 district.paper.statistics.query 一致)。
|
||||
|
||||
- 数据源:m0 `monthly_statistics` 日汇总表,不查 ES。
|
||||
- 固定过滤:`soft_delete=0`、`district_id`、日期左闭右开区间。
|
||||
- 可选过滤:`device_type`;`idStrList` 中 building、floor、toilet 分别独立 IN。
|
||||
- `paperLengthMeters=Σpaper_length/100`,表内 `paper_length` 单位为厘米。
|
||||
- `perCapita=paperLengthMeters/Σuse_count`,四舍五入保留两位小数。
|
||||
- 当 useCount=0 时,perCapita 取 paperLengthMeters,延续老系统语义。
|
||||
- 日期上限读取 Redis `Dictionary.StatisticsDays`,缺失或非法时拒绝查询。
|
||||
## 2. 权限(与老系统的关键差异)
|
||||
|
||||
## 3. 参数
|
||||
老系统 5 个接口**无任何行级权限**(UserUtils 全被注释,agentId/propertyId 入参被删)。MCP 版必须补上:
|
||||
|
||||
- 沿用 `district.paper.statistics.query` 的模式:先按 `permissionService.applyDistrictRowFilter` 校验 districtId 对当前用户可见,不可见抛 `IllegalStateException("站点不存在或无权访问")`。
|
||||
- 只发用户委托 Token(user_delegated),服务 Token 不给这几个 query 工具的 scope(遵守 AK/Scope 约束,见 [[xiaoqu-mcp-architecture]])。
|
||||
|
||||
## 3. 工具形态:推荐合并为 1 个工具
|
||||
|
||||
老系统 5 个接口本质是 5 种分组组合,合并成一个工具对 agent 更友好(少注册、少 scope 绑定、schema 自解释):
|
||||
|
||||
```
|
||||
district.paper.percapita.query (@XqTool(category="district", kind="read"))
|
||||
```
|
||||
|
||||
参数:
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|---|---|---|
|
||||
| districtId | 是 | 正整数站点 ID |
|
||||
| startDate/endDate | 是 | yyyy-MM-dd,含首尾日期,endDate 不晚于今天 |
|
||||
| groupBy | 是 | gender / floor / floorGender |
|
||||
| daily | 否 | 默认 false;true 时增加日期维度 |
|
||||
| deviceType | 否 | 0 出纸机,1 擦手纸机 |
|
||||
| idStrList | 否 | 每项严格为 buildingId_floor_toiletId |
|
||||
| districtId | 是 | 站点 id |
|
||||
| startDate / endDate | 是 | yyyy-MM-dd,含当天;跨度 ≤ StatisticsDays |
|
||||
| groupBy | 是 | `gender` / `floor` / `floorGender`(楼层×卫生间类型,即老 ByToilet) |
|
||||
| daily | 否,默认 false | true=按日折线(仅 gender/floor 支持;floorGender+daily 直接参数报错) |
|
||||
| deviceType | 否 | 0=出纸机 1=擦手纸机 |
|
||||
| idStrList | 否 | 位置筛选,`buildingId_floor_toiletId` 格式 |
|
||||
|
||||
## 4. 面向 LLM 的返回结构
|
||||
映射:`gender`+daily=false→①,+daily=true→②,`floor`+false→③,+true→④,`floorGender`→⑤。
|
||||
|
||||
返回结构不随分组方式变化。维度和指标位于同一个行对象,无数据日期或分组不生成行。
|
||||
返回 `Map<String, Object>`(项目统一风格,无 DTO 类)。**面向 LLM 消费设计**(王恒 2026-09-22 确认:后续所有 MCP 工具设计都以返给 LLM 展示为准)——一种结构走天下,不随 groupBy/daily 变形态:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -53,33 +57,37 @@
|
||||
"daily": false,
|
||||
"totalPerCapita": 1.32,
|
||||
"rows": [
|
||||
{
|
||||
"date": null,
|
||||
"floor": null,
|
||||
"gender": "男卫",
|
||||
"paperLengthMeters": 120.5,
|
||||
"useCount": 96,
|
||||
"perCapita": 1.26
|
||||
}
|
||||
{"date": "2026-09-01", "floor": null, "gender": "男卫", "paperLengthMeters": 120.5, "useCount": 96, "perCapita": 1.25}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- 未参与分组的 `date/floor/gender` 保持为 null。
|
||||
- `daily=true` 时 `totalPerCapita` 为 null;非按日查询时为整个区间的总人均。
|
||||
- gender 映射:0男卫、1女卫、2母婴室、3无障碍、4第三卫、5其他,其他值显示未知。
|
||||
- 楼层按数据库字符串值的字典序输出。
|
||||
- `rows` 是扁平自描述行数组:每行把维度值(date/floor/gender)和指标(paperLengthMeters/useCount/perCapita)放同一对象,LLM 不需要维护平行数组下标对齐。
|
||||
- 维度字段恒在、不参与的维度为 null:`daily=false` 时 `date=null`;`groupBy=gender` 时 `floor=null`;`groupBy=floor` 时 `gender=null`;`floorGender` 两者都有值。
|
||||
- 透出 `paperLengthMeters`、`useCount` 原始值,LLM 可自行二次计算,不必从人均反推。
|
||||
- 无数据的日期/分组**不出行**(LLM 语义:没有就是没数据,不会误读成 0)——放弃老系统折线"补完整日期横轴 + 填 0.0"的做法,画折线是消费方的事。
|
||||
- `totalPerCapita` 仅在 daily=false 时给出(区间总人均);daily=true 时为 null,由消费方按需聚合。
|
||||
- gender 文案映射沿用老系统:0男卫/1女卫/2母婴室/3无障碍/4第三卫/5其他。
|
||||
- 数值统一 Double、保留 2 位小数;use_count=0 时人均取 paperLength 原值(沿用老逻辑)。
|
||||
|
||||
## 5. 权限与作用域
|
||||
备选方案:1:1 拆 5 个工具(`district.paper.percapita.{gender,genderDaily,floor,floorDaily,floorGender}.query`)。更贴近老接口但注册/绑定成本 ×5,不推荐。
|
||||
|
||||
- 查询前用 `PermissionService.applyDistrictRowFilter` 校验当前用户能否查看站点。
|
||||
- 站点不存在和无权访问统一返回“站点不存在或无权访问”。
|
||||
- 工具只绑定 `service_enabled=0` 的用户委托作用域,不向 sync、ops-query 等服务身份作用域开放。
|
||||
废弃方案:ECharts 平行数组结构(nameList/valueList、dateList/series、xnameList/groupList/groups)——给前端画图顺,但 LLM 消费有下标错位、补 0.0 混淆语义、多形态理解成本三个问题,已被王恒否决(2026-09-22)。
|
||||
|
||||
## 6. 实现文件
|
||||
## 4. 改动文件清单
|
||||
|
||||
- `src/main/java/me/iiv/agent/mcp/tool/PaperPerCapitaStatisticsTools.java`
|
||||
- `src/test/java/me/iiv/agent/mcp/tool/PaperPerCapitaStatisticsToolsTest.java`
|
||||
- `sql/seed-dev.sql`
|
||||
- `doc/agent-mcp-tools-reference.md`
|
||||
- `README.md`
|
||||
1. 新建 `src/main/java/me/iiv/agent/mcp/tool/PaperPerCapitaStatisticsTools.java`(@Component implements ToolObject,骨架照抄 DistrictPaperStatisticsTools.java:73-87)
|
||||
2. 实体/Mapper:**无需新增**(monthly_statistics 已覆盖)
|
||||
3. 权限:**无需新增**(复用 applyDistrictRowFilter,PermissionService.java:798)
|
||||
4. ES:**不涉及**
|
||||
5. 单测 `src/test/java/me/iiv/agent/mcp/tool/PaperPerCapitaStatisticsToolsTest.java`(Mockito + AuthContextHolder.set(),参照 DistrictPaperStatisticsToolsTest)
|
||||
6. `sql/seed-dev.sql` 加幂等 scope_tool 绑定;生产绑 scope 1/2/3/5(与 district.paper.statistics.query 同组)
|
||||
7. 登记 `doc/agent-mcp-tools-reference.md`
|
||||
8. 装配层零改动(McpToolConfig 自动收集 ToolObject bean,ToolSyncService 自动同步 tool 表)
|
||||
|
||||
## 5. 已知坑 / 注意点
|
||||
|
||||
- 老代码 `totalPaperLength` 用 Integer 存 SQL 已 /100 的值再除,精度有损失;MCP 版统一用 Double 计算,不与老系统逐分核对。
|
||||
- `setWrapperByPosition` 的三段独立 IN 语义要保持(不是联合匹配),否则结果集不同。
|
||||
- daily 不再补完整日期横轴(老系统补 0.0 是给前端画图用;MCP 面向 LLM,无数据日期不出行)。
|
||||
- floor 是 String 列,排序按字典序(老系统行为),文档里注明。
|
||||
|
||||
Reference in New Issue
Block a user