feat(rotatingMachine): resolve supplier+type from asset registry, drop denormalized fields
specificClass._setupCurves now calls assetResolver.resolveAssetMetadata to derive supplier/type/units from the model id, instead of trusting denormalized fields on the node config. If the model isn't in the registry, installs a null-predictor stub and logs a clear "pick a model from the asset menu" error rather than crashing. rotatingMachine.html: defaults block trimmed (supplier/category/assetType were stale copies of registry data). Tests: - New test/basic/assetMetadata.basic.test.js covers the registry-resolve path and the missing-model fallback. - nodeClass-config / error-paths / nodeClass-routing / factories / abort-deadlock fixtures updated to the trimmed asset shape. - 209/209 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ class nodeClass extends BaseNodeAdapter {
|
||||
static statusInterval = 1000;
|
||||
|
||||
buildDomainConfig(uiConfig) {
|
||||
_rejectLegacyAssetFields(uiConfig);
|
||||
|
||||
const flowUnit = _resolveUnit(uiConfig.unit, 'volumeFlowRate', 'm3/h');
|
||||
// Stash extras on the Machine class so its constructor (called by
|
||||
// BaseNodeAdapter via DomainClass) picks them up alongside the
|
||||
@@ -33,6 +35,7 @@ class nodeClass extends BaseNodeAdapter {
|
||||
uuid: uiConfig.assetUuid || uiConfig.uuid || null,
|
||||
tagCode: uiConfig.assetTagCode || uiConfig.assetTagNumber || null,
|
||||
tagNumber: uiConfig.assetTagNumber || null,
|
||||
model: uiConfig.model || null,
|
||||
unit: flowUnit,
|
||||
curveUnits: {
|
||||
pressure: _resolveUnit(uiConfig.curvePressureUnit, 'pressure', 'mbar'),
|
||||
@@ -47,6 +50,23 @@ class nodeClass extends BaseNodeAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Strict cutover: with the AssetResolver in place, supplier/category/assetType
|
||||
// are no longer node config — they're derived from the registry by model id.
|
||||
// Old flows that still have them saved must be re-saved through the editor.
|
||||
function _rejectLegacyAssetFields(uiConfig) {
|
||||
const offenders = ['supplier', 'category', 'assetType'].filter((k) => {
|
||||
const v = uiConfig[k];
|
||||
return typeof v === 'string' && v.trim() !== '';
|
||||
});
|
||||
if (offenders.length > 0) {
|
||||
throw new Error(
|
||||
`rotatingMachine: legacy asset field(s) [${offenders.join(', ')}] are saved on this node. ` +
|
||||
`After the AssetResolver refactor these are derived from the model id. ` +
|
||||
`Open the node in the editor, re-select the model, and save to migrate.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _resolveUnit(candidate, expectedMeasure, fallback) {
|
||||
const raw = typeof candidate === 'string' ? candidate.trim() : '';
|
||||
const fb = String(fallback || '').trim();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// stitches them together and preserves the public API the existing test
|
||||
// suite + sibling nodes (MGC, pumpingStation) depend on.
|
||||
|
||||
const { BaseDomain, UnitPolicy, state, nrmse, interpolation, convert } = require('generalFunctions');
|
||||
const { BaseDomain, UnitPolicy, state, nrmse, interpolation, convert, assetResolver } = require('generalFunctions');
|
||||
|
||||
const { loadModelCurve } = require('./curves/curveLoader');
|
||||
const { normalizeMachineCurve } = require('./curves/curveNormalizer');
|
||||
@@ -67,6 +67,54 @@ class Machine extends BaseDomain {
|
||||
|
||||
_setupCurves() {
|
||||
this.model = this.config.asset?.model;
|
||||
// Resolve derived metadata (supplier / type / allowed units) from the asset
|
||||
// registry. Source of truth lives in generalFunctions/datasets/assetData/.
|
||||
// If the registry has no entry for this model, assetMetadata is null and
|
||||
// we'll error out with a clear message below.
|
||||
this.assetMetadata = this.model
|
||||
? assetResolver.resolveAssetMetadata('machine', this.model)
|
||||
: null;
|
||||
|
||||
if (!this.model) {
|
||||
this.logger.error(`rotatingMachine: asset.model is required. Open the node, pick a model from the asset menu, and save.`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
if (!this.assetMetadata) {
|
||||
this.logger.error(`rotatingMachine: model '${this.model}' not found in asset registry (datasets/assetData/machine.json). Cannot derive supplier/type/units.`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
// Validate the chosen deployment unit. Hard check: it must be a recognised
|
||||
// flow unit (convert() can describe it). Soft check: warn if it isn't in
|
||||
// the registry's allowed-set for this model — the list is the editor's
|
||||
// recommended dropdown, not an exhaustive whitelist.
|
||||
const chosenUnit = this.config.asset?.unit;
|
||||
if (!chosenUnit) {
|
||||
this.logger.error(`rotatingMachine: asset.unit is required for model '${this.model}'. Re-save the node from the editor.`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const desc = convert().describe(chosenUnit);
|
||||
if (desc.measure !== 'volumeFlowRate') {
|
||||
this.logger.error(`rotatingMachine: asset.unit '${chosenUnit}' is not a flow unit (got measure '${desc.measure}').`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
} catch (_) {
|
||||
this.logger.error(`rotatingMachine: asset.unit '${chosenUnit}' is not a recognised unit.`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
const allowedUnits = this.assetMetadata.units || [];
|
||||
if (allowedUnits.length > 0 && !allowedUnits.includes(chosenUnit)) {
|
||||
this.logger.warn(
|
||||
`rotatingMachine: asset.unit '${chosenUnit}' is not in the registry's recommended list ` +
|
||||
`for model '${this.model}' (allowed: [${allowedUnits.join(', ')}]). Continuing — the unit is a valid flow unit.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { rawCurve, error } = loadModelCurve(this.model);
|
||||
this.rawCurve = rawCurve;
|
||||
if (error) { this.logger.error(`${error} in machineConfig. Cannot make predictions.`); this._installNullPredictors(); return; }
|
||||
|
||||
Reference in New Issue
Block a user