135 lines
4.8 KiB
JavaScript
135 lines
4.8 KiB
JavaScript
|
|
/**
|
||
|
|
* Centralised measurement update routing for rotatingMachine.
|
||
|
|
*
|
||
|
|
* Wraps the four measurement types coming from child measurement nodes
|
||
|
|
* (flow / power / temperature / pressure) and dispatches each to the
|
||
|
|
* appropriate handler. Pressure is delegated to the host's pressureRouter
|
||
|
|
* (built in P5.4); the other three are normalised + written + drift-tracked
|
||
|
|
* here.
|
||
|
|
*
|
||
|
|
* The handlers reach back into the host for `_resolveMeasurementUnit`,
|
||
|
|
* `_updateMetricDrift`, `_updatePredictionHealth`, `updatePosition` and the
|
||
|
|
* measurements container. Behaviour is preserved 1:1 from the original
|
||
|
|
* specificClass methods.
|
||
|
|
*/
|
||
|
|
|
||
|
|
class MeasurementHandlers {
|
||
|
|
constructor(ctx) {
|
||
|
|
if (!ctx || !ctx.host) {
|
||
|
|
throw new Error('MeasurementHandlers: ctx.host is required');
|
||
|
|
}
|
||
|
|
this.host = ctx.host;
|
||
|
|
this.logger = ctx.logger || ctx.host.logger;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Single entry point used by child-measurement event listeners.
|
||
|
|
* Unknown types warn and fall back to a no-op position refresh so a
|
||
|
|
* mis-configured child can't silently break the FSM tick.
|
||
|
|
*/
|
||
|
|
dispatch(measurementType, value, position, context = {}) {
|
||
|
|
switch (measurementType) {
|
||
|
|
case 'pressure':
|
||
|
|
return this.host.updateMeasuredPressure(value, position, context);
|
||
|
|
case 'flow':
|
||
|
|
return this.updateMeasuredFlow(value, position, context);
|
||
|
|
case 'power':
|
||
|
|
return this.updateMeasuredPower(value, position, context);
|
||
|
|
case 'temperature':
|
||
|
|
return this.updateMeasuredTemperature(value, position, context);
|
||
|
|
default:
|
||
|
|
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
||
|
|
return this.host.updatePosition();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
updateMeasuredTemperature(value, position, context = {}) {
|
||
|
|
const host = this.host;
|
||
|
|
this.logger.debug(
|
||
|
|
`Temperature update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`,
|
||
|
|
);
|
||
|
|
let unit;
|
||
|
|
try {
|
||
|
|
unit = host._resolveMeasurementUnit('temperature', context.unit);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.warn(`Rejected temperature update: ${error.message}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
host.measurements
|
||
|
|
.type('temperature')
|
||
|
|
.variant('measured')
|
||
|
|
.position(position || 'atEquipment')
|
||
|
|
.child(context.childId)
|
||
|
|
.value(value, context.timestamp, unit);
|
||
|
|
}
|
||
|
|
|
||
|
|
updateMeasuredFlow(value, position, context = {}) {
|
||
|
|
const host = this.host;
|
||
|
|
if (!host._isOperationalState()) {
|
||
|
|
this.logger.warn(`Machine not operational, skipping flow update from ${context.childName || 'unknown'}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
|
||
|
|
let unit;
|
||
|
|
try {
|
||
|
|
unit = host._resolveMeasurementUnit('flow', context.unit);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.warn(`Rejected flow update: ${error.message}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
host.measurements
|
||
|
|
.type('flow').variant('measured').position(position).child(context.childId)
|
||
|
|
.value(value, context.timestamp, unit);
|
||
|
|
|
||
|
|
if (host.predictFlow) {
|
||
|
|
const canonical = host.unitPolicy.canonical.flow;
|
||
|
|
const predicted = host.predictFlow.outputY || 0;
|
||
|
|
host.measurements.type('flow').variant('predicted').position('downstream')
|
||
|
|
.value(predicted, Date.now(), canonical);
|
||
|
|
host.measurements.type('flow').variant('predicted').position('atEquipment')
|
||
|
|
.value(predicted, Date.now(), canonical);
|
||
|
|
}
|
||
|
|
|
||
|
|
const measuredCanonical = host.measurements
|
||
|
|
.type('flow').variant('measured').position(position)
|
||
|
|
.getCurrentValue(host.unitPolicy.canonical.flow);
|
||
|
|
|
||
|
|
host._updateMetricDrift('flow', measuredCanonical, context);
|
||
|
|
host._updatePredictionHealth();
|
||
|
|
}
|
||
|
|
|
||
|
|
updateMeasuredPower(value, position, context = {}) {
|
||
|
|
const host = this.host;
|
||
|
|
if (!host._isOperationalState()) {
|
||
|
|
this.logger.warn(`Machine not operational, skipping power update from ${context.childName || 'unknown'}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.logger.debug(`Power update: ${value} at ${position} from ${context.childName || 'child'}`);
|
||
|
|
let unit;
|
||
|
|
try {
|
||
|
|
unit = host._resolveMeasurementUnit('power', context.unit);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.warn(`Rejected power update: ${error.message}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
host.measurements
|
||
|
|
.type('power').variant('measured').position(position).child(context.childId)
|
||
|
|
.value(value, context.timestamp, unit);
|
||
|
|
|
||
|
|
if (host.predictPower) {
|
||
|
|
host.measurements.type('power').variant('predicted').position('atEquipment')
|
||
|
|
.value(host.predictPower.outputY || 0, Date.now(), host.unitPolicy.canonical.power);
|
||
|
|
}
|
||
|
|
|
||
|
|
const measuredCanonical = host.measurements
|
||
|
|
.type('power').variant('measured').position(position)
|
||
|
|
.getCurrentValue(host.unitPolicy.canonical.power);
|
||
|
|
|
||
|
|
host._updateMetricDrift('power', measuredCanonical, context);
|
||
|
|
host._updatePredictionHealth();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = MeasurementHandlers;
|