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:
znetsixe
2026-05-12 17:12:33 +02:00
parent b373727338
commit 28344c6810
9 changed files with 148 additions and 18 deletions

View File

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