'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;