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.
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:
- Free chlorine — liquid chlorine 12.5%, cal-hypo 65% and 73%, dichlor, or trichlor. Stabilized chlorines carry a warning showing the cyanuric acid they add. Above 10 ppm the card switches to a sodium thiosulfate neutralization amount (or advises holding to let it burn down).
- pH — muriatic acid or dry acid to lower, soda ash to raise. The acid dose is scaled by the measured total alkalinity (the buffer resisting the change), and large corrections prompt a staged-dosing note.
- Total alkalinity — sodium bicarbonate to raise, muriatic acid to lower, with a note that lowering TA also drops pH.
- Calcium hardness — calcium chloride to raise; when hardness is far over target the card reports a drain-and-refill percentage instead, because no chemical lowers it.
- Cyanuric acid — stabilizer to raise; dilution-only when over, since CYA does not degrade.
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
- Doses are field estimates. The tool assumes even mixing and a single turnover between dose and retest; it does not model demand, sunlight burn-off, or bather load. Dose in stages (no more than a third of a large pH or acid correction at once), keep circulation running, and retest.
- The LSI constant assumes TDS under 1000 ppm. Salt pools and older water will read slightly off; the tool does not take a TDS input.
- Code thresholds are seeded to California Title 22 / county minimums [3]. They are a starting point, not a substitute for the facility's own permit — verify the numbers in
POOLSagainst it. - It is a calculator, not a controller. It reads nothing from the pool and actuates nothing; the operator still tests the water and adds the chemicals.
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.
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
- Pump Room Chemistry Calculator
- Langelier Saturation Index and the standard temperature/alkalinity factor methodology (Taylor water-balance convention).
- California Code of Regulations, Title 22, Division 4, Chapter 20 (public pool water quality; §65529 recirculation and turnover).