t16-alarm-order.mjs 6.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import assert from 'node:assert/strict'
  2. import { acceptWorkOrderDelta } from '../../../src/modules/cockpit/domain/workOrderPush.js'
  3. import { abnormalCountRows } from '../../../src/modules/cockpit/domain/abnormalCounts.js'
  4. import { toHandlingEfficiency, handlingRateView, handlingRateHint, responseEfficiencyPanel, toEventAnalysis, toEventHistory } from '../../../src/modules/cockpit/domain/warningResponsePanels.js'
  5. import { toWorkOrder } from '../../../src/modules/cockpit/domain/workOrder.js'
  6. import { workOrderRows } from '../../../src/modules/cockpit/domain/equipmentDetailView.js'
  7. import { toWorkOrderDelta } from '../../../src/modules/cockpit/services/runtimeApi.js'
  8. import { handleFrame, onDelta, onWorkOrderDelta, resetForTest } from '../../../src/modules/cockpit/services/runtimeSocket.js'
  9. import { planModeLayers } from '../../../src/modules/cockpit/domain/modeLayerPlan.js'
  10. import { toWorkOrderBrief, refreshedEventSelection } from '../../../src/modules/cockpit/domain/eventList.js'
  11. import { eventDetailView } from '../../../src/modules/cockpit/domain/eventDetail.js'
  12. import { fetchHandlingEfficiency } from '../../../src/modules/cockpit/services/warningResponseApi.js'
  13. const history = toEventHistory({ rows: [{ timeBucket: '2026-10-09', eventCount: 3 }] })
  14. assert.equal(history.trend[0].count, 3)
  15. assert.equal(history.bucketed, true)
  16. const analysis = toEventAnalysis({ dimensions: [{ dimensionCode: 'EVENT_TYPE', buckets: [{ bucketCode: 'GAS', bucketLabel: '燃气报警', count: 4 }] }, { dimensionCode: 'DISTRICT', buckets: [{ bucketCode: '0', bucketLabel: '城南', count: 2 }] }] })
  17. assert.equal(analysis.types[0].count, 4)
  18. assert.equal(analysis.regions[0].label, '城南')
  19. assert.equal(toEventAnalysis({ dimensions: { types: [{ code: 'X', count: 7 }] } }).types[0].count, 7)
  20. const raw = { summary: { totalEvents: 99, handlingEfficiency: { value: '0.6667', numerator: 2, denominator: 3, state: 'AVAILABLE', basis: 'CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS' } }, workOrderStateCounts: [{ stateCode: 5, count: 2 }, { stateCode: 6, count: 1 }, { stateCode: 7, count: 4 }] }
  21. const duration = responseEfficiencyPanel(toHandlingEfficiency({ summary: { averageDuration: { value: 2.4, unit: 'HOURS' } }, durationTrend: [{ timeBucket: 'day', averageDuration: { value: 30, unit: 'MINUTES' } }] }), { state: 'success' })
  22. assert.equal(duration.blocks[0].items.find((row) => row.key === 'closeDuration').value, '2.4 小时')
  23. assert.equal(duration.blocks[2].points[0].displayText, '30 分钟')
  24. const rate = toHandlingEfficiency(raw)
  25. assert.equal(handlingRateView(rate), '66.7%')
  26. assert.equal(handlingRateView({ ...rate, denominator: 0 }), '不适用')
  27. assert.equal(handlingRateHint({ ...rate, denominator: 0 }), '当前范围没有可计入工单,处置率不适用')
  28. assert.doesNotMatch(JSON.stringify(responseEfficiencyPanel(rate, { state: 'success' })), /CLOSED_WORK_ORDERS/)
  29. assert.equal(rate.workOrderStates.find((row) => row.code === '6').count, 1)
  30. const result = await fetchHandlingEfficiency({ config: { dataSource: 'real', api: { baseUrl: 'http://test.invalid' } }, fetchImpl: async () => ({ ok: true, status: 200, json: async () => raw }) })
  31. assert.equal(result.state, 'success')
  32. assert.deepEqual(abnormalCountRows([{ status: 'WORK_ORDER', count: 100 }, { status: 'ALARM', count: 2 }], 'point').map((row) => row.count), [2, 0, 0, 0])
  33. assert.deepEqual(abnormalCountRows([{ status: 'NORMAL', count: 100 }, { status: 'EXPIRED', count: 3 }], 'metric').map((row) => row.count), [0, 0, 3, 0, 0])
  34. for (const [flag, text] of [[true, '已关联当前事件'], [false, '未关联当前事件'], [undefined, '当前事件关联关系未发布']]) {
  35. assert.equal(workOrderRows([toWorkOrder({ workOrderCode: 'WO', stateCode: 0, associatedWithCurrentEvent: flag })])[0].associationText, text)
  36. }
  37. for (let code = 0; code <= 7; code++) assert.ok(toWorkOrderBrief({ stateCode: code }).stateName)
  38. assert.equal(eventDetailView({ workOrderCode: 'WO', workOrderStateName: '延期中' }).workOrderStateName, '延期中')
  39. const selected = { code: 'EVENT', workOrderStateName: '处理中' }
  40. assert.match(refreshedEventSelection(selected, null).refreshNotice, /上次读取/)
  41. assert.match(refreshedEventSelection(selected, { sections: { events: { state: 'success', data: [] } } }).refreshNotice, /上次读取/)
  42. const updated = refreshedEventSelection(selected, { sections: { events: { state: 'success', data: [{ code: 'EVENT', workOrder: { code: 'WO', stateName: '已结案' } }] } } })
  43. assert.equal(updated.workOrderStateName, '已结案')
  44. assert.equal(updated.refreshNotice, '')
  45. resetForTest()
  46. let deviceFrames = 0
  47. let orderFrames = 0
  48. onDelta(() => { deviceFrames++ })
  49. const off = onWorkOrderDelta(() => { orderFrames++ })
  50. const frame = { type: 'cockpitWorkOrderStateDelta', serverUpdatedAt: '2026-10-09T12:00:00+08:00', scanIntervalMs: 60000, data: { items: [{ workOrderCode: 'WO', deviceCode: null, stateCode: 0 }] } }
  51. assert.equal(toWorkOrderDelta(frame).items[0].stateCode, 0)
  52. assert.equal(toWorkOrderDelta(frame).scanIntervalMs, 60000)
  53. const accepted = acceptWorkOrderDelta({}, toWorkOrderDelta(frame))
  54. assert.equal(accepted.items.length, 1)
  55. assert.equal(acceptWorkOrderDelta(accepted.seen, toWorkOrderDelta(frame)).items.length, 0)
  56. assert.equal(acceptWorkOrderDelta(accepted.seen, { ...toWorkOrderDelta(frame), items: [{ code: 'WO-OTHER' }] }).items.length, 1)
  57. assert.equal(acceptWorkOrderDelta(accepted.seen, { ...toWorkOrderDelta(frame), serverUpdatedAt: '2026-10-08T12:00:00+08:00' }).items.length, 0)
  58. assert.equal(acceptWorkOrderDelta({}, null).items.length, 0)
  59. handleFrame(JSON.stringify(frame))
  60. assert.equal(orderFrames, 1)
  61. assert.equal(deviceFrames, 0)
  62. off()
  63. handleFrame(frame)
  64. assert.equal(orderFrames, 1)
  65. assert.equal(handleFrame({ ...frame, serverUpdatedAt: 'invalid' }), null)
  66. assert.equal(handleFrame({ type: 'unknown' }), null)
  67. resetForTest()
  68. const statuses = ['WARNING', 'ALARM', 'OFFLINE', 'EXPIRED']
  69. const objects = statuses.map((status, index) => ({ kind: 'monitoring-point', code: `p${index}`, deviceCodes: [`d${index}`], status: 'NORMAL', longitude: 110, latitude: 28, coordinate: { renderable: true } }))
  70. const plan = planModeLayers({ mode: 'warningResponse', objects, abnormalDevices: statuses.map((status, index) => ({ code: `d${index}`, status })), facilities: [], pipelines: [] })
  71. assert.deepEqual(plan.markers.map((row) => row.code), ['p0', 'p1'])
  72. console.log('T16 contract assertions passed: rate, zero denominator, buckets, association, 0-7 dictionary, order-frame isolation, narrow map.')