B3.3 follow-up: drop _unitView mirror; use UnitPolicy property bags directly

Same as MGC — UnitPolicy property bags replace the manual _unitView/
unitPolicyView reassignment. specificClass.js 400→377. 196/196 tests
still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-11 17:13:20 +02:00
parent 9e8463b41d
commit 84126e9130
5 changed files with 15 additions and 38 deletions

View File

@@ -24,7 +24,7 @@ const FILL = {
const SHOW_METRICS = new Set(['operational', 'warmingup', 'accelerating', 'decelerating']);
function buildOutput(host) {
const o = host.measurements.getFlattenedOutput({ requestedUnits: host.unitPolicyView.output });
const o = host.measurements.getFlattenedOutput({ requestedUnits: host.unitPolicy.output });
o.state = host.state.getCurrentState();
o.runtime = host.state.getRunTimeHours();
o.ctrl = host.state.getCurrentPosition();
@@ -74,7 +74,7 @@ function buildStatusBadge(host) {
const fill = FILL[stateName] || 'grey';
const parts = [`${host.currentMode}: ${symbol}`];
if (SHOW_METRICS.has(stateName)) {
const fu = host.unitPolicyView.output.flow || 'm3/h';
const fu = host.unitPolicy.output.flow || 'm3/h';
const flow = Math.round(host.measurements.type('flow').variant('predicted').position('downstream').getCurrentValue(fu) ?? 0);
const power = Math.round(host.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue('kW') ?? 0);
const pos = Math.round((host.state?.getCurrentPosition?.() ?? 0) * 100) / 100;

View File

@@ -7,7 +7,7 @@
*/
function calcFlow(host, x) {
const u = host.unitPolicyView.canonical.flow;
const u = host.unitPolicy.canonical.flow;
if (host.hasCurve) {
if (!host._isOperationalState()) {
host.measurements.type('flow').variant('predicted').position('downstream').value(0, Date.now(), u);
@@ -27,7 +27,7 @@ function calcFlow(host, x) {
}
function calcPower(host, x) {
const u = host.unitPolicyView.canonical.power;
const u = host.unitPolicy.canonical.power;
if (host.hasCurve) {
if (!host._isOperationalState()) {
host.measurements.type('power').variant('predicted').position('atEquipment').value(0, Date.now(), u);
@@ -52,7 +52,7 @@ function inputFlowCalcPower(host, flow) {
}
host.logger.warn('No curve data available for power calculation. Returning 0.');
host.measurements.type('power').variant('predicted').position('atEquipment')
.value(0, Date.now(), host.unitPolicyView.canonical.power);
.value(0, Date.now(), host.unitPolicy.canonical.power);
return 0;
}

View File

@@ -43,7 +43,7 @@ function getMeasuredPressure(host) {
}
host.logger.error('No valid pressure measurements available to calculate prediction using last known pressure');
applyDiff(0);
const fu = host.unitPolicyView.canonical.flow;
const fu = host.unitPolicy.canonical.flow;
host.measurements.type('flow').variant('predicted').position('max').value(host.predictFlow.currentFxyYMax, Date.now(), fu);
host.measurements.type('flow').variant('predicted').position('min').value(host.predictFlow.currentFxyYMin, Date.now(), fu);
return 0;

View File

@@ -56,9 +56,6 @@ class Machine extends BaseDomain {
this.config = this.configUtils.updateConfig(this.config, {
general: { name: `${this.config.functionality?.softwareType}_${this.config.general.id}` },
});
this.unitPolicyView = this._freezeUnitView(this.unitPolicy);
this._unitPolicyInstance = this.unitPolicy;
this.unitPolicy = this.unitPolicyView;
this._setupCurves();
this.groupPredictFlow = null; this.groupPredictPower = null; this.groupPredictCtrl = null; this.groupNCog = 0;
@@ -68,33 +65,13 @@ class Machine extends BaseDomain {
this._setupChildren();
}
_freezeUnitView(p) {
const slot = (m, k) => (typeof p[m] === 'function' ? p[m](k) : p[m]?.[k]);
return Object.freeze({
canonical: Object.freeze({
pressure: slot('canonical', 'pressure'), atmPressure: slot('canonical', 'atmPressure') || 'Pa',
flow: slot('canonical', 'flow'), power: slot('canonical', 'power'),
temperature: slot('canonical', 'temperature'),
}),
output: Object.freeze({
pressure: slot('output', 'pressure'), flow: slot('output', 'flow'),
power: slot('output', 'power'), temperature: slot('output', 'temperature'),
atmPressure: slot('output', 'atmPressure') || 'Pa',
}),
curve: Object.freeze({
pressure: slot('curve', 'pressure'), flow: slot('curve', 'flow'),
power: slot('curve', 'power'), control: slot('curve', 'control'),
}),
});
}
_setupCurves() {
this.model = this.config.asset?.model;
const { rawCurve, error } = loadModelCurve(this.model);
this.rawCurve = rawCurve;
if (error) { this.logger.error(`${error} in machineConfig. Cannot make predictions.`); this._installNullPredictors(); return; }
try {
this.curve = normalizeMachineCurve(rawCurve, this.unitPolicyView, this.logger);
this.curve = normalizeMachineCurve(rawCurve, this.unitPolicy, this.logger);
this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } });
const built = buildPredictors(this.config.asset.machineCurve);
this.predictors = built;
@@ -150,7 +127,7 @@ class Machine extends BaseDomain {
this.virtualPressureChildIds = { upstream: 'dashboard-sim-upstream', downstream: 'dashboard-sim-downstream' };
this.realPressureChildIds = { upstream: new Set(), downstream: new Set() };
this.virtualPressureChildren = new VirtualPressureChildren({
logger: this.logger, unitPolicy: this.unitPolicyView, parentRef: this,
logger: this.logger, unitPolicy: this.unitPolicy, parentRef: this,
ids: this.virtualPressureChildIds,
}).build();
this.pressureInit = new PressureInitialization({
@@ -184,10 +161,10 @@ class Machine extends BaseDomain {
}
_init() {
const tu = this.unitPolicyView.output.temperature;
const tu = this.unitPolicy.output.temperature;
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15, Date.now(), tu);
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325, Date.now(), 'Pa');
const fu = this.unitPolicyView.canonical.flow;
const fu = this.unitPolicy.canonical.flow;
const fmin = this.predictFlow ? this.predictFlow.currentFxyYMin : 0;
const fmax = this.predictFlow ? this.predictFlow.currentFxyYMax : 0;
this.measurements.type('flow').variant('predicted').position('max').value(fmax, Date.now(), fu);
@@ -259,8 +236,8 @@ class Machine extends BaseDomain {
// ── state-machine driven recompute ─────────────────────────────────
_updateState() {
if (!this._isOperationalState()) {
const fu = this.unitPolicyView.canonical.flow;
const pu = this.unitPolicyView.canonical.power;
const fu = this.unitPolicy.canonical.flow;
const pu = this.unitPolicy.canonical.power;
this.measurements.type('flow').variant('predicted').position('downstream').value(0, Date.now(), fu);
this.measurements.type('flow').variant('predicted').position('atEquipment').value(0, Date.now(), fu);
this.measurements.type('power').variant('predicted').position('atEquipment').value(0, Date.now(), pu);
@@ -366,9 +343,9 @@ class Machine extends BaseDomain {
updateCurve(newCurve) {
this.logger.info('Updating machine curve');
const normalized = normalizeMachineCurve(newCurve, this.unitPolicyView, this.logger);
const normalized = normalizeMachineCurve(newCurve, this.unitPolicy, this.logger);
this.config = this.configUtils.updateConfig(this.config, {
asset: { machineCurve: normalized, curveUnits: this.unitPolicyView.curve },
asset: { machineCurve: normalized, curveUnits: this.unitPolicy.curve },
});
if (!this.predictFlow || !this.predictPower || !this.predictCtrl) {
const built = buildPredictors(this.config.asset.machineCurve);

View File

@@ -106,7 +106,7 @@ test('source.getStatusBadge shows warning when pressure inputs are not initializ
state: { getCurrentState: () => 'operational', getCurrentPosition: () => 50 },
pressureInit: { getStatus: () => ({ initialized: false, hasUpstream: false, hasDownstream: false, hasDifferential: false }) },
measurements: { type() { return { variant() { return { position() { return { getCurrentValue() { return 0; } }; } }; } }; } },
unitPolicyView: { output: { flow: 'm3/h' } },
unitPolicy: { output: { flow: 'm3/h' } },
logger: { error: () => {} },
};
// Import the buildStatusBadge helper directly — it's the same code the