# Duplex pump controller

Executable supervisory logic for two pumps, with deterministic simulation and recorded test results. This package has not been commissioned on hardware.

## Run

Use Python 3.10 or newer. From this directory:

```sh
python -m pip install -r requirements.txt
python run.py
python -m unittest -v
```

Open `report.html` after the run. `run.py` executes all tests before regenerating the trace, figures, verification record, and report. `controller.py` and the tests have no third-party dependencies; NumPy and Matplotlib are used for the plot.

## Controller interface

Call `Controller.step(time_seconds, Inputs(...))` with a complete Boolean input snapshot. Time must be finite and nondecreasing. Call `apply_events(time, [changes, ...])` to combine distinct input updates atomically. Conflicting updates to the same input at one timestamp are rejected. Reset is a one-scan pulse in this event interface.

Three level inputs must satisfy `assist => start => wet_low`. An invalid sequence latches a sensor fault. Automatic demand starts a cycle that stays active until `wet_low` is false. Minimum run applies to a proved-running pump whose demand disappears while the wet permissive remains; the low-level stop always wins. Pump availability represents separate external permissives and protections.

Initial boot and power restoration require dry levels, no manual requests, no run feedback, and an explicit reset. A reset clears a pump fault only when that pump is also available. Operator reset with active demand is deliberately rejected.

## Evidence

- `test_controller.py`: 18 controller regression tests, including 14,336 one-scan input/state combinations. `test_virtual_commissioning.py` adds five tests, bringing the executed total to 23.
- `results/scenario_trace.csv`: actual outputs from the reference event sequence.
- `results/expected_outputs.csv`: ten hand-specified checkpoints checked by the run.
- `verification.json`: executed test names and source hashes.
- `cause_and_effect.csv`, `io_list.csv`: review documents.
- `hardware_acceptance.md`: an unexecuted procedure for a future installation.

The input sweep is not an exhaustive proof over all possible time histories. Physical motor protection, electrical design, sensor reliability, and field acceptance remain outside this model. No field performance or certification is claimed.

## Completed virtual commissioning

`python run.py` also couples the controller to a synthetic 200 L reservoir with delayed motor proof. `virtual-validation.html` reports eleven plant cases, 50/100/250 ms scan timing, and 36 acceptance checks. The 1 L/s reference completes seven alternating cycles without spill. Start failure and sustained feedback loss transfer demand to the remaining pump. The 9 L/s over-capacity case spills 144.4 L; a five-second power interruption without the required quiet reset spills 40 L. Those are adverse operating conditions, not passed capacity claims.

`results/virtual-reservoir-traces.csv` contains every scan. `completion.json` records expected and observed outcomes with source hashes. Five added regression tests bring the package total to 23. This is the completed simulated substitute for hardware commissioning; physical electrical and hydraulic behavior is not inferred from it.

Selected service is 1 L/s with a 4 L/s duty pump and one standby. The attended restoration case isolates inflow 50 s after the modeled power loss, drains the tank, resets with quiet inputs and restores service without spill. `selected-operating-envelope.json` states this policy. The 6 L/s assist case is not accepted as single-pump-failure-tolerant service.
