/** * 票 71 部署后真实复验(2026-10-23)。 * * 对照 verify/artifacts/t71/expected-after-deploy.md 的部署前预演表,逐项判 PASS/FAIL: * 1. map-snapshot 里「预警未解决 + 未结束工单、无报警」的两台设备应已翻为 WORK_ORDER; * 2. 只有预警、没有工单的设备仍 WARNING; * 3. 报警设备 21119740 的点位仍 ALARM(票 52 分支不受改序影响); * 4. 未关联设备业务轴计数:预警 3→1、工单 3→5; * 5. Q3 留档:monitoring/situation 的 metricStatus 不跟随(两台仍是 WARNING)。 * * 用法:node verify-after-deploy.mjs */ import fs from 'node:fs' const BASE = 'http://192.168.110.96:8301/api/cockpit/v1' const OUT = 'after-deploy' fs.mkdirSync(OUT, { recursive: true }) const get = async (path, file) => { const res = await fetch(BASE + path) const text = await res.text() fs.writeFileSync(`${OUT}/${file}`, text) return JSON.parse(text) } const snap = (await get('/map-snapshot?page=1&pageSize=1000', 'map-snapshot.json')).data const situation = (await get('/monitoring/situation', 'situation.json')).data const devices = new Map(snap.unassociatedEquipment.map((d) => [d.deviceCode, d.deviceStatus])) const sortKeys = (o) => Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b))) const counts = {} for (const status of devices.values()) counts[status] = (counts[status] || 0) + 1 const checks = [] const check = (name, actual, expected) => { const ok = JSON.stringify(actual) === JSON.stringify(expected) checks.push({ name, ok, actual, expected }) console.log(`${ok ? 'PASS' : 'FAIL'} | ${name} | 实测 ${JSON.stringify(actual)} | 预期 ${JSON.stringify(expected)}`) } console.log('snapshotAt:', snap.snapshotAt) check('41010500001320000002 应翻 WORK_ORDER', devices.get('41010500001320000002'), 'WORK_ORDER') check('TEST-CBYL5432168 应翻 WORK_ORDER', devices.get('TEST-CBYL5432168'), 'WORK_ORDER') check('TEST-TCLLJ002215 无工单应仍 WARNING', devices.get('TEST-TCLLJ002215'), 'WARNING') check('860347065467627 有工单无预警应仍 WORK_ORDER', devices.get('860347065467627'), 'WORK_ORDER') check('41010500001320000001 应仍 NORMAL', devices.get('41010500001320000001'), 'NORMAL') check('8652052334 应仍 OFFLINE', devices.get('8652052334'), 'OFFLINE') const alarmPoint = snap.monitoringPoints.find((p) => (p.deviceCodes || []).includes('21119740')) check('21119740 所在点位应仍 ALARM', alarmPoint && alarmPoint.pointStatus, 'ALARM') check('未关联设备业务轴计数', sortKeys(counts), sortKeys({ NORMAL: 1, WARNING: 1, WORK_ORDER: 5, OFFLINE: 2 })) // Q3:situation 按事实判,不跟随业务序 —— 该端点的设备状态只在异常设备清单里(status 字段) const abnormal = situation.abnormalEquipment || [] const statusInSituation = (code) => { const row = abnormal.find((d) => d.deviceCode === code) return row ? row.status : 'NOT_LISTED' } // 「不跟随」的判据是**不出现 WORK_ORDER**(situation 没有这个桶,按事实落 WARNING/ALARM/OFFLINE), // 不钉死具体是 WARNING:设备在线态是独立事实,会随现网漂移(CBYL 今日即被判 OFFLINE)。 const checkNotFollowing = (name, code) => { const actual = statusInSituation(code) const ok = actual !== 'WORK_ORDER' checks.push({ name, ok, actual, expected: '≠ WORK_ORDER' }) console.log(`${ok ? 'PASS' : 'FAIL'} | ${name} | 实测 ${actual} | 预期 ≠ WORK_ORDER`) } checkNotFollowing('Q3: 41010500001320000002 的 situation 状态不跟随业务序', '41010500001320000002') checkNotFollowing('Q3: TEST-CBYL5432168 的 situation 状态不跟随业务序', 'TEST-CBYL5432168') const failed = checks.filter((c) => !c.ok) console.log(`\n复验 ${checks.length - failed.length}/${checks.length} 通过`) process.exit(failed.length === 0 ? 0 : 1)