diff --git a/tests/unit/handlers/coolingSystem.handlers.test.js b/tests/unit/handlers/coolingSystem.handlers.test.js index 3b88359..1691645 100644 --- a/tests/unit/handlers/coolingSystem.handlers.test.js +++ b/tests/unit/handlers/coolingSystem.handlers.test.js @@ -62,8 +62,8 @@ const createMockEquipment = () => ({ { equipment: 'TC-7501', is_active: true, miner_side_out_temp: { value: 37.1, unit: '°C' }, tower_side_in_temp: { value: 29.2, unit: '°C' }, tower_side_out_temp: { value: 36.9, unit: '°C' }, tcv_position: { value: 55, unit: '%' } } ], cooling_towers: [ - { equipment: 'TR-7501', is_running: true, fan_status: 'Running', fan_cv: { value: 60, unit: 'CV' }, level: { value: 82, unit: '%' }, vibration: { value: 0.8, unit: 'mm/s', status: 'Normal' } }, - { equipment: 'TR-7502', is_running: true, fan_status: 'Running', fan_cv: { value: 45, unit: 'CV' }, level: { value: 85, unit: '%' }, vibration: { value: 0.6, unit: 'mm/s', status: 'Normal' } } + { equipment: 'TR-7501', circuit: 'COOLING_TOWER', is_running: true, fan_status: 'Running', fan_cv: { value: 60, unit: 'CV' }, level: { value: 82, unit: '%' }, vibration: { value: 0.8, unit: 'mm/s', status: 'Normal' } }, + { equipment: 'TR-7502', circuit: 'HVAC_CONDENSER', is_running: true, fan_status: 'Running', fan_cv: { value: 45, unit: 'CV' }, level: { value: 85, unit: '%' }, vibration: { value: 0.6, unit: 'mm/s', status: 'Normal' } } ], valves: [ { equipment: 'PCV-7502', position: { value: 12, unit: '%' } }, @@ -1409,6 +1409,30 @@ test('6 - differential_pressure populated when per-group PTs configured', (t) => t.pass() }) +test('6 - differential_pressure from single-transmitter array (supply/return/diff)', (t) => { + const equipment = createMockEquipment() + equipment.pressures = [ + ...equipment.pressures, + { equipment: 'PT-7502-A', value: 2.81, unit: 'bar', supply_pressure: 2.81, return_pressure: 2.2, differential_pressure: 0.61 }, + { equipment: 'PT-7502-B', value: 2.9, unit: 'bar', supply_pressure: 2.9, return_pressure: 2.3 } + ] + const config = createMockConfig() + config.cooling_system.miner_loop.line1.group_pressure_sensors = ['PT-7502-A', 'PT-7502-B'] + const view = buildMinersCircuit1View(equipment, config) + + const dp = view.lines[0].differential_pressure + t.is(dp.length, 2, 'one row per transmitter') + // Group 1: device-provided differential is preferred + t.is(dp[0].supply.tag, 'PT-7502-A', 'supply tag = transmitter id') + t.is(dp[0].return.tag, 'PT-7502-A', 'return tag = same transmitter id') + t.is(dp[0].supply.reading.value, 2.81, 'supply from supply_pressure') + t.is(dp[0].return.reading.value, 2.2, 'return from return_pressure') + t.is(dp[0].delta_p.value, 0.61, 'uses device-provided differential_pressure') + // Group 2: no device differential -> derived from supply - return + t.is(dp[1].delta_p.value, 0.6, 'derived delta-p = supply - return when DP absent') + t.pass() +}) + test('7 - rack_statuses derived from live rack power (online = power > 0)', (t) => { const equipment = createMockEquipment() const config = createMockConfig() diff --git a/workers/lib/server/handlers/cooling.system.handlers.js b/workers/lib/server/handlers/cooling.system.handlers.js index fc0d328..3d49722 100644 --- a/workers/lib/server/handlers/cooling.system.handlers.js +++ b/workers/lib/server/handlers/cooling.system.handlers.js @@ -76,6 +76,34 @@ function buildVibrationSwitch (vibrationSwitches, switchTag) { function buildGroupDifferentialPressure (lineConfig, pressures) { const groupSensors = lineConfig.group_pressure_sensors || {} + + if (Array.isArray(groupSensors)) { + return groupSensors.map((sensorId, i) => { + const pt = pressures?.find(s => s.equipment === sensorId) || null + const mkSlot = (val) => ({ + tag: sensorId, + type: pt?.type || null, + reading: val != null ? { value: val, unit: pt?.unit || 'bar' } : null + }) + const supply = mkSlot(pt?.supply_pressure) + const ret = mkSlot(pt?.return_pressure) + const supplyVal = supply.reading?.value + const returnVal = ret.reading?.value + const deltaPVal = pt?.differential_pressure != null + ? Math.round(pt.differential_pressure * 100) / 100 + : (supplyVal != null && returnVal != null + ? Math.round((supplyVal - returnVal) * 100) / 100 + : null) + const unit = supply.reading?.unit || ret.reading?.unit || pt?.unit || 'bar' + return { + group: i + 1, + supply, + return: ret, + delta_p: deltaPVal != null ? { value: deltaPVal, unit } : null + } + }) + } + const supplyIds = groupSensors.supply || [] const returnIds = groupSensors.return || [] const count = Math.max(supplyIds.length, returnIds.length) @@ -183,13 +211,25 @@ function buildMinersCircuit1View (equipment, config) { // Compute summary values from line sensor data — units derived from sensors const tempUnit = lines[0]?.supply?.temperature?.unit const flowUnit = lines[0]?.supply?.flow?.unit - const pressureUnit = lines[0]?.supply?.pressure?.unit + // avg over the 16 per-group PTs, not the unset per-line pressure sensors + const allGroups = lines.flatMap(l => l.differential_pressure || []) + const pressureUnit = allGroups.find(g => g.supply?.reading?.unit)?.supply?.reading?.unit || + allGroups.find(g => g.return?.reading?.unit)?.return?.reading?.unit || + lines[0]?.supply?.pressure?.unit const allSupplyTemps = lines.map(l => l.supply.temperature?.value).filter(v => v != null) const allReturnTemps = lines.map(l => l.return.temperature?.value).filter(v => v != null) const allSupplyFlows = lines.map(l => l.supply.flow?.value).filter(v => v != null) - const allSupplyPressures = lines.map(l => l.supply.pressure?.value).filter(v => v != null) - const allReturnPressures = lines.map(l => l.return.pressure?.value).filter(v => v != null) + const groupSupplyPressures = allGroups.map(g => g.supply?.reading?.value).filter(v => v != null) + const groupReturnPressures = allGroups.map(g => g.return?.reading?.value).filter(v => v != null) + const allGroupDeltaP = allGroups.map(g => g.delta_p?.value).filter(v => v != null) + // prefer per-group PTs; fall back to line-level pressure + const allSupplyPressures = groupSupplyPressures.length > 0 + ? groupSupplyPressures + : lines.map(l => l.supply.pressure?.value).filter(v => v != null) + const allReturnPressures = groupReturnPressures.length > 0 + ? groupReturnPressures + : lines.map(l => l.return.pressure?.value).filter(v => v != null) const avgSupplyTemp = allSupplyTemps.length > 0 ? Math.round((allSupplyTemps.reduce((a, b) => a + b, 0) / allSupplyTemps.length) * 10) / 10 @@ -213,9 +253,11 @@ function buildMinersCircuit1View (equipment, config) { const outletPressureAvg = allReturnPressures.length > 0 ? Math.round((allReturnPressures.reduce((a, b) => a + b, 0) / allReturnPressures.length) * 100) / 100 : null - const deltaPAvg = (inletPressureAvg != null && outletPressureAvg != null) - ? Math.round((inletPressureAvg - outletPressureAvg) * 100) / 100 - : null + const deltaPAvg = allGroupDeltaP.length > 0 + ? Math.round((allGroupDeltaP.reduce((a, b) => a + b, 0) / allGroupDeltaP.length) * 100) / 100 + : ((inletPressureAvg != null && outletPressureAvg != null) + ? Math.round((inletPressureAvg - outletPressureAvg) * 100) / 100 + : null) const controlValveEntries = coolingConfig.control_valves || {} const controlValves = {} @@ -261,7 +303,7 @@ function buildMinersCircuit2View (equipment, config) { const temperatures = equipment.temperatures const levels = equipment.levels const heatExchangers = equipment.heat_exchangers - const coolingTowers = equipment.cooling_towers + const coolingTowers = (equipment.cooling_towers || []).filter(ct => ct.circuit === 'COOLING_TOWER') const vibrationSwitches = equipment.vibration_switches const valves = equipment.valves const tanks = equipment.tanks @@ -321,19 +363,24 @@ function buildMinersCircuit2View (equipment, config) { } }) - // Summary: pre-HX and post-HX temps from heat exchangers + // pre/post-HX from the loop's configured sensors, else HX-derived + const preHxConfigReading = getSensorReading(temperatures, towerConfig.pre_hx_temp_sensor) + const postHxConfigReading = getSensorReading(temperatures, towerConfig.post_hx_temp_sensor) + const allTowerSideIn = heatExchangerData.map(hx => hx.tower_side_in_temp?.value).filter(v => v != null) const allTowerSideOut = heatExchangerData.map(hx => hx.tower_side_out_temp?.value).filter(v => v != null) - const preHxTemp = allTowerSideIn.length > 0 + const hxPreHxTemp = allTowerSideIn.length > 0 ? Math.round((allTowerSideIn.reduce((a, b) => a + b, 0) / allTowerSideIn.length) * 10) / 10 : null - const postHxTemp = allTowerSideOut.length > 0 + const hxPostHxTemp = allTowerSideOut.length > 0 ? Math.round((allTowerSideOut.reduce((a, b) => a + b, 0) / allTowerSideOut.length) * 10) / 10 : null + const preHxTemp = preHxConfigReading?.value ?? hxPreHxTemp + const postHxTemp = postHxConfigReading?.value ?? hxPostHxTemp const deltaT = (preHxTemp != null && postHxTemp != null) ? Math.round((postHxTemp - preHxTemp) * 10) / 10 : null - const tempUnit = heatExchangerData[0]?.tower_side_in_temp?.unit + const tempUnit = preHxConfigReading?.unit || postHxConfigReading?.unit || heatExchangerData[0]?.tower_side_in_temp?.unit // Tower level from config sensor const towerLevelSensor = towerConfig.tower_level_sensor @@ -706,7 +753,7 @@ function buildHvacCircuit2View (equipment, config) { const temperatures = equipment.temperatures const flows = equipment.flows const levels = equipment.levels - const coolingTowers = equipment.cooling_towers + const coolingTowers = (equipment.cooling_towers || []).filter(ct => ct.circuit === 'HVAC_CONDENSER') const condenserConfig = config?.cooling_system?.hvac_condenser || {} const viewConfig = config?.cooling_system?.view_metadata?.hvac?.circuit2 || {}