Files
xiaoqu_mcp_service/xiaoqu-agent-service/doc/traffic-statistics-mcp-tool-design.md
T
wangheng e41ecf9ea1 feat(mcp): 新增人流量统计工具,统计类工具查询区间统一限一个月
- district.traffic.statistics.query:复刻老 trafficStatistics 饼图与
  trafficStatisticsDetail 折线图,SUM(use_count) 按 gender/日期×gender 聚合,
  view=gender 占比 + view=daily_gender 按日趋势零填充(修正老系统插入错位 bug)
- 行级权限对齐 district.paper.* 系列;gender=null 归「未配置」不再 NPE
- 日期区间硬限 31 天(一个月),超出报错提示修改日期(2026-09-24 拍板);
  district.paper.percapita.query 与 district.paper.device_average.query 同步加限,
  与 Redis StatisticsDays 取更严
- 新增 12 个单测;设计文档 doc/traffic-statistics-mcp-tool-design.md
2026-09-24 16:22:10 +08:00

112 lines
7.0 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.
# 人流量统计 MCP 工具设计(评审稿)
需求:老系统人流量统计两个接口做成 MCP 工具,2026-09-24 用户截图指派(trafficStatistics 饼图 + trafficStatisticsDetail 折线图),先评审再实现。
拟建工具:`district.traffic.statistics.query`(**一个工具、两个 view**,对齐
`district.paper.device_average.query` 把老系统多接口合并为 view 参数的既有风格)
- `view=gender`:人流量占比(复刻 `/statisticsReport/trafficStatistics` 饼图)
- `view=daily_gender`:人流量按日趋势(复刻 `/statisticsReport/trafficStatisticsDetail` 折线图)
## 背景
老系统 `StatisticsReportController`(xiaoqu-franchisee):
- `trafficStatistics`(:347-442):饼图,按 gender 分组 SUM(use_count),算占比
- `trafficStatisticsDetail`(:453-559):折线图,按 日期×gender 分组 SUM(use_count),区间内日期零填充
两个接口查询的都是 **m0 `monthly_statistics`** 日汇总表(与 district.paper.* 系列同一张表),
过滤条件完全一致:`statistics_time` 区间(start 00:00:00 ~ end 23:59:59)+ `district_id` 必填
+ `setWrapperByInput`(:3062-3070)可选 `idStrList`(`楼id_楼层_厕所id` 拼串,拆成三个 IN)与 `device_type`。
天数上限走 Redis `Dictionary.StatisticsDays`。
老接口的行为与坑:
1. **权限校验是注释掉的**(`// User user = UserUtils.getUser(request)`),裸奔。
2. **`use_count` 即人流量**,与用纸长度无关——现有 `district.paper.statistics.query` 虽也返回 useCount,
但受厕位/厕墙口径约束(厕墙行恒 0,老 :200-203 意图),不能直接当人流量用,所以单独建工具。
3. **折线零填充有 bug**(:518-525):首次出现的 gender 用 `curValueList.add(dayOrder, v)` 是**插入**而非覆盖
(valueList 预填充时又漏了下标 0),序列与日期错位;后续 `set(dayOrder, ...)` 才是对的。新工具修正为
按区间长度预填 0 再 set。
4. **gender 为 null 直接 NPE**(:387 `gender.toString()`),老数据有 null 时会 500。新工具归为「未配置」分组。
5. percent 用 `DecimalFormat("######0.00")` 截断两位;totalCount=0 时 percent=0。
6. 天数上限校验被 try/catch 吞掉(:366-368),Redis 缺 StatisticsDays 时静默放行。新工具 fail fast
(与 paper 系列一致)。
7. idStrList 拼串(`123_F1_456`)是前端树组件的传参方式,MCP 场景语义差,
建议改为与 `district.paper.device_average.query` 一致的 `buildingIds/floors/toiletIds` 三个列表 IN。
## 待拍板决策
**2026-09-24 用户拍板:全部按建议执行(决策 1-7 锁定),并追加两条:**
- **决策 8(追加):日期区间硬限一个月(31 天)**,超出直接报错「查询时间段最长 31 天(一个月),请修改日期范围」。
本工具**不依赖** Redis `Dictionary.StatisticsDays`(硬限即上限,避免双口径)。
- **决策 9(追加):存量工具 `district.paper.percapita.query`(33)与 `district.paper.device_average.query`(34)同步加 31 天硬限**,
原有 StatisticsDays 检查保留(两者取更严)。
| # | 决策点 | 建议 | 备选 |
|---|---|---|---|
| 1 | 工具拆分 | **一个工具两个 view**(对齐 34 号工具合并风格) | 两个工具 1:1 对齐老接口 |
| 2 | 权限 | **做行级权限**(`applyDistrictRowFilter`,与同表的 district.paper.* 一致,不可见返回「站点不存在或无权访问」) | 对齐老系统裸奔(权限已注释) |
| 3 | 位置过滤入参 | **buildingIds/floors/toiletIds 三列表 IN**(对齐 34 号工具) | 复制老 idStrList 拼串 |
| 4 | deviceType 取值 | **不限枚举**,任意非负整数透传(老系统无约束;人流量不限于出纸设备) | 限制 0/1(与纸工具一致) |
| 5 | endDate 晚于今天 | **拒绝**(对齐新工具族;monthly_statistics 是日汇总,查未来无意义) | 老系统不限制,放行 |
| 6 | gender=null | 归为 `genderCode=null, gender="未配置"` 分组 | 跳过该行(老系统 NPE) |
| 7 | 分类 | **customer**(按「分类都放到 customer」口径) | hardware |
## 参数契约
- `districtId`:Long,必填,正整数。
- `startDate` / `endDate`:String,必填,yyyy-MM-dd,含当天;startDate ≤ endDate;endDate 不得晚于今天(决策 5);
区间自然日数 ≤ Redis `Dictionary.StatisticsDays`(缺失/非法 fail fast)。
- `view`:String,必填,`gender`(占比)或 `daily_gender`(按日趋势)。
- `deviceType`:Integer,可选,非负整数透传 `device_type =`(决策 4)。
- `buildingIds` / `floors` / `toiletIds`:List,可选,元素须正整数/非空串;各维度内部 IN、维度之间 AND
(对齐 34 号工具;替代老 idStrList 拼串,决策 3)。
固定条件:`soft_delete=0`、`district_id=`、`statistics_time` 在区间内。
聚合在 DB 完成(GROUP BY gender / DATE_FORMAT(statistics_time)+gender),不查 ES。
## 返回契约
公共:`districtId / districtName / dateRange{startDate,endDate,days} / view / unit="人次"`。
`view=gender`(对齐老饼图 PieStatisticsOutput):
```
totalCount 区间内 SUM(use_count)
rows[]: genderCode / gender / useCount / percent percent=useCount*100/totalCount,HALF_UP 两位;totalCount=0 时 percent=0
notice? 无数据时说明
```
`view=daily_gender`(对齐老折线 LineChartStatisticsOutput,修正零填充错位 bug):
```
dates[] 区间内全部日期(yyyy-MM-dd 升序)
totalCount
series[]: genderCode / gender / useCount(合计) / values[] values 与 dates 等长对齐,无数据日期为 0
notice?
```
gender 文案:0男卫、1女卫、2母婴室、3无障碍、4第三卫、5其他;null 为「未配置」,其他整数 `未知(code)`
(与 34 号工具一致)。
## 与老系统的差异汇总
1. 两个接口合并为一个工具的 view 参数。
2. 行级权限:老系统注释掉了,新工具按同表工具惯例做 `applyDistrictRowFilter`(待拍板,决策 2)。
3. idStrList 拼串改为 buildingIds/floors/toiletIds 三列表(决策 3)。
4. 折线零填充错位 bug 修正(老 :518-525)。
5. gender=null 归「未配置」,不再 NPE。
6. Redis StatisticsDays 缺失 fail fast(老系统 try/catch 吞掉)。
7. endDate 不得晚于今天(老系统不限制,决策 5)。
## 测试要点
- 参数校验:缺 districtId/日期、startDate>endDate、endDate 晚于今天、超 StatisticsDays、view 非法、
deviceType 负数、位置列表非法元素。
- 权限:站点不可见返回「站点不存在或无权访问」(若决策 2 通过);tenant_service 按总部视角。
- view=gender:多 gender 聚合、percent 两位小数、totalCount=0 时 percent=0、未知 code、null gender 归未配置。
- view=daily_gender:零填充序列与 dates 等长对齐(回归老系统插入错位 bug)、跨月区间、空数据。
- 位置三维 IN/AND 语义、deviceType 透传、StatisticsDays 缺失 fail fast。