Files
EVOLV/tools/physics-sanity/README.md
znetsixe 6e6699c763 tools: add physics-sanity + Docker MCP scaffolding + tools/README
- tools/physics-sanity/ — JS library of cross-node balance helpers
  (mass / hydraulic / hydraulic-power / oxygen-transfer / energy) with
  7 unit tests + a CLI demo. Designed for `require()` from per-node
  integration tests where shape-based unit tests miss physically-
  impossible plant states.
- tools/docker-compose.yml + tools/mcp/{node-red-admin,influxdb,browser}
  scaffolding — placeholder Dockerfiles + a ROADMAP.md for the Node-RED
  admin MCP. Compose file is the target shape for the Q3-2026 migration
  to the central MCP server; the per-service Dockerfile stays in this
  repo as the canonical definition either way. Implementations are TODO.
- tools/README.md — top-level tooling index; documents the CI order for
  running every tool on a PR.
- .gitignore: ignore tools/.env (developer-specific MCP endpoints).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:16:47 +02:00

2.9 KiB
Raw Blame History

@evolv/physics-sanity

Cross-node physical-balance helpers. Import from any node's test files to assert that scenario states close mass, hydraulic, hydraulic-power, oxygen-transfer, or energy balances within a stated tolerance.

Why

Per-node unit tests verify shape and behaviour. They don't catch physically impossible plant states that arise from cross-node coupling — e.g. a pumpingStation reporting outflow > inflow + accumulation, or a diffuser reporting OTR inconsistent with its KLa × ΔC × V.

These helpers don't replace per-node tests. They sit on top of an integration scenario and assert the closing balance.

Usage

const sanity = require('../../../tools/physics-sanity');

test('three-pump station closes the hydraulic balance', () => {
  // … drive the scenario, take a snapshot …
  const r = sanity.assertHydraulicBalance({
    headerSuctionPa: ps.suctionPressurePa,
    headerDischargePa: ps.dischargePressurePa,
    pumpHeadPa: sumOfPumpHeads,
    frictionPa: pipeFrictionEstimate,
  });
  assert.equal(r.ok, true, sanity.reportToString(r));
});

Helpers exported

Function Asserts
assertMassBalance({ inflowKgPerS, outflowKgPerS, accumulationKgPerS }) in - out - accumulation ≈ 0
assertHydraulicBalance({ headerSuctionPa, headerDischargePa, pumpHeadPa, frictionPa, staticHeadPa }) ΔP_headers ≈ pumpHead - friction - static
assertHydraulicPower({ flowM3PerS, headPa, shaftPowerW, efficiency }) shaft ≈ Q·H / η
assertOxygenTransfer({ klaPerS, csMgPerL, cMgPerL, otrKgPerS, volumeM3 }) OTR ≈ KLa · (Cs - C) · V
assertEnergyBalance({ heatInW, workInW, heatOutW, workOutW, accumulationW }) Q_in + W_in ≈ Q_out + W_out + ΔE

Each returns { ok, label, ...residuals }. reportToString(r) formats for human-readable failure messages.

CLI demo

node tools/physics-sanity/bin/physics-sanity.js

Runs four sanity-check scenarios against the helpers (smoke-test for the library itself).

Tolerance defaults

Domain Absolute Relative
mass 1e-6 kg/s 0.1 %
hydraulic ΔP 50 Pa (0.5 mbar) 0.1 %
hydraulic power 1 W 0.5 %
OTR 1e-4 kg/s 0.5 %
energy 1 W 0.1 %

Override per call with absTol / relTol.

Where to use this

Out-of-the-box destinations:

Scenario Where to add Calls
pumpingStation hydraulic closure nodes/pumpingStation/test/integration/ assertHydraulicBalance, assertHydraulicPower
reactor → settler mass balance nodes/reactor/test/integration/ assertMassBalance
diffuser OTR vs reactor uptake nodes/diffuser/test/integration/ assertOxygenTransfer
machineGroupControl efficiency sanity nodes/machineGroupControl/test/integration/ assertHydraulicPower

A future tool can scan integration tests and report which scenarios do or don't have a closing-balance assertion.