import { getMonitoringPointStatus } from './gisMapColors.js' import { isActiveWorkOrderStatus } from './waterSupplyUiModel.js' /** * 监测报警一张图的点位状态阈值: * 同一设备只保留一个点位,点位状态按“关联工单 > 报警 > 预警 > 离线 > 正常”取最高风险语义。 * 该状态只服务于地图着色,不替代设备报警状态、报警记录状态或工单状态。 */ const alarmLevelLabels = { 1: '一级报警', 2: '二级报警', 3: '三级报警', 4: '四级报警' } export function deriveAlarmGisStatus(point = {}) { const sharedStatus = getMonitoringPointStatus(point) if (sharedStatus !== 'normal') return sharedStatus const hasAlarmEvent = point.alarmId ?? point.id ?? point.warningType ?? point.alarmType return hasAlarmEvent && Number(point.recordStatus) !== 1 ? 'alarm' : 'normal' } const displayStatusLabels = { 1: '待审核', 2: '已转工单', 3: '已解除' } function deviceKeyOf(record = {}) { const key = record.deviceCode ?? record.equipmentCode ?? record.equipmentId return key === null || key === undefined ? '' : String(key) } function hasCoordinate(value) { return String(value ?? '').trim() !== '' && Number.isFinite(Number(value)) } function numberOrUndefined(value) { return value === null || value === undefined || value === '' ? undefined : Number(value) } function toText(...values) { const found = values.find(value => value !== null && value !== undefined && String(value).trim() !== '') return found === undefined ? undefined : found } /** 台账按设备建索引时优先保留仍有关联工单的那条报警 */ function preferLedgerRow(candidate, current) { if (!current) return true const candidateActive = Boolean(candidate.orderNo) && isActiveWorkOrderStatus(candidate.orderStatus) const currentActive = Boolean(current.orderNo) && isActiveWorkOrderStatus(current.orderStatus) return candidateActive && !currentActive } function indexLedgerRows(ledgerRows) { const byAlarmId = new Map() const byDeviceCode = new Map() ledgerRows.forEach((row) => { if (!row) return const alarmId = row.id ?? row.alarmId if (alarmId !== null && alarmId !== undefined && !byAlarmId.has(String(alarmId))) { byAlarmId.set(String(alarmId), row) } const deviceCode = deviceKeyOf(row) if (!deviceCode) return if (preferLedgerRow(row, byDeviceCode.get(deviceCode)) || !byDeviceCode.has(deviceCode)) { byDeviceCode.set(deviceCode, row) } }) return { byAlarmId, byDeviceCode } } function buildDevicePointIndex(devicePoints) { const byDeviceCode = new Map() devicePoints.forEach((device) => { const deviceCode = deviceKeyOf(device) if (deviceCode && !byDeviceCode.has(deviceCode)) byDeviceCode.set(deviceCode, device) }) return byDeviceCode } function pickRepresentative(alarms) { return [...alarms].sort((a, b) => { const levelA = numberOrUndefined(a.alarmLevel) const levelB = numberOrUndefined(b.alarmLevel) if (levelA === undefined && levelB === undefined) return 0 if (levelA === undefined) return 1 if (levelB === undefined) return -1 return levelA - levelB })[0] } /** * 报警点位归一化:只保留有坐标的报警点,同一设备合并为一个点位, * 并用报警台账与设备点位补齐设备报警状态、报警记录状态、报警展示状态和关联工单状态。 * 三类状态各自成字段,任何一类缺失都不会用另一类顶替。 */ export function buildAlarmGisPoints({ alarmPoints = [], ledgerRows = [], devicePoints = [] } = {}) { const alarmList = (Array.isArray(alarmPoints) ? alarmPoints : []) .filter(point => point && hasCoordinate(point.longitude ?? point.lng) && hasCoordinate(point.latitude ?? point.lat)) if (!alarmList.length) return [] const { byAlarmId, byDeviceCode: ledgerByDeviceCode } = indexLedgerRows(Array.isArray(ledgerRows) ? ledgerRows : []) const deviceByDeviceCode = buildDevicePointIndex(Array.isArray(devicePoints) ? devicePoints : []) const groups = new Map() alarmList.forEach((point, index) => { const key = deviceKeyOf(point) || `alarm-${point.alarmId ?? index}` const group = groups.get(key) || [] group.push(point) groups.set(key, group) }) const built = [] groups.forEach((alarms, deviceCode) => { const representative = pickRepresentative(alarms) const alarmId = representative.alarmId ?? representative.id const ledgerRow = (alarmId !== undefined && byAlarmId.get(String(alarmId))) || ledgerByDeviceCode.get(deviceCode) || {} const devicePoint = deviceByDeviceCode.get(deviceCode) || {} const recordStatus = numberOrUndefined(ledgerRow.alarmStatus) const deviceAlarmStatus = numberOrUndefined(devicePoint.alarmStatus) ?? numberOrUndefined(ledgerRow.deviceAlarmStatus) const displayStatus = numberOrUndefined(ledgerRow.displayStatus) const longitude = Number(representative.longitude ?? representative.lng) const latitude = Number(representative.latitude ?? representative.lat) const alarmLevel = numberOrUndefined(representative.alarmLevel) const point = { ...representative, deviceCode: toText(representative.deviceCode, deviceKeyOf(ledgerRow), deviceKeyOf(devicePoint), deviceCode), deviceName: toText(representative.deviceName, devicePoint.equipmentName, ledgerRow.deviceName), longitude, latitude, lng: longitude, lat: latitude, alarmCount: alarms.length, alarmLevel, alarmLevelText: toText(representative.alarmLevelText, alarmLevelLabels[alarmLevel]), recordStatus, recordStatusText: toText(ledgerRow.alarmStatusText, recordStatus === undefined ? '未获取' : recordStatus === 1 ? '已处理' : '待处理'), deviceAlarmStatus, deviceAlarmStatusText: toText(ledgerRow.deviceAlarmStatusText, deviceAlarmStatus === undefined ? '未获取' : deviceAlarmStatus === 1 ? '报警中' : '正常'), displayStatus, displayStatusText: toText(ledgerRow.displayStatusText, displayStatusLabels[displayStatus]), orderNo: toText(ledgerRow.orderNo, representative.orderNo), orderStatus: numberOrUndefined(ledgerRow.orderStatus) ?? numberOrUndefined(representative.orderStatus), onlineStatus: numberOrUndefined(devicePoint.onlineStatus) ?? numberOrUndefined(representative.onlineStatus), currentStatus: numberOrUndefined(devicePoint.currentStatus) ?? numberOrUndefined(representative.currentStatus), onlineStatusText: toText(devicePoint.onlineStatusText, representative.onlineStatusText), currentStatusText: toText(devicePoint.currentStatusText, representative.currentStatusText) } point.statusKey = deriveAlarmGisStatus(point) built.push(point) }) return built.sort((a, b) => { const levelA = a.alarmLevel ?? Number.MAX_SAFE_INTEGER const levelB = b.alarmLevel ?? Number.MAX_SAFE_INTEGER return levelA - levelB }) } const searchableFields = ['deviceName', 'deviceCode', 'name', 'warningType', 'alarmType', 'equipmentLocation', 'location'] export function filterAlarmGisPoints(points = [], keyword = '') { const list = Array.isArray(points) ? points : [] const needle = String(keyword ?? '').trim().toLocaleLowerCase() if (!needle) return list return list.filter((point) => searchableFields .some(field => String(point?.[field] ?? '').toLocaleLowerCase().includes(needle))) } export function summarizeAlarmGisPoints(points = []) { const list = Array.isArray(points) ? points : [] const byStatus = { normal: 0, alarm: 0, warning: 0, workOrder: 0, offline: 0 } let highLevel = 0 list.forEach((point) => { const statusKey = point?.statusKey ?? deriveAlarmGisStatus(point) if (Object.prototype.hasOwnProperty.call(byStatus, statusKey)) byStatus[statusKey] += 1 const alarmLevel = numberOrUndefined(point?.alarmLevel) if (alarmLevel !== undefined && alarmLevel <= 2) highLevel += 1 }) return { total: list.length, highLevel, byStatus } }