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

7.0 KiB
Raw Blame History

人流量统计 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。