Two fixes for the reactor unit-confusion drift surfaced in the 2026-05-19 wiki uplift: 1. X_A_init default in reactor.html was 0.001 g COD/m³, which is effectively zero nitrifying biomass — the reactor cannot nitrify ammonia under that initial condition (per the project memory note, ~50 mg/L is the minimum). Aligned to the schema default of 200 in generalFunctions/src/configs/reactor.json. Same change in test/helpers/factories.js so the test factory mirrors the operational default; tests that need low-biomass scenarios already override. 2. New test/basic/timestep-units.basic.test.js locks in the `config.timeStep is interpreted as seconds` contract — verifies the engine's days-stored / seconds-input invariant and asserts the schema declares `unit: "s"`, `default: 1`. Companion to the schema fix in the generalFunctions submodule. Full test suite: 49/49 pass (was 46/46 + 3 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
2.9 KiB
JavaScript
150 lines
2.9 KiB
JavaScript
const EventEmitter = require('node:events');
|
|
|
|
function makeUiConfig(overrides = {}) {
|
|
return {
|
|
name: 'reactor-test',
|
|
reactor_type: 'CSTR',
|
|
volume: 100,
|
|
length: 10,
|
|
resolution_L: 5,
|
|
alpha: 0,
|
|
n_inlets: 1,
|
|
kla: NaN,
|
|
S_O_init: 0,
|
|
S_I_init: 30,
|
|
S_S_init: 100,
|
|
S_NH_init: 16,
|
|
S_N2_init: 0,
|
|
S_NO_init: 0,
|
|
S_HCO_init: 5,
|
|
X_I_init: 25,
|
|
X_S_init: 75,
|
|
X_H_init: 30,
|
|
X_STO_init: 0,
|
|
X_A_init: 200,
|
|
X_TS_init: 125,
|
|
timeStep: 1,
|
|
enableLog: false,
|
|
logLevel: 'error',
|
|
positionVsParent: 'atEquipment',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeReactorConfig(overrides = {}) {
|
|
const ui = makeUiConfig(overrides);
|
|
return {
|
|
general: {
|
|
id: 'reactor-node-1',
|
|
name: ui.name,
|
|
unit: null,
|
|
logging: {
|
|
enabled: ui.enableLog,
|
|
logLevel: ui.logLevel,
|
|
},
|
|
},
|
|
functionality: {
|
|
positionVsParent: ui.positionVsParent || 'atEquipment',
|
|
softwareType: 'reactor',
|
|
},
|
|
reactor_type: ui.reactor_type,
|
|
volume: Number(ui.volume),
|
|
length: Number(ui.length),
|
|
resolution_L: Number(ui.resolution_L),
|
|
alpha: Number(ui.alpha),
|
|
n_inlets: Number(ui.n_inlets),
|
|
kla: Number(ui.kla),
|
|
initialState: [
|
|
Number(ui.S_O_init),
|
|
Number(ui.S_I_init),
|
|
Number(ui.S_S_init),
|
|
Number(ui.S_NH_init),
|
|
Number(ui.S_N2_init),
|
|
Number(ui.S_NO_init),
|
|
Number(ui.S_HCO_init),
|
|
Number(ui.X_I_init),
|
|
Number(ui.X_S_init),
|
|
Number(ui.X_H_init),
|
|
Number(ui.X_STO_init),
|
|
Number(ui.X_A_init),
|
|
Number(ui.X_TS_init),
|
|
],
|
|
timeStep: Number(ui.timeStep),
|
|
};
|
|
}
|
|
|
|
function makeNodeStub() {
|
|
const handlers = {};
|
|
const sent = [];
|
|
const warns = [];
|
|
const errors = [];
|
|
const statuses = [];
|
|
|
|
return {
|
|
id: 'reactor-node-1',
|
|
source: null,
|
|
on(event, cb) {
|
|
handlers[event] = cb;
|
|
},
|
|
send(msg) {
|
|
sent.push(msg);
|
|
},
|
|
warn(msg) {
|
|
warns.push(msg);
|
|
},
|
|
error(msg) {
|
|
errors.push(msg);
|
|
},
|
|
status(msg) {
|
|
statuses.push(msg);
|
|
},
|
|
_handlers: handlers,
|
|
_sent: sent,
|
|
_warns: warns,
|
|
_errors: errors,
|
|
_statuses: statuses,
|
|
};
|
|
}
|
|
|
|
function makeREDStub(nodeMap = {}) {
|
|
return {
|
|
nodes: {
|
|
getNode(id) {
|
|
return nodeMap[id] || null;
|
|
},
|
|
createNode() {},
|
|
registerType() {},
|
|
},
|
|
httpAdmin: {
|
|
get() {},
|
|
},
|
|
};
|
|
}
|
|
|
|
function makeMeasurementChild({
|
|
id = 'measurement-1',
|
|
name = 'temp-sensor-1',
|
|
distance = 'atEquipment',
|
|
positionVsParent = 'atEquipment',
|
|
type = 'temperature',
|
|
} = {}) {
|
|
return {
|
|
config: {
|
|
general: { id, name },
|
|
functionality: { distance, positionVsParent, softwareType: 'measurement' },
|
|
asset: { type },
|
|
},
|
|
measurements: {
|
|
emitter: new EventEmitter(),
|
|
},
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
makeUiConfig,
|
|
makeReactorConfig,
|
|
makeNodeStub,
|
|
makeREDStub,
|
|
makeMeasurementChild,
|
|
};
|