背景:CRM 经营画像需要"每客户设备数、离线数"。当前做法同步 devices 全量 (71,034 行,抽样 1000 台仅 13 台绑定物业,其余 property_id="0"),代价远大于收益。 本工具直接返回聚合结果: - 入参 propertyId 或 propertyIds(≤20,去重),includeUnbound 默认 false - 返回每物业 total/online/fault/offline + data_cutoff_at + notice - 口径与 hardware.list.query 一致:hardware_view、soft_delete=0、status 1/2/3 - 行级权限复用 PermissionService.applyHardwareRowFilter - 每物业 4 次 selectCount(项目未注册分页拦截器;无 clone 先例,重建 wrapper) 测试:DeviceStatisticsToolsTest 5 例(参数校验/超限/计数/权限调用/去重)全过。 设计文档:doc/hardware-statistics-query-mcp-tool-design.md(含待评审三问)。
4.1 KiB
4.1 KiB
hardware.statistics.query · MCP 工具设计
状态:待评审(评审通过后回写追加决策) 关联:CRM 客户经营画像 v1.1「设备台数 / 设备离线」因子 日期:2026-10-20
1. 背景
CRM 经营画像需要"某客户有几台设备、几台离线",用于:
- 价值分因子「设备台数」;
- 风险分因子「设备异常(离线占比)」。
当前下游做法是同步 devices.changes.list(m0.hardware_view 全量)后在 CRM 侧聚合,实测问题:
| 问题 | 数据 |
|---|---|
| 未绑定物业的设备占绝大多数 | 抽样 1000 台,property_id != 0 的仅 13 台(1.3%);未绑定用字符串 "0" 表示(不是空串) |
| 全量 71,034 行 | 同步 98.7% 无效行,占磁盘、拖慢同步 |
| 下游还要自己维护投影表 | 与"只读查询"的定位不符 |
结论:下游只需要统计值,应由 MCP 直接提供聚合结果。
2. 老系统行为与坑
| 项 | 结论 |
|---|---|
| 老接口 | hardware/listByPage(HardwareTools.hardware.list.query 已对齐),支持 propertyId / districtId / status / online 过滤并返回 total |
| 无现成统计接口 | 工具目录 60 个工具中没有站点设备数统计工具;district.paper.statistics.query 只统计用纸(米/人次/换纸卷数),不含设备数 |
| 数据源 | m0.hardware_view 是当前状态视图,无历史 → 统计只反映调用时刻 |
| 状态枚举 | status:1=在线 2=故障 3=离线;soft_delete=0 为有效行 |
| 未绑定表示 | property_id = 0(字符串/数值零),不是空 |
3. 决策表
| 决策 | 选择 | 备选 | 理由 |
|---|---|---|---|
| 工具名 | hardware.statistics.query |
device.statistics.query / hardware.count.query |
与既有 hardware.*、district.paper.statistics.query 命名一致 |
| 入参 | propertyId(单个)或 propertyIds(≤20) |
全租户一次返回 | 下游按客户查询;全量返回等于把聚合成本转给调用方,且无分页语义 |
| 默认是否含未绑定 | 不含(includeUnbound=false) |
含 | 未绑定无法归属客户,计入会污染"客户设备数" |
| 返回结构 | items[] 每物业一行:total/online/fault/offline + data_cutoff_at + notice |
只返回四个总数 | 下游需要多客户时一次拿全 |
| 权限 | 复用 PermissionService.applyHardwareRowFilter |
新写过滤逻辑 | 与 hardware.list.query 完全一致,避免第二套权限口径 |
| 查询方式 | 每个物业 4 次 selectCount |
单条 GROUP BY |
项目未注册分页拦截器;LambdaQueryWrapper 无 clone 先例,重建 wrapper 更安全。20 物业 = 80 次轻量 count,可接受 |
4. 参数契约
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
propertyId |
Long | 否(与 propertyIds 二选一) | 物业 id,正整数 |
propertyIds |
Long[] | 否 | 物业 id 列表,上限 20;重复值去重 |
includeUnbound |
Boolean | 否 | 默认 false;true 时计入 property_id = 0 的设备 |
校验:两者至少提供一个;id 必须为正;数量 ≤ 20;否则 IllegalArgumentException。
5. 返回契约
{
"items": [
{"property_id": 17, "total": 10, "online": 6, "fault": 2, "offline": 2}
],
"data_cutoff_at": "2026-10-20T08:40:00Z",
"notice": "数据源为 hardware_view 当前状态视图,仅反映调用时刻;未绑定物业设备未计入"
}
6. 与老系统差异
- 老系统无对应统计接口(只有分页列表 + total);本工具是其聚合封装,口径一致(同一张视图、同一套 status 枚举、同一套行级权限)。
- 不返回设备明细,避免下游误当列表用。
7. 测试要点
| # | 用例 |
|---|---|
| 1 | 未提供 propertyId / propertyIds → 报错 |
| 2 | 非正整数 / 超过 20 个 → 报错 |
| 3 | 单物业 → 4 个计数正确、返回 data_cutoff_at |
| 4 | 每个计数都调用行级权限过滤 |
| 5 | propertyIds 重复值去重 |
8. 待评审确认
- 是否同意"默认排除未绑定设备"?
- 单次上限 20 个物业是否合适?
- 是否需要同时返回
district_id维度(当前只按物业)?