hydraulic efficiency η = (Q·ΔP)/P + asset registry rename
The pre-existing efficiency formula `η = flow/power` produced tiny SI-unit
values (m³/J ≈ 1e-5), was monotonic in ctrl for centrifugal-pump curves
(no interior peak), and made NCog collapse to 0 — which cascaded into MGC
reporting BEP-position 0.0% always. Replaced with hydraulic efficiency
η = (Q·ΔP)/P_shaft, the dimensionless 0..1 ratio that has a real BEP and
matches the form MGC's group-level math uses.
- prediction/efficiencyMath.js:
* calcEfficiencyCurve takes pressureDiffPa; η = 0 when dP missing
* calcCog guards (yMax > yMin) before computing NCog (was unguarded /0)
* calcEfficiency falls back to predictFlow.currentF when measured ΔP is
missing, so predicted-variant calls still produce a meaningful η before
the differential measurement settles
- specificClass.js:
* Asset-registry lookup renamed: 'machine' → 'rotatingmachine' (matches
the datasets/assetData/ rename in generalFunctions). The error path
quotes the new filename so operators can find it.
* Two-call-site fix: with default-param stateConfig={}, the single-arg
constructor path (BaseNodeAdapter calls `new Machine(this.config)`
after pre-setting Machine._pendingExtras) was silently clobbering the
pre-set extras. Only overwrite when the caller explicitly passes them.
* Push port 0 deltas (notifyOutputChanged) after prediction updates so
dashboards see state + predicted-flow changes as they happen.
- pressure/pressureRouter.js: routing + fallback hardening (the trigger
for the bep-distance-cascade reproduction).
- display/workingCurves.js: Q-H curve generator extended.
- New tests:
* test/integration/qh-curve.integration.test.js — Q-H curve shape
* test/integration/bep-distance-cascade.integration.test.js — reproduces
the dashboard report (absDistFromPeak=0, NCog=0, efficiency=0 after a
setpoint move) at the unit level so future regressions fail loudly.
Full suite: 214/214 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,8 +43,24 @@ class Machine extends BaseDomain {
|
||||
// ES6 forbids `this` before super(). Single-threaded JS means stashing
|
||||
// on the class itself between the caller's args and super() is race-free;
|
||||
// configure() picks the extras up immediately after.
|
||||
constructor(machineConfig = {}, stateConfig = {}, errorMetricsConfig = {}) {
|
||||
Machine._pendingExtras = { stateConfig, errorMetricsConfig };
|
||||
//
|
||||
// Two call sites exist:
|
||||
// - nodeClass.buildDomainConfig() pre-sets Machine._pendingExtras and
|
||||
// then BaseNodeAdapter calls `new Machine(this.config)` (single-arg).
|
||||
// - Tests / direct callers pass (machineConfig, stateConfig, errMetrics)
|
||||
// explicitly.
|
||||
// With default-param `stateConfig={}`, the single-arg path was silently
|
||||
// clobbering the pre-set extras with an empty object, so the state machine
|
||||
// booted with schema defaults (warmingup=5s, speed=1%/s, mode=dynspeed)
|
||||
// regardless of what the editor saved. Only overwrite when an explicit
|
||||
// value is provided.
|
||||
constructor(machineConfig = {}, stateConfig, errorMetricsConfig) {
|
||||
if (stateConfig !== undefined || errorMetricsConfig !== undefined) {
|
||||
Machine._pendingExtras = {
|
||||
stateConfig: stateConfig ?? {},
|
||||
errorMetricsConfig: errorMetricsConfig ?? {},
|
||||
};
|
||||
}
|
||||
super(machineConfig);
|
||||
}
|
||||
|
||||
@@ -72,7 +88,7 @@ class Machine extends BaseDomain {
|
||||
// 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)
|
||||
? assetResolver.resolveAssetMetadata('rotatingmachine', this.model)
|
||||
: null;
|
||||
|
||||
if (!this.model) {
|
||||
@@ -81,7 +97,7 @@ class Machine extends BaseDomain {
|
||||
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.logger.error(`rotatingMachine: model '${this.model}' not found in asset registry (datasets/assetData/rotatingmachine.json). Cannot derive supplier/type/units.`);
|
||||
this._installNullPredictors();
|
||||
return;
|
||||
}
|
||||
@@ -291,6 +307,10 @@ class Machine extends BaseDomain {
|
||||
this.measurements.type('power').variant('predicted').position('atEquipment').value(0, Date.now(), pu);
|
||||
}
|
||||
this._updatePredictionHealth();
|
||||
// Push port 0 deltas so downstream dashboards / probes see state +
|
||||
// predicted-flow updates as they happen. BaseNodeAdapter listens for
|
||||
// 'output-changed' on this.emitter to fire _emitOutputs().
|
||||
this.notifyOutputChanged();
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
@@ -302,6 +322,7 @@ class Machine extends BaseDomain {
|
||||
this.calcDistanceBEP(efficiency, cog, minEfficiency);
|
||||
}
|
||||
this._updatePredictionHealth();
|
||||
this.notifyOutputChanged();
|
||||
}
|
||||
|
||||
// ── mode + input dispatch ──────────────────────────────────────────
|
||||
@@ -371,7 +392,8 @@ class Machine extends BaseDomain {
|
||||
const powerCurve = this.groupPredictPower.currentFxyCurve[this.groupPredictPower.currentF];
|
||||
const flowCurve = this.groupPredictFlow.currentFxyCurve[this.groupPredictFlow.currentF];
|
||||
if (!powerCurve?.y?.length || !flowCurve?.y?.length) return 0;
|
||||
const { peakIndex } = this.calcEfficiencyCurve(powerCurve, flowCurve);
|
||||
const dP = this.groupPredictFlow.currentF;
|
||||
const { peakIndex } = this.calcEfficiencyCurve(powerCurve, flowCurve, dP);
|
||||
const yMin = this.groupPredictFlow.currentFxyYMin;
|
||||
const yMax = this.groupPredictFlow.currentFxyYMax;
|
||||
if (yMax <= yMin) return 0;
|
||||
@@ -381,7 +403,7 @@ class Machine extends BaseDomain {
|
||||
|
||||
// ── efficiency math (delegates) ────────────────────────────────────
|
||||
calcCog() { return eff.calcCog(this); }
|
||||
calcEfficiencyCurve(p, f) { return eff.calcEfficiencyCurve(p, f); }
|
||||
calcEfficiencyCurve(p, f, dP) { return eff.calcEfficiencyCurve(p, f, dP); }
|
||||
calcEfficiency(power, flow, variant) { return eff.calcEfficiency(this, power, flow, variant); }
|
||||
calcDistanceBEP(e, max, min) { return eff.calcDistanceBEP(this, e, max, min); }
|
||||
calcDistanceFromPeak(e, peak) { return eff.calcDistanceFromPeak(e, peak); }
|
||||
|
||||
Reference in New Issue
Block a user