Files
monster/src/sampling/samplingProgram.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

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