Calculating Pool Chemical Doses and Water Balance Offline

Pump Room is a single-file, offline calculator for aquatics operators. It takes a set of test-kit readings for one of four bodies of water, computes the chemical doses needed to hit target, reports the Langelier Saturation Index (LSI) for scale/corrosion balance, and flags any reading that breaches code. It has no server, no build step, and no external requests — the whole tool is one HTML file with an inline stylesheet and script [1].

The problem

Dosing math on the pool deck is done under time pressure, often from memory or a laminated chart, and always per-pool: the same 1 ppm free-chlorine correction is a splash of liquid chlorine in the spa and several gallons in the competition pool. A wrong order of magnitude either closes a body of water needlessly or leaves it under-treated. The pump room also has poor signal, so anything that depends on a live connection is unreliable exactly where it is needed. Pump Room fixes the volume, the code minimums, and the target set for each body in one place, so the operator enters readings and reads off quantities in the units they actually measure with.

How it works

The operator selects a body of water, enters test-kit readings (free chlorine, pH, total alkalinity, calcium hardness, cyanuric acid, temperature), and the tool renders three things live: a hazard banner if anything is out of code, the LSI gauge, and one dose card per chemistry parameter. Each dose card shows the current reading, an editable target, a chemical picker where more than one product applies, and the computed quantity formatted into field units (fluid ounces, quarts, gallons; ounces and pounds). Readings persist to localStorage under the key pumproom.v1, so a closed tab or a dead tablet loses nothing.

Pump Room main screen showing pool selector, readings grid, LSI gauge, and dose cards
The main screen: body-of-water selector, the test-kit readings grid, the LSI gauge, and one dose card per parameter. Placeholder pending a real screenshot.

The Copy log line button formats the current readings and the computed LSI into a single text line, timestamped and prefixed with the body of water, ready to paste into DigiQuatics once back in range:

Competition Pool — 07/08 2:14 PM — FC 2.4 / pH 7.6 / TA 90 / CH 300 / CYA 30 / 80°F / LSI -0.03

The math

Saturation index

Water balance uses the Langelier Saturation Index with a cyanuric-acid correction to the carbonate alkalinity [2]:

LSI = pH + TF(temp) + log10(CH) - 0.4 + log10(max(1, TA - CYA/3)) - 12.1

The constant 12.1 assumes total dissolved solids under 1000 ppm. TF(temp) is the standard temperature factor, looked up from a ten-point table spanning 32 °F to 105 °F and interpolated linearly between breakpoints. The gauge reads corrosive below −0.3, balanced within ±0.3, and scale-forming above +0.3.

The temperature-factor breakpoints
°F    TF
 32   0.0
 37   0.1
 46   0.2
 53   0.3
 60   0.4
 66   0.5
 76   0.6
 84   0.7
 94   0.8
105   0.9

Values between two breakpoints are interpolated linearly; below 32 °F the factor is held at 0.0 and above 105 °F at 0.9.

Dosing

Every dose is scaled from a per-10,000-gallon constant to the selected body's volume. The engine covers five parameters:

Hazard banner

The striped banner fires when free chlorine is below the selected body's code minimum, when free chlorine exceeds 10 ppm, or when pH falls outside 7.2–8.0. It names the specific breach and the corrective posture (for example, closing the body until chlorine is restored and retested).

Editing the config

All facility-specific numbers live at the top of the <script> block, so re-tuning the tool for a different site is a matter of editing data, not logic. The POOLS array holds each body's volume, code free-chlorine minimum, and default targets:

const POOLS = [
  { id:"comp", name:"Competition Pool", gal:315643, minFC:2.0,
    targets:{ fc:3.0, ph:7.5, ta:90, ch:300, cya:30 } },
  // ...instructional, splash pad, spa
];

The chemical products and their per-10,000-gallon strengths are in the CHLOR and ACID arrays; the remaining reagent constants (soda ash, bicarbonate, calcium chloride, stabilizer, thiosulfate) are in the K object; the temperature factors are in TF_TABLE. Change the numbers there and every dose re-scales automatically.

Limitations

Update: turnover & flow compliance (v1.1.0)

Version 1.1.0 adds a Flow & Turnover card below the LSI gauge. Enter the flowmeter reading (GPM) and the card reports the turnover time for the selected body, a pass/fail verdict against the code maximum, and — where a filter is configured — the filter loading rate against the media's cap.

The math

turnover_hours     = gallons / (GPM × 60)
min_compliant_GPM  = gallons / (max_hours × 60)
filter_loading     = GPM / filter_area          (gpm/ft², vs. media cap)

Each body carries its own Title 22 maximum turnover [3]: 6 hours for the pools, 1 hour for the splash pad, 0.5 hours for the spa. The card always shows the code maximum and the minimum compliant flow for the selected body, so the target is visible before the flowmeter is read; a compliant reading also reports its percentage margin, and a failing one states the GPM shortfall.

Flow and Turnover card showing a compliant turnover time with margin and the minimum compliant GPM
The v1.1.0 Flow & Turnover card: computed turnover against the body's code maximum. Placeholder pending a real screenshot.

Configuration

The POOLS array gains maxTurnoverH, filterArea (ft²), and filterMedia per body. A new FILTER_MEDIA table beside it holds the loading caps: high-rate sand 15, rapid sand 3, diatomaceous earth 2, cartridge 0.375 gpm/ft². filterArea ships as null, which hides the loading line entirely — set it once per body to enable the check.

References

  1. Pump Room Chemistry Calculator
  2. Langelier Saturation Index and the standard temperature/alkalinity factor methodology (Taylor water-balance convention).
  3. California Code of Regulations, Title 22, Division 4, Chapter 20 (public pool water quality; §65529 recirculation and turnover).