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>
114 lines
3.2 KiB
JavaScript
114 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
// Sampling program — the time-driven core. Each tick:
|
|
// 1. on i_start or scheduled date → init a sampling run (m3PerPuls, stop_time)
|
|
// 2. while running: integrate m3PerTick into temp_pulse; emit a pulse when
|
|
// it crosses 1 unless the cooldown guard blocks it
|
|
// 3. after stop_time: clear running state.
|
|
// flowCalc derives m3PerTick from the latest q (m3/h) and the wall-clock
|
|
// delta since the last call — runs once per tick before sampling_program.
|
|
|
|
const params = require('../parameters/parameters');
|
|
const { regNextDate } = require('../schedule/schedule');
|
|
|
|
function getModelPrediction(m) {
|
|
const samplingHours = Number(m.sampling_time) || 0;
|
|
const predictedRate = params.getPredictedFlowRate(m);
|
|
const fallbackRate = m.flowTracker.getEffectiveFlow();
|
|
const flowM3PerHour = predictedRate > 0 ? predictedRate : fallbackRate;
|
|
m.predFlow = Math.max(0, flowM3PerHour * samplingHours);
|
|
return m.predFlow;
|
|
}
|
|
|
|
function flowCalc(m) {
|
|
const timePassed = m.flowTime > 0 ? (Date.now() - m.flowTime) / 1000 : 0;
|
|
m.m3PerTick = (m.q / 60 / 60) * timePassed;
|
|
m.flowTime = Date.now();
|
|
}
|
|
|
|
function _beginRun(m) {
|
|
m.running = true;
|
|
m.temp_pulse = 0;
|
|
m.pulse = false;
|
|
m.updateBucketVol(0);
|
|
m.sumPuls = 0;
|
|
m.m3Total = 0;
|
|
m.timePassed = 0;
|
|
m.timeLeft = 0;
|
|
m.predM3PerSec = 0;
|
|
|
|
getModelPrediction(m);
|
|
m.m3PerPuls = Math.round(m.predFlow / m.targetPuls);
|
|
m.predM3PerSec = m.predFlow / m.sampling_time / 60 / 60;
|
|
m.start_time = Date.now();
|
|
m.stop_time = Date.now() + (m.sampling_time * 60 * 60 * 1000);
|
|
|
|
regNextDate(m, m.monsternametijden);
|
|
m.i_start = false;
|
|
}
|
|
|
|
function _endRun(m) {
|
|
m.m3PerPuls = 0;
|
|
m.temp_pulse = 0;
|
|
m.pulse = false;
|
|
m.updateBucketVol(0);
|
|
m.sumPuls = 0;
|
|
m.timePassed = 0;
|
|
m.timeLeft = 0;
|
|
m.predFlow = 0;
|
|
m.predM3PerSec = 0;
|
|
m.m3Total = 0;
|
|
m.running = false;
|
|
}
|
|
|
|
function _maybeEmitPulse(m) {
|
|
if (!(m.temp_pulse >= 1 && m.sumPuls < m.absMaxPuls)) {
|
|
if (m.pulse) m.pulse = false;
|
|
return;
|
|
}
|
|
|
|
const now = Date.now();
|
|
const cooldownMs = m.minSampleIntervalSec * 1000;
|
|
const blocked = m.lastSampleTime && (now - m.lastSampleTime) < cooldownMs;
|
|
|
|
if (blocked) {
|
|
m.missedSamples++;
|
|
m.pulse = false;
|
|
m.temp_pulse = Math.min(m.temp_pulse, 1);
|
|
if (!m.lastSampleWarnTime || (now - m.lastSampleWarnTime) > cooldownMs) {
|
|
m.lastSampleWarnTime = now;
|
|
m.logger.warn(`Sampling too fast. Cooldown active for ${Math.ceil((cooldownMs - (now - m.lastSampleTime)) / 1000)}s.`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
m.temp_pulse -= 1;
|
|
m.pulse = true;
|
|
m.lastSampleTime = now;
|
|
m.sumPuls++;
|
|
m.updateBucketVol(Math.round(m.sumPuls * m.volume_pulse * 100) / 100);
|
|
}
|
|
|
|
function samplingProgram(m) {
|
|
if (((m.i_start) || (Date.now() >= m.nextDate)) && !m.running) {
|
|
if (!params.validateFlowBounds(m)) {
|
|
m.running = false;
|
|
m.i_start = false;
|
|
return;
|
|
}
|
|
_beginRun(m);
|
|
}
|
|
|
|
if (m.stop_time > Date.now()) {
|
|
m.timePassed = Math.round((Date.now() - m.start_time) / 1000);
|
|
m.timeLeft = Math.round((m.stop_time - Date.now()) / 1000);
|
|
m.temp_pulse += m.m3PerTick / m.m3PerPuls;
|
|
m.m3Total += m.m3PerTick;
|
|
_maybeEmitPulse(m);
|
|
} else if (m.running) {
|
|
_endRun(m);
|
|
}
|
|
}
|
|
|
|
module.exports = { samplingProgram, flowCalc, getModelPrediction };
|