diff --git a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md index 78e1886..b131a2c 100644 --- a/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md +++ b/xiaoqu-agent-service/doc/agent-mcp-tools-reference.md @@ -755,7 +755,7 @@ - 入参:`districtId`(必填正整数)、`startDate`/`endDate`(必填 yyyy-MM-dd,startDate≤endDate≤今天)、 `positionMode`(position=仅厕位/wall=仅厕墙,只作用于 ES 换纸卷数口径)、`deviceType`(0/1)、`buildingId`、`floor`、`toiletId`。 - 聚合维度 gender(0男1女2母婴3残卫4第三5其他99未知): - `paperLengthM`=Σpaper_length/100(米,3 位小数)、`useCount`=Σuse_count(厕墙行恒 0 直接求和)、 + `paperLengthM`=Σpaper_length/100(米,3 位小数)、`useCount`=Σuse_count(仅厕位模式 runl_mode=0 的厕位行累计,厕墙行恒 0)、 `deviceDayCount`=按日 distinct 厕位/厕数之和(老系统 perPositionsCount,输出改名)、 `perUseLengthM`=paperLengthM/useCount(useCount=0 为 null);另输出按日明细(date/gender/paperLengthM/useCount/deviceDayCount)。 - 换纸卷数 `rfidCount`(ES):读 `district_strategy.rfid_use_flag`(行 id=站点 id)—— diff --git a/xiaoqu-agent-service/doc/district-paper-statistics-design.md b/xiaoqu-agent-service/doc/district-paper-statistics-design.md index dd5d366..771ff8d 100644 --- a/xiaoqu-agent-service/doc/district-paper-statistics-design.md +++ b/xiaoqu-agent-service/doc/district-paper-statistics-design.md @@ -19,8 +19,8 @@ (实现为左闭右开 `[start, end+1)` 避免秒精度丢行);可选 `building_id/floor/toilet_id/device_type` 精确匹配。 - 聚合维度 `gender`(gender 为 null 的行跳过,老 :178-180): - `paperLengthM` = Σpaper_length / 100(paper_length 单位厘米,输出米,3 位小数); - - `useCount` = Σuse_count(老代码对厕位/厕墙、runl_mode 有多个分支,但厕墙行 use_count 恒 0, - 等价于直接求和——老 :188-203 分支差异不影响结果); + - `useCount` = Σuse_count(**仅 position_id>0 且 runl_mode=0 的厕位模式行累计**,厕墙行恒 0; + 这是老 :200-203、:220-222 分支的意图——老 :188 的日分组已存在分支漏判 runlMode,按意图统一); - `deviceDayCount` = 按日 distinct 厕位/厕数之和(老 :190-192、:249:positionSet 存 `p_{positionId}`(厕位行)与 `t_{toiletId}`(厕墙行),每天取 set size 再累加)。 老出参字段名 perPositionsCount 注释有误,本工具改名 deviceDayCount); @@ -91,7 +91,7 @@ | rfid 去重实现 | count+分页拉全量文档,内存 Set 去重 | terms 聚合 size 10000 数 bucket | 不拉全量、低内存低耗时;上限 10000 对正常站点足够,超限部分不计(见 §2.2) | | district_strategy 不存在 / rfid_use_flag=null | NPE(:324-325) | 按"未使用 rfid"口径走 state=2 count | 该表与站点 1:1,缺失视为未启用 rfid;避免 500 | | Dictionary 字典缺失 | NPE 被外层 catch 吞掉,继续执行 | 抛 IllegalStateException | fail fast,避免权限/口径配置异常导致错误数据范围 | -| use_count 聚合 | 厕位/厕墙/runl_mode 多分支 | 直接求和 | 厕墙行恒 0,分支差异不影响结果,简化口径 | +| use_count 聚合 | 厕位/厕墙/runl_mode 多分支,:188 已存在日分组分支漏判 runlMode | 仅 position_id>0 且 runl_mode=0 行累计 | 统一为老代码新分组分支的意图口径(厕位模式使用人次),消除老代码分支不一致的 bug | | DB 时间过滤 | between 含两端(秒精度) | 左闭右开 [start, end+1) | 等价且避免 23:59:59 之后毫秒级数据丢行 | ## 6. 行级权限说明 diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsTools.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsTools.java index b960b14..aea5e1a 100644 --- a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsTools.java +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsTools.java @@ -233,13 +233,21 @@ public class DistrictPaperStatisticsTools implements ToolObject { int paperLength = row.getPaperLength() == null ? 0 : row.getPaperLength(); int useCount = row.getUseCount() == null ? 0 : row.getUseCount(); agg.paperCm += paperLength; - agg.useCount += useCount; + boolean positionRow = row.getPositionId() != null && row.getPositionId() > 0; + // useCount 仅厕位模式(runl_mode=0)的厕位行累计,厕墙恒 0(老 :200-203/220-222 意图; + // 老代码日分组已存在分支漏判 runlMode,此处按意图统一,见设计文档差异点) + boolean countUse = positionRow && row.getRunlMode() != null && row.getRunlMode() == 0; + if (countUse) { + agg.useCount += useCount; + } String day = DAY_FORMAT.format(row.getStatisticsTime().toInstant().atZone(SHANGHAI)); DailyAgg dailyAgg = agg.daily.computeIfAbsent(day, key -> new DailyAgg()); dailyAgg.paperCm += paperLength; - dailyAgg.useCount += useCount; + if (countUse) { + dailyAgg.useCount += useCount; + } // 厕位行按 positionId、厕墙行按 toiletId 计台(老 "p_"/"t_" positionSet) - if (row.getPositionId() != null && row.getPositionId() > 0) { + if (positionRow) { dailyAgg.positionKeys.add("p_" + row.getPositionId()); } else { dailyAgg.positionKeys.add("t_" + row.getToiletId()); diff --git a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsToolsTest.java b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsToolsTest.java index 80be52f..b264ba1 100644 --- a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsToolsTest.java +++ b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperStatisticsToolsTest.java @@ -116,6 +116,7 @@ class DistrictPaperStatisticsToolsTest { row.setToiletId(toiletId); row.setPaperLength(paperLengthCm); row.setUseCount(useCount); + row.setRunlMode(0); return row; } @@ -285,6 +286,27 @@ class DistrictPaperStatisticsToolsTest { anyLong(), anyLong(), any(), any()); } + @Test + void useCountOnlyAccumulatesForPositionModeRows() { + mockDistrictAndDictionary(); + MonthlyStatistics wallModeRow = row(today.minusDays(1), 11L, 3L, 0, 100, 5); + wallModeRow.setRunlMode(1); + when(monthlyStatisticsMapper.selectList(any())).thenReturn(List.of( + wallModeRow, + row(today.minusDays(1), 11L, 3L, 0, 100, 2))); + when(districtStrategyMapper.selectOne(any())).thenReturn(null); + when(esGateway.countState2(anyString(), anyBoolean(), anyLong(), anyInt(), anyLong(), anyLong(), + any(), any())).thenReturn(0L); + + Map result = tools.districtPaperStatisticsQuery(9L, start, end, null, null, null, null, null); + + @SuppressWarnings("unchecked") + List> summary = (List>) result.get("summaryByGender"); + // runl_mode=1 的厕位行 useCount 不计入,仅厕位模式(runl_mode=0)累计 + assertEquals(2L, summary.get(0).get("useCount")); + assertEquals(2.0, summary.get(0).get("paperLengthM")); + } + @Test void useCountZeroYieldsNullPerUseLength() { mockDistrictAndDictionary();