- 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
7.0 KiB
人流量统计 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。 天数上限走 RedisDictionary.StatisticsDays。
老接口的行为与坑:
- 权限校验是注释掉的(
// User user = UserUtils.getUser(request)),裸奔。 use_count即人流量,与用纸长度无关——现有district.paper.statistics.query虽也返回 useCount, 但受厕位/厕墙口径约束(厕墙行恒 0,老 :200-203 意图),不能直接当人流量用,所以单独建工具。- 折线零填充有 bug(:518-525):首次出现的 gender 用
curValueList.add(dayOrder, v)是插入而非覆盖 (valueList 预填充时又漏了下标 0),序列与日期错位;后续set(dayOrder, ...)才是对的。新工具修正为 按区间长度预填 0 再 set。 - gender 为 null 直接 NPE(:387
gender.toString()),老数据有 null 时会 500。新工具归为「未配置」分组。 - percent 用
DecimalFormat("######0.00")截断两位;totalCount=0 时 percent=0。 - 天数上限校验被 try/catch 吞掉(:366-368),Redis 缺 StatisticsDays 时静默放行。新工具 fail fast (与 paper 系列一致)。
- 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); 区间自然日数 ≤ RedisDictionary.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 号工具一致)。
与老系统的差异汇总
- 两个接口合并为一个工具的 view 参数。
- 行级权限:老系统注释掉了,新工具按同表工具惯例做
applyDistrictRowFilter(待拍板,决策 2)。 - idStrList 拼串改为 buildingIds/floors/toiletIds 三列表(决策 3)。
- 折线零填充错位 bug 修正(老 :518-525)。
- gender=null 归「未配置」,不再 NPE。
- Redis StatisticsDays 缺失 fail fast(老系统 try/catch 吞掉)。
- 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。