# 07: 地图摘要状态筛选与未关联设备语义规格(来自决策票 02) Type: spec Status: ready-for-agent Label: ready-for-agent Blocked by: 02 Category: spec 本规格固化[决策票 02](02-map-status-filter-and-unassociated-devices.md)的维护者确认结果,作为[同一张 map](../map.md)的子规格单。它是「真实数据接线」阶段地图摘要筛选、未关联设备语义、全局状态筛选入口与自动渐进加载的实施输入;不覆盖 `.scratch/cockpit-shell-panel-layout/spec.md`。 ## 进度:100% 规格文档已发布为 `ready-for-agent` 并挂到同一决策 map;100% 仅指规格交付,**不表示筛选契约、自动加载或饼图已实现**。后端契约改动(多值 `pointStatus`、命中设备子集、八桶全局计数、坐标汇总筛选后口径)需按地图约定在决策票收口后进入后端执行票;前端接线边界在本规格内。 ## Problem Statement 值班人员需要在地图上按点位状态聚焦观察,但当前实现存在五处缺口与缺陷: 1. **状态筛选不作用到一张图**:`map-snapshot` 不收 `pointStatus`(票 25 I1),状态条件只作用到监测分布面板与台账;值班人员选了状态,地图不跟随,界面却写着「全局筛选」。 2. **未关联设备没有状态语义**:未关联设备没有点位状态来源,此前只能「不参与状态筛选」或「被排除」,与「行业/范围/类型筛选对两类对象都生效」的语义不一致。 3. **筛选死胡同**:监测分布面板选中计数为 0 的状态后显示「该面板当前没有数据」,筛选行被吞掉,无法回到未筛选或改选其它状态。 4. **入口分散且重复**:状态筛选入口只在监测分布面板内,非全局可操作;地图顶部状态统计栏只读不可选。 5. **手动渐进加载**:地图摘要需要手动点「加载下一页」「刷新快照」,存在冗余按钮,也不符合系统自动分批加载的性能目标。 ## Solution 从值班人员视角:地图顶部的状态统计栏成为**全局组合筛选入口**(八桶多选 OR),筛选对监测点与未关联设备**都生效**,但各自使用各自的状态来源;监测分布面板改为饼图展示且不再自带筛选;地图摘要自动渐进加载,右上角保留一个小刷新图标(🔄)供手动换新快照。 关键语义(详见决策票 02 的 Answer): - **行业/范围/类型筛选**=设备级匹配、点位级 OR 归并:监测点是设备聚合点,任一设备命中 `industry/region/deviceType` → 其所属监测点进入结果;未关联设备按自身这三个属性直接匹配。`map-snapshot` 与 `/monitoring/distribution` 两端同一语义。 - **状态筛选**对两类对象都生效、各自使用各自的状态来源:监测点按后端聚合 `pointStatus`(六态)匹配,不做设备级 OR 归并;未关联设备按设备状态词表(五态 + 状态未发布 UNKNOWN)匹配;`EMPTY`(无设备)只适用于监测点。 - **「无关联设备」桶**=只看未关联设备这类对象(类别筛选),与状态筛选正交:选状态但没选「无关联设备」桶 → 显示该状态的点位与未关联设备;只选「无关联设备」桶 → 只看未关联设备。 - **状态栏计数**=全局未筛全集(后端全量、不分页);筛选只改选中高亮、不改计数。状态栏与监测分布饼图共用同一份全局未筛分布。 - **`pointStatus` 契约**=单参数多值(逗号分隔或重复参数,OR):`pointStatus=ALARM,WARNING,UNKNOWN,UNASSOCIATED`;`UNKNOWN`=点位上状态缺失或不在六态词典(未关联设备=设备状态未发布);认不出的值 400 `INVALID_REQUEST`;`mapFilter` 回显整套生效值;不传=全部。 - **坐标汇总**=筛选后口径(默认不筛时是全局,筛了就是筛选集内全局)。 - **组合筛选**=AND(行业 ∧ 范围 ∧ 类型 ∧ 状态桶);`mapFilter` 回显全部生效维度;结果为空走既有 `EMPTY`,前端不猜原因。 - **展开点位**:只显示命中设备 + 一行可展开的「另有 N 台」(未命中设备);未命中设备展开后可完全交互(hover/选中/定位/进详情),仅视觉弱化;设备总数 X=该点位全部关联设备、已上图 Y=命中且上图子集;`map-snapshot` 点位响应带命中设备子集(未筛=全部),前端无需 N+1。 - **自动渐进加载与刷新**:移除「加载下一页」按钮,进入系统/筛选变化后自动连续分批加载到完成(批次间留帧间隔);移除「刷新快照」按钮,右上角保留 🔄 手动刷新入口;自动刷新时机=筛选变化(换新快照第 1 页)、新鲜度周期到点、相关 WS 增量到达。 - **死胡同根治**:筛选控件常驻可操作;结果为空只是结果区一句说明,不吞掉入口。 ## User Stories 1. 作为值班人员,我想在一张图上方始终看到点位状态统计栏,以便快速了解当前全局状态分布。 2. 作为值班人员,我想点击状态统计栏中的状态项就能按状态筛选地图,以便聚焦报警/预警等关注对象。 3. 作为值班人员,我想同时选择多个状态(如报警+预警)查看地图,以便一次看到多类关注点位。 4. 作为值班人员,我想选了某个状态后未关联设备仍能按自身设备状态出现在地图上,以便不会漏掉正在报警的未挂接设备。 5. 作为值班人员,我想只选「无关联设备」桶时地图只看未关联设备,以便单独核查未挂接设备。 6. 作为值班人员,我想按行业筛选时点位的归属由它包含的设备决定(任一设备命中即显示),以便符合「监测点是设备聚合点」的业务认知。 7. 作为值班人员,我想同时组合行业、范围、类型、状态筛选,以便精确缩小区间。 8. 作为值班人员,我想点开一个命中筛选的监测点时只看到命中的设备,以便展开结果与筛选语义一致。 9. 作为值班人员,我想展开「另有 N 台」能看到并操作未命中设备(hover/选中/定位/详情),以便不被筛选挡住必要操作。 10. 作为值班人员,我想点位详情的设备总数显示该点位全部设备、已上图数显示命中子集,以便数字不自相矛盾。 11. 作为值班人员,我想状态栏计数始终是全局未筛全集,切筛选不改计数,以便统计口径稳定可对照。 12. 作为值班人员,我想监测分布面板用饼图展示状态分布,以便直观看出占比。 13. 作为值班人员,我想筛选结果为空时仍能操作筛选入口(取消/改选),以便不会陷入「无法再筛」的死胡同。 14. 作为值班人员,我想地图摘要自动分批加载直到完成,以便无需手动点「加载下一页」。 15. 作为值班人员,我想在右上角保留一个小刷新图标手动换新快照,以便需要时强制刷新。 16. 作为值班人员,我想筛选变化时地图自动换新快照从第 1 页重载,以便不会出现旧范围对象混在新范围内。 17. 作为值班人员,我想坐标汇总说明与当前筛选后的对象集合一致,以便地图上点少了、摘要也如实跟随。 18. 作为值班人员,我想后端对认不出的状态值返回 400 而不是静默忽略,以便错误参数能被发现。 19. 作为值班人员,我想监测分布面板不再自带筛选入口,以便全局条件只有一个可操作面。 20. 作为部署/维护者,我想通过 `mapFilter` 回显看到实际生效的筛选组合,以便排障时不靠猜。 ## Implementation Decisions ### 后端契约(供后端执行票) - `map-snapshot`: - 入参增加多值 `pointStatus`(逗号分隔或重复参数),取值=六态 + `UNKNOWN` + `UNASSOCIATED`;认不出的值 400 `INVALID_REQUEST`;`mapFilter` 回显整套生效值。 - `UNKNOWN`=点位上 `pointStatus` 缺失或不在六态词典;`UNASSOCIATED`=结果包含未关联设备(类别筛选,不改变点位状态匹配)。 - 行业/范围/类型筛选改按**设备级匹配、点位 OR 归并**:任一设备命中 → 点位进入;未关联设备按自身属性匹配。与 `/monitoring/distribution` 同一语义。 - 点位响应带**命中设备子集**(`deviceCodes`/设备明细按当前筛选裁剪;未筛=全部)。 - 未筛时提供**八桶全局计数**(六态 + `UNKNOWN` + `UNASSOCIATED`),供状态栏与饼图共用;筛选时计数不变。 - 坐标校验汇总按**筛选后口径**计算。 - `/monitoring/distribution`: - 入参多值化(同 `map-snapshot` 语义);`pointStatusDistribution` 未筛时含八桶;筛选语义与 `map-snapshot` 一致。 - 两端点对「认不出的值 400」与 `mapFilter` 回显的约定统一。 ### 前端 - **统一「地图集合计划」纯函数层**(新模块,与 `domain/modeLayerPlan.js` 同构):输入筛选(行业/范围/类型/状态集合)与地图批次,输出地图对象集合 + 状态栏八桶计数 + 命中设备子集,一次算清;组件/store 只消费它,不做第二次过滤判断。 - `stores/filterStore.js`:`pointStatus` 由单值改为**多值集合**(空集=全部),增删/清空语义化;与行业/范围/类型同层级、切模式不重置。 - `stores/mapStore.js` + `domain/mapSummary.js` 装载器:接入多值 `pointStatus` 入参;新增**自动分批加载**(进入/筛选变化后连续 `loadNext` 至 settled,批次间留帧间隔,被新筛选打断按 token 纪律);刷新时机=筛选变化、freshness tick、WS 增量,统一走 `refresh()`。 - 组件层: - 地图顶部状态栏(`MapCanvas` 图例区)从只读变为**可多选的全局筛选入口**(八桶 OR、全局计数、选中高亮);行常驻可点(含计数 0 的行)。 - 右上角新增 **🔄 手动刷新**入口,调用 `mapStore.refresh()`;移除「加载下一页」「刷新快照」按钮(调试形态可保留)。 - 监测分布面板:移除 `statusFilter` 块与自带筛选;状态统计改为**饼图**(消费全局未筛分布,高亮当前选中)。 - 点位展开(关联设备图层):按命中设备子集渲染 + 「另有 N 台」可展开;未命中设备可完全交互但弱化;总数/已上图口径如实。 - 视觉与文案:状态词表继续只来自 `domain/pointStatus.js`(六态 + UNKNOWN + UNASSOCIATED)与 `domain/deviceStatus.js`(设备五态);饼图与状态栏共用同一份全局未筛分布数据。 ## Testing Decisions - **好测试的定义**:只测外部行为与契约语义——筛选入参拼装、点位归并结果、八桶计数、命中设备子集、自动分批加载状态机、刷新时机、死胡同可操作性;不测组件内部实现。 - **主接缝**:统一的「地图集合计划」纯函数(注入批次与筛选 → 断言对象集合/计数/子集)+ `mapSummary` 装载器(注入取数 → 断言分页/快照/自动分批/刷新)。沿用仓库既有模式:`.scratch/cockpit-shell-panel-layout/verify/tNN-*.mjs` 零依赖逻辑闸门(先例 `t08-map-summary.mjs`、`t09-point-status.mjs`、`t18-monitoring-panels.mjs`、`t46-mode-layer-plan.mjs`)。 - **浏览器走查**(沿用 `-walkthrough.mjs` + `data-*` 断言面):状态栏多选高亮与计数不变、🔄 手动刷新、饼图渲染、展开「另有 N 台」交互、死胡同修复(选中 0 计数状态后仍可改选)、自动分批加载完成态。 - **边界断言**:`pointStatus` 白名单(六态 + UNKNOWN + UNASSOCIATED,认不出不传参的既有兜底在「非法值 400」契约下调整语义);`EMPTY` 桶不出现在未关联设备匹配中;点位不跨状态重复(按聚合状态匹配);组合筛选 AND 语义与 `mapFilter` 回显一致。 - **变异测试**:对归并/计数/白名单守卫做变异确认有牙(先例 t18 的做法)。 ## Out of Scope - 「异常统计」的状态词汇与统计口径(归决策票 06)。 - 工单状态、报警—工单闭环(归决策票 01/06)。 - 设备级状态词表本身(`domain/deviceStatus.js`)的定义与视觉(关联设备图层既有契约);本规格只引用它作为未关联设备匹配取值。 - real 模式下新鲜度 tick / WS 增量与装载器的**接线细节**(归决策票 04);本规格只定「刷新时机」的语义。 - 后端实现与真实联调(后端执行票按地图约定在相关决策收口后创建)。 - 移动端、UE5、Docker、国产化认证及非驾驶舱领域。 ## Further Notes - 本规格依赖决策票 02(已 resolved);后端契约改动需在决策票 03(数据时间)、04(real 生命周期)、05(联调基线)、06(业务语义)收口后进入后端执行票。 - **2026-10-08 修订(按决策票 06)**:范围维度由「自由串 `region` + `regionMap` 配置」改为**片区**(`district` `0/1/2 → 城南/城北/太常`,空值/乱码=未分区);本文中涉及 `region`/`regionMap` 的表述以该修订为准。 - 领域词汇已同步 `CONTEXT.md`:新增「未关联设备」「状态筛选」词条。 - 未运行 build/debug 验证(遵循仓库 AGENTS.md 规则);本规格只交付实施输入。