waterAlarmGisModel.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { getMonitoringPointStatus } from './gisMapColors.js'
  2. import { isActiveWorkOrderStatus } from './waterSupplyUiModel.js'
  3. /**
  4. * 监测报警一张图的点位状态阈值:
  5. * 同一设备只保留一个点位,点位状态按“关联工单 > 报警 > 预警 > 离线 > 正常”取最高风险语义。
  6. * 该状态只服务于地图着色,不替代设备报警状态、报警记录状态或工单状态。
  7. */
  8. const alarmLevelLabels = { 1: '一级报警', 2: '二级报警', 3: '三级报警', 4: '四级报警' }
  9. export function deriveAlarmGisStatus(point = {}) {
  10. const sharedStatus = getMonitoringPointStatus(point)
  11. if (sharedStatus !== 'normal') return sharedStatus
  12. const hasAlarmEvent = point.alarmId ?? point.id ?? point.warningType ?? point.alarmType
  13. return hasAlarmEvent && Number(point.recordStatus) !== 1 ? 'alarm' : 'normal'
  14. }
  15. const displayStatusLabels = { 1: '待审核', 2: '已转工单', 3: '已解除' }
  16. function deviceKeyOf(record = {}) {
  17. const key = record.deviceCode ?? record.equipmentCode ?? record.equipmentId
  18. return key === null || key === undefined ? '' : String(key)
  19. }
  20. function hasCoordinate(value) {
  21. return String(value ?? '').trim() !== '' && Number.isFinite(Number(value))
  22. }
  23. function numberOrUndefined(value) {
  24. return value === null || value === undefined || value === '' ? undefined : Number(value)
  25. }
  26. function toText(...values) {
  27. const found = values.find(value => value !== null && value !== undefined && String(value).trim() !== '')
  28. return found === undefined ? undefined : found
  29. }
  30. /** 台账按设备建索引时优先保留仍有关联工单的那条报警 */
  31. function preferLedgerRow(candidate, current) {
  32. if (!current) return true
  33. const candidateActive = Boolean(candidate.orderNo) && isActiveWorkOrderStatus(candidate.orderStatus)
  34. const currentActive = Boolean(current.orderNo) && isActiveWorkOrderStatus(current.orderStatus)
  35. return candidateActive && !currentActive
  36. }
  37. function indexLedgerRows(ledgerRows) {
  38. const byAlarmId = new Map()
  39. const byDeviceCode = new Map()
  40. ledgerRows.forEach((row) => {
  41. if (!row) return
  42. const alarmId = row.id ?? row.alarmId
  43. if (alarmId !== null && alarmId !== undefined && !byAlarmId.has(String(alarmId))) {
  44. byAlarmId.set(String(alarmId), row)
  45. }
  46. const deviceCode = deviceKeyOf(row)
  47. if (!deviceCode) return
  48. if (preferLedgerRow(row, byDeviceCode.get(deviceCode)) || !byDeviceCode.has(deviceCode)) {
  49. byDeviceCode.set(deviceCode, row)
  50. }
  51. })
  52. return { byAlarmId, byDeviceCode }
  53. }
  54. function buildDevicePointIndex(devicePoints) {
  55. const byDeviceCode = new Map()
  56. devicePoints.forEach((device) => {
  57. const deviceCode = deviceKeyOf(device)
  58. if (deviceCode && !byDeviceCode.has(deviceCode)) byDeviceCode.set(deviceCode, device)
  59. })
  60. return byDeviceCode
  61. }
  62. function pickRepresentative(alarms) {
  63. return [...alarms].sort((a, b) => {
  64. const levelA = numberOrUndefined(a.alarmLevel)
  65. const levelB = numberOrUndefined(b.alarmLevel)
  66. if (levelA === undefined && levelB === undefined) return 0
  67. if (levelA === undefined) return 1
  68. if (levelB === undefined) return -1
  69. return levelA - levelB
  70. })[0]
  71. }
  72. /**
  73. * 报警点位归一化:只保留有坐标的报警点,同一设备合并为一个点位,
  74. * 并用报警台账与设备点位补齐设备报警状态、报警记录状态、报警展示状态和关联工单状态。
  75. * 三类状态各自成字段,任何一类缺失都不会用另一类顶替。
  76. */
  77. export function buildAlarmGisPoints({ alarmPoints = [], ledgerRows = [], devicePoints = [] } = {}) {
  78. const alarmList = (Array.isArray(alarmPoints) ? alarmPoints : [])
  79. .filter(point => point && hasCoordinate(point.longitude ?? point.lng) && hasCoordinate(point.latitude ?? point.lat))
  80. if (!alarmList.length) return []
  81. const { byAlarmId, byDeviceCode: ledgerByDeviceCode } = indexLedgerRows(Array.isArray(ledgerRows) ? ledgerRows : [])
  82. const deviceByDeviceCode = buildDevicePointIndex(Array.isArray(devicePoints) ? devicePoints : [])
  83. const groups = new Map()
  84. alarmList.forEach((point, index) => {
  85. const key = deviceKeyOf(point) || `alarm-${point.alarmId ?? index}`
  86. const group = groups.get(key) || []
  87. group.push(point)
  88. groups.set(key, group)
  89. })
  90. const built = []
  91. groups.forEach((alarms, deviceCode) => {
  92. const representative = pickRepresentative(alarms)
  93. const alarmId = representative.alarmId ?? representative.id
  94. const ledgerRow = (alarmId !== undefined && byAlarmId.get(String(alarmId)))
  95. || ledgerByDeviceCode.get(deviceCode)
  96. || {}
  97. const devicePoint = deviceByDeviceCode.get(deviceCode) || {}
  98. const recordStatus = numberOrUndefined(ledgerRow.alarmStatus)
  99. const deviceAlarmStatus = numberOrUndefined(devicePoint.alarmStatus)
  100. ?? numberOrUndefined(ledgerRow.deviceAlarmStatus)
  101. const displayStatus = numberOrUndefined(ledgerRow.displayStatus)
  102. const longitude = Number(representative.longitude ?? representative.lng)
  103. const latitude = Number(representative.latitude ?? representative.lat)
  104. const alarmLevel = numberOrUndefined(representative.alarmLevel)
  105. const point = {
  106. ...representative,
  107. deviceCode: toText(representative.deviceCode, deviceKeyOf(ledgerRow), deviceKeyOf(devicePoint), deviceCode),
  108. deviceName: toText(representative.deviceName, devicePoint.equipmentName, ledgerRow.deviceName),
  109. longitude,
  110. latitude,
  111. lng: longitude,
  112. lat: latitude,
  113. alarmCount: alarms.length,
  114. alarmLevel,
  115. alarmLevelText: toText(representative.alarmLevelText, alarmLevelLabels[alarmLevel]),
  116. recordStatus,
  117. recordStatusText: toText(ledgerRow.alarmStatusText, recordStatus === undefined ? '未获取' : recordStatus === 1 ? '已处理' : '待处理'),
  118. deviceAlarmStatus,
  119. deviceAlarmStatusText: toText(ledgerRow.deviceAlarmStatusText, deviceAlarmStatus === undefined ? '未获取' : deviceAlarmStatus === 1 ? '报警中' : '正常'),
  120. displayStatus,
  121. displayStatusText: toText(ledgerRow.displayStatusText, displayStatusLabels[displayStatus]),
  122. orderNo: toText(ledgerRow.orderNo, representative.orderNo),
  123. orderStatus: numberOrUndefined(ledgerRow.orderStatus) ?? numberOrUndefined(representative.orderStatus),
  124. onlineStatus: numberOrUndefined(devicePoint.onlineStatus) ?? numberOrUndefined(representative.onlineStatus),
  125. currentStatus: numberOrUndefined(devicePoint.currentStatus) ?? numberOrUndefined(representative.currentStatus),
  126. onlineStatusText: toText(devicePoint.onlineStatusText, representative.onlineStatusText),
  127. currentStatusText: toText(devicePoint.currentStatusText, representative.currentStatusText)
  128. }
  129. point.statusKey = deriveAlarmGisStatus(point)
  130. built.push(point)
  131. })
  132. return built.sort((a, b) => {
  133. const levelA = a.alarmLevel ?? Number.MAX_SAFE_INTEGER
  134. const levelB = b.alarmLevel ?? Number.MAX_SAFE_INTEGER
  135. return levelA - levelB
  136. })
  137. }
  138. const searchableFields = ['deviceName', 'deviceCode', 'name', 'warningType', 'alarmType', 'equipmentLocation', 'location']
  139. export function filterAlarmGisPoints(points = [], keyword = '') {
  140. const list = Array.isArray(points) ? points : []
  141. const needle = String(keyword ?? '').trim().toLocaleLowerCase()
  142. if (!needle) return list
  143. return list.filter((point) => searchableFields
  144. .some(field => String(point?.[field] ?? '').toLocaleLowerCase().includes(needle)))
  145. }
  146. export function summarizeAlarmGisPoints(points = []) {
  147. const list = Array.isArray(points) ? points : []
  148. const byStatus = { normal: 0, alarm: 0, warning: 0, workOrder: 0, offline: 0 }
  149. let highLevel = 0
  150. list.forEach((point) => {
  151. const statusKey = point?.statusKey ?? deriveAlarmGisStatus(point)
  152. if (Object.prototype.hasOwnProperty.call(byStatus, statusKey)) byStatus[statusKey] += 1
  153. const alarmLevel = numberOrUndefined(point?.alarmLevel)
  154. if (alarmLevel !== undefined && alarmLevel <= 2) highLevel += 1
  155. })
  156. return { total: list.length, highLevel, byStatus }
  157. }