Refactor of monster to use the platform infrastructure (BaseDomain, BaseNodeAdapter, ChildRouter, commandRegistry, statusBadge). Extracts concerns into focused modules per .claude/refactor/MODULE_SPLIT.md generic template. Tests stay green; CONTRACT.md generated; legacy aliases preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
164 lines
5.5 KiB
JavaScript
164 lines
5.5 KiB
JavaScript
'use strict';
|
|
|
|
// Monster — multi-parameter biological process monitoring (Unit-level).
|
|
// Orchestrator only: wires the parameters/flow/rain/schedule/sampling
|
|
// modules in configure() and calls them in tick(). The mutable sampling
|
|
// state (running, sumPuls, m3PerPuls, …) lives on `this` so the existing
|
|
// test surface (`monster.bucketVol`, `monster.q`, …) keeps working.
|
|
|
|
const { BaseDomain } = require('generalFunctions');
|
|
const params = require('./parameters/parameters');
|
|
const FlowTracker = require('./flow/flowTracker');
|
|
const RainAggregator = require('./rain/rainAggregator');
|
|
const schedule = require('./schedule/schedule');
|
|
const sampling = require('./sampling/samplingProgram');
|
|
const { buildOutput } = require('./io/output');
|
|
const { buildStatusBadge } = require('./io/statusBadge');
|
|
|
|
class Monster extends BaseDomain {
|
|
static name = 'monster';
|
|
|
|
configure() {
|
|
this.init = false;
|
|
|
|
this._initState();
|
|
this._initSamplingDefaults();
|
|
|
|
this.flowTracker = new FlowTracker({ measurements: this.measurements, logger: this.logger });
|
|
this.rainAggregator = new RainAggregator({ logger: this.logger });
|
|
|
|
if (Number.isFinite(this.config?.constraints?.maxRainRef)) {
|
|
this.rainMaxRef = this.config.constraints.maxRainRef;
|
|
}
|
|
|
|
this.init = true;
|
|
params.applyBoundsAndTargets(this);
|
|
|
|
this.router.onRegister('measurement', (child) => this._wireMeasurementChild(child));
|
|
}
|
|
|
|
_initState() {
|
|
this.aquonSampleName = '112100';
|
|
this.monsternametijden = {};
|
|
this.rain_data = {};
|
|
this.aggregatedOutput = {};
|
|
this.sumRain = 0;
|
|
this.avgRain = 0;
|
|
this.daysPerYear = 0;
|
|
this.lastRainUpdate = 0;
|
|
this.rainMaxRef = 10;
|
|
this.predFactor = 0.7;
|
|
this.start_time = Date.now();
|
|
this.stop_time = Date.now();
|
|
this.flowTime = 0;
|
|
this.timePassed = 0;
|
|
this.timeLeft = 0;
|
|
this.currHour = new Date().getHours();
|
|
}
|
|
|
|
_initSamplingDefaults() {
|
|
const c = this.config.constraints || {};
|
|
const a = this.config.asset || {};
|
|
|
|
this.pulse = false;
|
|
this.bucketVol = 0;
|
|
this.sumPuls = 0;
|
|
this.predFlow = 0;
|
|
this.bucketWeight = 0;
|
|
|
|
this.q = 0;
|
|
this.i_start = false;
|
|
this.sampling_time = c.samplingtime;
|
|
this.emptyWeightBucket = a.emptyWeightBucket;
|
|
this.nominalFlowMin = c.nominalFlowMin;
|
|
this.flowMax = c.flowMax;
|
|
this.minSampleIntervalSec = c.minSampleIntervalSec || 60;
|
|
|
|
this.temp_pulse = 0;
|
|
this.volume_pulse = 0.05;
|
|
this.minVolume = c.minVolume;
|
|
this.maxVolume = 0;
|
|
this.maxWeight = c.maxWeight;
|
|
this.cap_volume = 55;
|
|
this.targetVolume = 0;
|
|
this.minPuls = 0;
|
|
this.maxPuls = 0;
|
|
this.absMaxPuls = 0;
|
|
this.targetPuls = 0;
|
|
this.m3PerPuls = 0;
|
|
this.predM3PerSec = 0;
|
|
this.m3PerTick = 0;
|
|
this.m3Total = 0;
|
|
this.running = false;
|
|
this.invalidFlowBounds = false;
|
|
this.lastSampleTime = 0;
|
|
this.lastSampleWarnTime = 0;
|
|
this.missedSamples = 0;
|
|
}
|
|
|
|
_wireMeasurementChild(child) {
|
|
if (!child?.measurements?.emitter) return;
|
|
const childType = child?.config?.asset?.type;
|
|
if (childType && childType !== 'flow') return;
|
|
|
|
const handler = (eventData) => this.flowTracker.handleMeasuredFlow(eventData);
|
|
child.measurements.emitter.on('flow.measured.upstream', handler);
|
|
child.measurements.emitter.on('flow.measured.downstream', handler);
|
|
child.measurements.emitter.on('flow.measured.atequipment', handler);
|
|
}
|
|
|
|
handleInput(topic, payload) {
|
|
switch (topic) {
|
|
case 'i_start': this.i_start = Boolean(payload); break;
|
|
case 'monsternametijden': schedule.updateMonsternametijden(this, payload); break;
|
|
case 'rain_data': this.updateRainData(payload); break;
|
|
case 'input_q': this.flowTracker.updateManualFlow(payload); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
updateRainData(value) {
|
|
this.rain_data = value;
|
|
this.lastRainUpdate = Date.now();
|
|
if (this.init && !this.running) {
|
|
this.aggregatedOutput = this.rainAggregator.update(value);
|
|
this.sumRain = this.rainAggregator.sumRain;
|
|
this.avgRain = this.rainAggregator.avgRain;
|
|
}
|
|
}
|
|
|
|
updateBucketVol(val) {
|
|
this.bucketVol = val;
|
|
this.bucketWeight = val + this.emptyWeightBucket;
|
|
}
|
|
|
|
// Public surface kept for legacy tests (sampling-guards, factories.js).
|
|
getSampleCooldownMs() { return params.getSampleCooldownMs(this); }
|
|
validateFlowBounds() { return params.validateFlowBounds(this); }
|
|
getRainIndex() { return params.getRainIndex(this); }
|
|
getPredictedFlowRate() { return params.getPredictedFlowRate(this); }
|
|
getMeasuredFlow() { return this.flowTracker.getMeasuredFlow(); }
|
|
getManualFlow() { return this.flowTracker.getManualFlow(); }
|
|
getEffectiveFlow() { return this.flowTracker.getEffectiveFlow(); }
|
|
get_model_prediction() { return sampling.getModelPrediction(this); }
|
|
flowCalc() { sampling.flowCalc(this); }
|
|
sampling_program() { sampling.samplingProgram(this); }
|
|
set_boundries_and_targets() { params.applyBoundsAndTargets(this); }
|
|
regNextDate(rows) { schedule.regNextDate(this, rows); }
|
|
updateMonsternametijden(v) { schedule.updateMonsternametijden(this, v); }
|
|
updatePredRain(v) { return this.rainAggregator.update(v); }
|
|
|
|
tick() {
|
|
this.logger.debug('Monster tick running');
|
|
this.q = this.flowTracker.getEffectiveFlow();
|
|
this.flowCalc();
|
|
this.sampling_program();
|
|
this.notifyOutputChanged();
|
|
}
|
|
|
|
getOutput() { return buildOutput(this); }
|
|
getStatusBadge() { return buildStatusBadge(this); }
|
|
}
|
|
|
|
module.exports = Monster;
|