P2 wave 1: extract concerns from pumpingStation specificClass

Splits pumpingStation/src/ into focused concern modules. specificClass.js
will be slimmed to an orchestrator in P2.9 (integration); for now both
the inlined logic AND the new modules coexist so tests stay green
throughout.

  src/basin/         BasinGeometry + thresholdValidator (pure)
  src/measurement/   flowAggregator + measurementRouter + calibration
  src/control/       levelBased + flowBased(stub) + manual + index dispatcher
  src/safety/        safetyController split into dryRun + overfill rules
  src/commands/      registry array + handlers (canonical names from start)
  src/editor.js      260 lines of SVG basin-diagram redraw, was inline in .html
  examples/standalone-demo.js  was if(require.main===module) at bottom of specificClass.js
  CONTRACT.md        canonical inputs + outputs + emitted events

Modified:
  src/specificClass.js  removed the 170-line standalone demo block
  pumpingStation.html   oneditprepare/oneditsave delegate to editor.{init,save}
  pumpingStation.js     added admin endpoint serving src/editor.js

102 basic tests pass (60 new + 42 existing).
specificClass.js itself is unchanged in behaviour — integration is P2.9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-10 20:18:49 +02:00
parent da50403c76
commit 7afcd6e54a
27 changed files with 2533 additions and 463 deletions

36
src/control/manual.js Normal file
View File

@@ -0,0 +1,36 @@
async function run() {
// No-op: manual mode is event-driven via set.demand → forwardDemand,
// not tick-driven.
}
async function forwardDemand(ctx, demand) {
const { machineGroups, machines, logger } = ctx;
logger?.info?.(`Manual demand forwarded: ${demand}`);
if (machineGroups && Object.keys(machineGroups).length > 0) {
await Promise.all(
Object.values(machineGroups).map((group) =>
group.handleInput('parent', demand).catch((err) => {
logger?.error?.(`Failed to forward demand to group: ${err.message}`);
})
)
);
}
if (machines && Object.keys(machines).length > 0) {
const perMachine = demand / Object.keys(machines).length;
for (const machine of Object.values(machines)) {
try {
await machine.handleInput('parent', 'execMovement', perMachine);
} catch (err) {
logger?.error?.(`Failed to forward demand to machine: ${err.message}`);
}
}
}
}
module.exports = {
name: 'manual',
run,
forwardDemand,
};