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>
This commit is contained in:
znetsixe
2026-05-19 10:16:47 +02:00
parent edef1cecbf
commit 6e6699c763
13 changed files with 548 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
# @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
```js
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
```bash
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.