// 批次 3 合成计划断言(验收清单第 1 项):四套变体归属、尺寸层级、锚点、内层几何、着色与降饱和。 // 直接跑 node,无需浏览器。 import { planMarker, MASTER_GEOMETRY, MARKER_WIDTH } from '../../../src/modules/cockpit/domain/markerPlan.js' let pass = 0 let fail = 0 function eq(actual, expected, label) { const a = JSON.stringify(actual) const e = JSON.stringify(expected) if (a === e) { pass++; console.log(` ✓ ${label}`) } else { fail++; console.log(` ✗ ${label}\n 期望 ${e}\n 实际 ${a}`) } } console.log('① 母版归属唯一') eq(planMarker({ kind: 'monitoring-point', status: 'ALARM' }).master, 'solid', '报警监测点 = 实心') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL' }).master, 'solid', '正常监测点 = 实心') eq(planMarker({ kind: 'monitoring-device', status: 'ALARM' }).master, 'solid', '关联设备 = 实心') eq(planMarker({ kind: 'monitoring-point', status: 'EMPTY' }).master, 'hollow', 'EMPTY 点位 = 镂空') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', unassociated: true }).master, 'hollow', '未关联设备 = 镂空') console.log('② 内层图标规则') eq(planMarker({ kind: 'monitoring-point', status: 'EMPTY', industry: 'GAS' }).inner.enabled, false, 'EMPTY 点位不用内层(即使有行业)') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', unassociated: true, industry: 'GAS' }).inner.enabled, true, '未关联设备用行业图标') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', unassociated: true, industry: 'GAS' }).inner.icon, 'gas', '未关联设备内层 = gas') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', industry: 'WATER_SUPPLY' }).inner.icon, 'water', '关联设备内层 = water') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', industry: 'DRAINAGE' }).inner.icon, 'drainage', '关联设备内层 = drainage') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', industry: 'MANHOLE' }).inner.icon, 'manhole', '关联设备内层 = manhole') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL', industry: 'UNKNOWN_X' }).inner.enabled, false, '未知行业不画内层') console.log('③ 尺寸层级:监测点恒大于设备(2026-10-12 走查反馈放大 50%:27/21)') for (const status of ['ALARM', 'WARNING', 'WORK_ORDER', 'NORMAL', 'OFFLINE']) { const p = planMarker({ kind: 'monitoring-point', status }).size.width const d = planMarker({ kind: 'monitoring-device', status }).size.width if (p > d) { pass++; console.log(` ✓ ${status}: 点 ${p} > 设备 ${d}`) } else { fail++; console.log(` ✗ ${status}: 点 ${p} <= 设备 ${d}`) } } eq(MARKER_WIDTH.point, 27, '监测点本体宽基准 = 27(18 × 1.5)') eq(MARKER_WIDTH.device, 21, '设备本体宽基准 = 21(14 × 1.5)') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL' }).size.height, Math.round(27 * MASTER_GEOMETRY.aspect * 100) / 100, '点高 = 宽 × 母版比例') console.log('③b billboard 绘制尺寸含描边余量(draw)') const dp = planMarker({ kind: 'monitoring-point', status: 'NORMAL' }) if (dp.draw.width > dp.size.width && dp.draw.height > dp.size.height) { pass++; console.log(` ✓ draw ${dp.draw.width}x${dp.draw.height} > 本体 ${dp.size.width}x${dp.size.height}(描边余量已含)`) } else { fail++; console.log(` ✗ draw ${dp.draw.width}x${dp.draw.height} 未含余量`) } if (dp.draw.pad > 0) { pass++; console.log(` ✓ pad 屏显 = ${dp.draw.pad}`) } else { fail++; console.log(' ✗ pad 缺失') } console.log('④ 锚点 = 尖端;标签偏移在图标上方(修重叠)') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL' }).anchor, { x: 0.4987, y: 1.0 }, '锚点在 bbox 底部中心(尖端)') const lp = planMarker({ kind: 'monitoring-point', status: 'NORMAL' }) if (lp.labelOffsetY < 0 && Math.abs(lp.labelOffsetY) > lp.size.height) { pass++; console.log(` ✓ 标签偏移 ${lp.labelOffsetY} 在图标上沿之外(高 ${lp.size.height})`) } else { fail++; console.log(` ✗ 标签偏移 ${lp.labelOffsetY} 未越过图标底 ${lp.size.height}`) } const ld = planMarker({ kind: 'monitoring-device', status: 'NORMAL' }) if (ld.labelOffsetY < 0 && Math.abs(ld.labelOffsetY) > ld.size.height) { pass++; console.log(` ✓ 设备标签偏移 ${ld.labelOffsetY} 在图标上沿之外(高 ${ld.size.height})`) } else { fail++; console.log(` ✗ 设备标签偏移 ${ld.labelOffsetY} 未越过图标底 ${ld.size.height}`) } console.log('⑤ 内层几何:80% 直径 + 帽心对齐(走查反馈 60% 看不清 → 两连放到 80%)') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', industry: 'GAS' }).inner.diameterRatio, 0.8, '内层直径 = 80%') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', industry: 'GAS' }).inner.center, MASTER_GEOMETRY.capCenter, '内层圆心 = 帽心') console.log('⑥ 着色与降饱和') eq(planMarker({ kind: 'monitoring-point', status: 'ALARM' }).tint, 'lv1', '报警 tint = lv1(状态推导)') eq(planMarker({ kind: 'monitoring-point', status: 'WARNING' }).tint, 'lv2', '预警 tint = lv2') eq(planMarker({ kind: 'monitoring-point', status: 'WORK_ORDER' }).tint, 'lv3', '工单 tint = lv3') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL' }).tint, 'lv4', '正常 tint = lv4') eq(planMarker({ kind: 'monitoring-point', status: 'OFFLINE' }).tint, 'neutral', '离线 tint = neutral') eq(planMarker({ kind: 'monitoring-point', status: 'EMPTY' }).tint, 'neutral', 'EMPTY tint = neutral') eq(planMarker({ kind: 'monitoring-point', status: 'MAINTENANCE' }).tint, 'neutral', '未知状态 tint = neutral(不猜)') eq(planMarker({ kind: 'monitoring-point', status: 'NORMAL', tone: 'lv1' }).tint, 'lv1', '调用方可覆盖 tone') eq(planMarker({ kind: 'monitoring-device', status: 'NORMAL', unassociated: true }).desaturate, true, '未关联设备 = 降饱和') eq(planMarker({ kind: 'monitoring-device', status: 'ALARM' }).desaturate, false, '关联设备不降饱和') eq(planMarker({ kind: 'monitoring-point', status: 'EMPTY' }).desaturate, false, 'EMPTY 点位不降饱和') console.log(`\n合成计划断言:${pass} 过 / ${fail} 败`) if (fail) process.exitCode = 1