Files
monster/src/specificClass.js
znetsixe 2a6a0bc34b P6: convert monster to BaseDomain + BaseNodeAdapter + concern split
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>
2026-05-10 22:09:25 +02:00

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;