/** * T46 一级模式图层计划与选择态矩阵 纯逻辑检查(票 46;规格 18 / 决策票 13)。 * * 零依赖 node 断言脚本(约定见 t01-panel-catalog.mjs)。 * 运行:node .scratch/cockpit-shell-panel-layout/verify/t46-mode-layer-plan.mjs * * 主 seam 是 `domain/modeLayerPlan.js` 的纯函数:五种状态 × 各对象层的可见矩阵、 * 异常点判定(只认设备状态事实)、图例与标记一致、引导门控、降级说明、回联目标与选择态。 */ import { MODE_LAYER, MODE_SELECTION, planModeLayers, planModeSelectionTransition, isAssociatedDeviceVisible, isAttentionDeviceStatus, } from '../../../src/modules/cockpit/domain/modeLayerPlan.js' import { legendMatchesMarkers } from '../../../src/modules/cockpit/domain/pointStatus.js' const checks = [] function check(name, pass, detail = '') { checks.push({ name, pass }) const rest = detail === '' ? '' : ` — ${detail}` console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${rest}`) } /** 造一张摘要对象集:2 监测点(1 异常)+ 1 未关联设备。 */ function fixtureObjects() { return [ { kind: 'monitoring-point', code: 'MP-A', name: '异常点监测点', status: 'NORMAL', deviceCodes: ['EQ-A1', 'EQ-A2'], longitude: 110.1, latitude: 28.4, coordinate: { state: 'VALID', reason: '', renderable: true, validated: true }, }, { kind: 'monitoring-point', code: 'MP-B', name: '正常点监测点', status: 'NORMAL', deviceCodes: [], longitude: 110.2, latitude: 28.4, coordinate: { state: 'VALID', reason: '', renderable: true, validated: true }, }, { kind: 'unassociated-equipment', code: 'EQ-FREE-1', name: '异常未关联设备', status: '', deviceCodes: [], longitude: 110.3, latitude: 28.4, coordinate: { state: 'VALID', reason: '', renderable: true, validated: true }, }, ] } /** 异常设备事实(照 `/monitoring/situation` 的领域形状)。 */ function fixtureAbnormal() { return [ { code: 'EQ-A1', name: '关联报警设备', status: 'ALARM' }, { code: 'EQ-FREE-1', name: '异常未关联设备', status: 'WARNING' }, ] } /** 关联设备行(placement 已由 deviceLayer.js 判好;计划只做模式过滤)。 */ function fixtureAssociated() { return [ { code: 'EQ-A1', pointCode: 'MP-A', status: 'ALARM', placement: 'drawn', longitude: 110.11, latitude: 28.41 }, { code: 'EQ-A2', pointCode: 'MP-A', status: 'NORMAL', placement: 'drawn', longitude: 110.12, latitude: 28.42 }, ] } function planOf(mode, extra = {}) { return planModeLayers({ mode, objects: fixtureObjects(), abnormalDevices: fixtureAbnormal(), associatedDevices: fixtureAssociated(), entered: true, ...extra, }) } // ================================================================ A 完整可见矩阵 { const overview = planOf(MODE_LAYER.OVERVIEW) const infra = planOf(MODE_LAYER.INFRASTRUCTURE) const monitoring = planOf(MODE_LAYER.MONITORING) const warning = planOf(MODE_LAYER.WARNING_RESPONSE) const codesOf = (plan) => plan.markers.map((m) => `${m.kind}|${m.code}`).sort() check('A1-overview-shows-everything', codesOf(overview).length === 3 && codesOf(overview).includes('monitoring-point|MP-A') && codesOf(overview).includes('monitoring-point|MP-B') && codesOf(overview).includes('unassociated-equipment|EQ-FREE-1'), `总览 ${codesOf(overview).join(' ')}`) check('A2-infrastructure-hides-monitoring', infra.markers.length === 0, `基础设施 ${infra.markers.length} 个标记(监测点/未关联设备/关联设备全隐藏)`) check('A3-monitoring-shows-points', codesOf(monitoring).length === 3, `运行监测 ${codesOf(monitoring).join(' ')}`) check('A4-warning-only-abnormal-points', codesOf(warning).length === 2 && codesOf(warning).includes('monitoring-point|MP-A') && codesOf(warning).includes('unassociated-equipment|EQ-FREE-1'), `预警处置 ${codesOf(warning).join(' ')}(正常点 MP-B 不在、异常未关联设备在)`) // 关联设备图层按票 05 锁定的范围:概览/运行监测全部,基础设施隐藏,预警处置仅 ALARM/WARNING。 check('A5-associated-device-matrix', overview.associatedDevices.length === 2 && monitoring.associatedDevices.length === 2 && infra.associatedDevices.length === 0 && warning.associatedDevices.length === 1 && warning.associatedDevices[0].code === 'EQ-A1', `关联设备各模式 ${[overview, infra, monitoring, warning].map((p) => p.associatedDevices.length).join('/')}`) // 异常点判定只认 WARNING/ALARM:工单/离线不混入(规格 Q8)。 const onlyWorkOrder = planModeLayers({ mode: MODE_LAYER.WARNING_RESPONSE, objects: fixtureObjects(), abnormalDevices: [ { code: 'EQ-A1', status: 'WORK_ORDER' }, { code: 'EQ-FREE-1', status: 'OFFLINE' }, ], entered: true, }) check('A6-work-order-offline-not-abnormal', onlyWorkOrder.markers.length === 0, `工单/离线不构成异常点(标记 ${onlyWorkOrder.markers.length} 个)`) // 异常设备数据未就绪 → 不猜:预警处置不画任何点,且给降级说明(规格:不借用点位聚合状态)。 const noAbnormalData = planOf(MODE_LAYER.WARNING_RESPONSE, { abnormalDevices: null }) check('A7-abnormal-data-missing-degrades-honestly', noAbnormalData.markers.length === 0 && noAbnormalData.degradedNotes.some((note) => note.key === 'abnormal-devices-unready'), `异常数据未就绪:标记 ${noAbnormalData.markers.length} 个,降级说明 ${ noAbnormalData.degradedNotes.map((n) => n.key).join(',') || '无' }`) // 管线四模式常驻(进入后);设施只在概览/基础设施可见。 const pipelinePlan = (mode) => planModeLayers({ mode, objects: fixtureObjects(), abnormalDevices: fixtureAbnormal(), pipelines: [], entered: true, }) check('A8-pipeline-resident-in-all-modes', [MODE_LAYER.OVERVIEW, MODE_LAYER.INFRASTRUCTURE, MODE_LAYER.MONITORING, MODE_LAYER.WARNING_RESPONSE] .every((mode) => pipelinePlan(mode).layerStates.pipelines === 'ready'), '管线在四模式均为可见层(契约就绪=ready;未就绪应是 degraded 而非 hidden)') // 未就绪也不许被模式隐藏(常驻 ≠ 数据就绪才常驻): const pipelineUnready = (mode) => planModeLayers({ mode, objects: fixtureObjects(), abnormalDevices: fixtureAbnormal(), pipelines: null, entered: true, }) check('A8b-pipeline-unready-still-visible-not-hidden', [MODE_LAYER.OVERVIEW, MODE_LAYER.INFRASTRUCTURE, MODE_LAYER.MONITORING, MODE_LAYER.WARNING_RESPONSE] .every((mode) => pipelineUnready(mode).layerStates.pipelines === 'degraded'), '管线数据未就绪四模式都是 degraded(degraded ≠ hidden:不把缺数据伪装成"该模式没有")') check('A9-facility-only-overview-infrastructure', pipelinePlan(MODE_LAYER.OVERVIEW).layerStates.facilities === 'degraded' && pipelinePlan(MODE_LAYER.INFRASTRUCTURE).layerStates.facilities === 'degraded' && pipelinePlan(MODE_LAYER.MONITORING).layerStates.facilities === 'hidden' && pipelinePlan(MODE_LAYER.WARNING_RESPONSE).layerStates.facilities === 'hidden', '设施点只在概览/基础设施可见,运行监测/预警处置隐藏') } // ================================================================ B 引导态门控 { const plan = planModeLayers({ mode: MODE_LAYER.OVERVIEW, objects: fixtureObjects(), abnormalDevices: fixtureAbnormal(), associatedDevices: fixtureAssociated(), entered: false, }) check('B1-intro-plan-empty', plan.markers.length === 0 && plan.legend.length === 0 && plan.locateTargets.length === 0 && plan.associatedDevices.length === 0 && plan.facilities.length === 0 && plan.pipelines.length === 0 && plan.selection.visible === false, `引导态全空(markers ${plan.markers.length} / legend ${plan.legend.length} / targets ${plan.locateTargets.length})`) } // ================================================================ C 图例与标记同源 { for (const mode of Object.values(MODE_LAYER)) { const plan = planOf(mode) const verdict = legendMatchesMarkers(plan.legend, plan.markers) check(`C1-legend-matches-markers-${mode}`, verdict.ok, verdict.problems.join(';') || '图例与标记逐键一致') } } // ================================================================ D 回联目标 { const warning = planOf(MODE_LAYER.WARNING_RESPONSE) check('D1-locate-targets-follow-mode', warning.locateTargets.length === 2 && warning.locateTargets.every((t) => ['MP-A', 'EQ-FREE-1'].includes(t.code)), `预警处置回联目标 ${warning.locateTargets.map((t) => t.code).join('/')}`) const infra = planOf(MODE_LAYER.INFRASTRUCTURE) check('D2-infrastructure-has-no-monitoring-targets', infra.locateTargets.length === 0, '基础设施模式下没有监测点回联目标(不拿监测点坐标冒充设施)') } // ================================================================ E 降级说明(数据未就绪 ≠ 空数据) { const withArrays = planModeLayers({ mode: MODE_LAYER.INFRASTRUCTURE, objects: [], facilities: [], pipelines: [], entered: true, }) check('E1-ready-layers-no-note', withArrays.degradedNotes.length === 0 && withArrays.layerStates.facilities === 'ready', '设施/管线契约就绪(空数组也是就绪)时无降级说明') const withoutArrays = planModeLayers({ mode: MODE_LAYER.INFRASTRUCTURE, objects: [], facilities: null, pipelines: null, entered: true, }) check('E2-unready-layers-degraded-notes', withoutArrays.degradedNotes.some((n) => n.key === 'facilities-unready') && withoutArrays.degradedNotes.some((n) => n.key === 'pipelines-unready') && withoutArrays.layerStates.facilities === 'degraded', `未就绪说明 ${withoutArrays.degradedNotes.map((n) => n.key).join(',')}`) } // ================================================================ F 选择态语义 { const pointSelection = { level: 'point', pointCode: 'MP-B', deviceCode: '' } // 总览↔分项:保留身份;目标模式显示不了就暂停(visible=false),切回自动恢复。 const infra = planOf(MODE_LAYER.INFRASTRUCTURE, { selection: pointSelection }) const back = planOf(MODE_LAYER.OVERVIEW, { selection: pointSelection }) check('F1-preserve-suspends-in-infrastructure', infra.selection.visible === false && back.selection.visible === true, `选 MP-B:基础设施暂停(visible=${infra.selection.visible})、切回总览恢复(visible=${back.selection.visible})`) // 分项↔分项:清空。 const t = planModeSelectionTransition({ fromMode: 'monitoring', toMode: 'warningResponse', selection: pointSelection }) check('F2-submode-switch-clears-selection', t.action === MODE_SELECTION.CLEAR && t.selection.level === 'none', '分项↔分项清空选择') const t2 = planModeSelectionTransition({ fromMode: 'overview', toMode: 'monitoring', selection: pointSelection }) check('F3-overview-submode-preserves', t2.action === MODE_SELECTION.PRESERVE && t2.selection.pointCode === 'MP-B', '总览↔分项保留选择身份') // 选中的设备在预警处置隐藏(NORMAL)→ 暂停;报警设备可见。 const normalDeviceSelection = { level: 'equipment', pointCode: 'MP-A', deviceCode: 'EQ-A2' } const warningHidden = planOf(MODE_LAYER.WARNING_RESPONSE, { selection: normalDeviceSelection }) const alarmDeviceSelection = { level: 'equipment', pointCode: 'MP-A', deviceCode: 'EQ-A1' } const warningShown = planOf(MODE_LAYER.WARNING_RESPONSE, { selection: alarmDeviceSelection }) check('F4-device-selection-pauses-by-mode', warningHidden.selection.visible === false && warningShown.selection.visible === true, `NORMAL 设备选中在预警处置暂停(${warningHidden.selection.visible})、ALARM 设备可见(${warningShown.selection.visible})`) // 未关联设备选中:基础设施隐藏,预警处置只认异常的那台。 const freeSelection = { level: 'equipment', pointCode: '', deviceCode: 'EQ-FREE-1' } const infraFree = planOf(MODE_LAYER.INFRASTRUCTURE, { selection: freeSelection }) const warningFree = planOf(MODE_LAYER.WARNING_RESPONSE, { selection: freeSelection }) check('F5-free-equipment-selection', infraFree.selection.visible === false && warningFree.selection.visible === true, `未关联设备在基础设施暂停(${infraFree.selection.visible})、预警处置可见(${warningFree.selection.visible})`) // 回归(真机人工走查抓到):设备级选择 + **不上图**(`no-location`,现网正式边界 // `UNVERIFIED_COORDINATE/renderable=false`)的设备 → 详情仍应可见。 // 详情面板与该设备能否上图无关 —— 一台无地图位置的设备仍要看档案/指标/预警分区。 // 曾误判为不可见导致外壳 `