feat(setDemand): surface specificClass.setDemand(value, unit='%') + slim npm pack

Why:
- pumpingStation level-based control was calling MGC.handleInput(percent)
  directly. handleInput expects canonical m³/s; a 1 % keep-alive arrived
  as 1 m³/s ≈ 3600 m³/h, the dispatcher clamped to dt.flow.max and the
  group ran at 100 %. The unit math already existed inside the set.demand
  command handler — but only that handler could reach it.

What:
- New public method `async setDemand(value, unit='%')` on MachineGroup
  (specificClass.js). Resolves the unit (`%` → interpolate against the
  dynamic-totals envelope, absolute units → convert(value)) and calls
  handleInput with canonical m³/s. Negative value remains the operator
  stop-all signal. Single source of truth for the percent → m³/s rule.
- Refactor handlers.setDemand to parse the payload + apply mode gating
  and then delegate to source.setDemand. Drops the local `convert` import
  (now reached via the source).
- Update commands.basic.test.js mock with a setDemand shim that mirrors
  the real method, so existing handleInput assertions still hold.

Packaging:
- Add .npmignore mirroring .gitignore plus dev-only trees (test/, wiki/,
  CLAUDE.md, …) so the published tarball stays small.
- Extend .gitignore with the standard dev-artifact deny list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-19 21:36:00 +02:00
parent a57e0095a3
commit aeb938c205
5 changed files with 94 additions and 33 deletions

View File

@@ -65,9 +65,25 @@ function makeSource({
if (handleInputResult instanceof Error) throw handleInputResult;
return handleInputResult;
},
// Used by set.demand handler when unit is %: needs dt.flow + interpolation.
// With min=0, max=100, the linear interpolation is identity so a bare
// numeric demand round-trips through handleInput unchanged.
// Mirror of the real specificClass.setDemand: resolves unit -> canonical
// m³/s and forwards to handleInput. With dt.flow {min:0,max:100} the %
// interpolation is identity, so a bare numeric demand round-trips through
// handleInput unchanged — keeping the existing assertions stable.
setDemand: async (value, unit = '%') => {
const v = Number(value);
if (!Number.isFinite(v)) return undefined;
if (v < 0) { await source.turnOffAllMachines(); return undefined; }
let canonical;
if (unit === '%') {
canonical = source.interpolation.interpolate_lin_single_point(
v, 0, 100, dt.flow.min, dt.flow.max);
} else {
const { convert } = require('generalFunctions');
canonical = convert(v).from(unit).to('m3/s');
}
return source.handleInput('parent', canonical);
},
// Retained for completeness — the mock setDemand uses these internally.
calcDynamicTotals: () => dt,
interpolation: {
interpolate_lin_single_point: (x, ix, iy, ox, oy) => {