How Formulas Work
This guide explains the PoolCloud formula engine — how it takes your pool's water readings and calculates exactly which chemicals to add and how much.
What is a Formula?
A formula is a complete recipe for balancing a pool's water chemistry. Each formula is designed for a specific sanitization type (chlorine, salt water, bromine, or minerals) and contains:
- Readings — what to measure (free chlorine, pH, alkalinity, etc.)
- Target ranges — the ideal range for each reading
- Balance order — the sequence in which to adjust chemicals (order matters!)
- Treatments — the chemicals and actions used to correct each reading
- Adjusters — special logic that accounts for chemical interactions (e.g. pH and alkalinity affect each other)
Formulas in Detail
Chlorine (chlorine_cal_hypo)
The most common formula, designed for standard chlorine pools using calcium hypochlorite.
Readings: Free Chlorine, Total Chlorine, pH, Total Alkalinity, Cyanuric Acid (CYA), Calcium Hardness
Balance order and treatments:
| Step | Reading | To Raise (up) | To Lower (down) |
|---|---|---|---|
| 1 | pH | pH Increaser, Soda Ash | pH Decreaser, Muriatic Acid |
| 2 | Total Alkalinity | Alkalinity Increaser, Baking Soda | pH Decreaser, Muriatic Acid |
| 3 | Free Chlorine | Chlorine Tablets (default), Chlorine Granules, Liquid Chlorine | Drain & Refill |
| 4 | Cyanuric Acid | Stabilizer | Drain & Refill |
| 5 | Calcium Hardness | Hardness Increaser | Drain & Refill |
| 6 | Combined Chlorine | — | Shock |
Default target ranges:
| Reading | Target Range | Notes |
|---|---|---|
| Free Chlorine (FC) | 1 – 3 ppm | Warning buffer: +2 ppm above max |
| pH | 7.2 – 7.8 | |
| Total Alkalinity (TA) | 80 – 150 ppm | Warning buffer: +20 above, +10 below |
| Cyanuric Acid (CYA) | 30 – 50 ppm | Warning buffer: +/- 10 ppm |
| Calcium Hardness (CH) | 175 – 225 ppm | Warning buffer: +/- 25 ppm. Overridden to 200–275 for plaster/concrete/pebble pools |
| Combined Chlorine (CC) | 0 – 0.1 ppm | Inferred from TC - FC. If high, triggers shock |
Adjusters: Shock adjuster (infers combined chlorine from TC - FC), pH/TA adjuster (accounts for cross-effects between pH and alkalinity)
Salt Water (salt)
For pools with a salt chlorine generator (SWG). Similar to the chlorine formula but adds salt monitoring and adjusts CYA targets upward since salt systems need more stabilizer.
Readings: Salt, Free Chlorine, Total Chlorine, pH, Total Alkalinity, Cyanuric Acid (CYA), Calcium Hardness
Balance order and treatments:
| Step | Reading | To Raise (up) | To Lower (down) |
|---|---|---|---|
| 1 | pH | pH Increaser, Soda Ash | pH Decreaser, Muriatic Acid |
| 2 | Total Alkalinity | Alkalinity Increaser, Baking Soda | pH Decreaser, Muriatic Acid |
| 3 | Free Chlorine | Increase SWG Output (default), Chlorine Granules, Dichlor | Drain & Refill |
| 4 | Cyanuric Acid | Stabilizer | Drain & Refill |
| 5 | Calcium Hardness | Hardness Increaser | Drain & Refill |
| 6 | Combined Chlorine | — | Shock |
| 7 | Salt | Pool Salt | Drain & Refill |
Target range overrides (vs. chlorine):
| Reading | Default Target | Salt Override | Reason |
|---|---|---|---|
| Cyanuric Acid (CYA) | 30 – 50 ppm | 60 – 80 ppm | Salt systems require more CYA to protect chlorine produced by the SWG |
| Salt | — | 2700 – 3500 ppm | Required for the SWG cell to produce chlorine |
All other target ranges are the same as the chlorine formula.
Key differences from chlorine:
- Default chlorine treatment is "Increase SWG Output" (a task, not a chemical) — tells the user to turn up their salt cell dial
- Dichlor is available as a chlorine substitute instead of liquid chlorine
- Salt reading added with its own treatment
Bromine (bromine)
For pools and hot tubs that use bromine instead of chlorine as a sanitizer.
Readings: Bromine, pH, Total Alkalinity, Cyanuric Acid (CYA), Calcium Hardness
Balance order and treatments:
| Step | Reading | To Raise (up) | To Lower (down) |
|---|---|---|---|
| 1 | pH | pH Increaser, Soda Ash | pH Decreaser, Muriatic Acid |
| 2 | Total Alkalinity | Alkalinity Increaser, Baking Soda | pH Decreaser, Muriatic Acid |
| 3 | Bromine | Bromine Granules | Drain & Refill |
| 4 | Cyanuric Acid | Stabilizer | Drain & Refill |
| 5 | Calcium Hardness | Hardness Increaser | Drain & Refill |
| 6 | Combined Chlorine | — | Shock |
Default target ranges:
| Reading | Target Range |
|---|---|
| Bromine (BRO) | 3 – 5 ppm |
| pH | 7.2 – 7.8 |
| Total Alkalinity (TA) | 80 – 150 ppm |
| Cyanuric Acid (CYA) | 30 – 50 ppm |
| Calcium Hardness (CH) | 175 – 225 ppm |
Key differences:
- No Free Chlorine or Total Chlorine readings — uses Bromine reading instead
- Only one treatment option for raising bromine (no substitutions for bromine up)
- Adjusters: pH/TA only (no shock adjuster since there's no FC/TC)
Minerals (minerals)
For pools using a mineral sanitization system (like Nature2, Frog, etc.). These systems use minerals as the primary sanitizer and only require trace levels of chlorine as a backup.
Readings: Free Chlorine, Total Chlorine, pH, Total Alkalinity, Cyanuric Acid (CYA), Calcium Hardness
Balance order and treatments:
| Step | Reading | To Raise (up) | To Lower (down) |
|---|---|---|---|
| 1 | pH | pH Increaser, Soda Ash | pH Decreaser, Muriatic Acid |
| 2 | Total Alkalinity | Alkalinity Increaser, Baking Soda | pH Decreaser, Muriatic Acid |
| 3 | Free Chlorine | Chlorine Tablets (default), Chlorine Granules, Liquid Chlorine | Drain & Refill |
| 4 | Cyanuric Acid | Stabilizer | Drain & Refill |
| 5 | Calcium Hardness | Hardness Increaser | Drain & Refill |
| 6 | Combined Chlorine | — | Shock |
Target range overrides (vs. chlorine):
| Reading | Default Target | Minerals Override | Reason |
|---|---|---|---|
| Free Chlorine (FC) | 1 – 3 ppm | 0.5 – 4 ppm | Mineral systems need less chlorine since minerals do most of the sanitizing |
All other target ranges are the same as the chlorine formula.
Key differences from chlorine:
- Wider and lower FC target range (0.5–4 ppm vs 1–3 ppm)
- Same treatments and balance order otherwise
All Readings Reference
Every reading the engine understands, with its default configuration:
| ID | Name | Short Name | Units | Default Value | Test Range | Default Target | Warning Buffer |
|---|---|---|---|---|---|---|---|
fc | Free Chlorine | Free Chlorine | ppm | 4 | 0 – 10 | 1 – 3 | +2 above, none below |
tc | Total Chlorine | Total Chlorine | ppm | 4 | 0 – 10 | 0 – 0.1 | — |
cc | Combined Chlorine | Combined Chlorine | ppm | 0 | 0 – 7 | 0 – 0.1 | — |
ph | pH | pH | — | 7.4 | 5 – 9 | 7.2 – 7.8 | — |
ta | Total Alkalinity | Alkalinity | ppm | 100 | 0 – 250 | 80 – 150 | +20 above, +10 below |
cya | Stabilizer (CYA) | CYA | ppm | 40 | 0 – 300 | 30 – 50 | +/- 10 |
ch | Calcium Hardness | Hardness | ppm | 200 | 0 – 1000 | 175 – 225 | +/- 25 |
salt | Salt Level | Salt | ppm | 3200 | 0 – 5000 | 2700 – 3500 | — |
bro | Bromine | Bromine | ppm | 3 | 0 – 7 | 3 – 5 | — |
copper | Copper | Copper | ppm | 1 | 0 – 7 | 0.4 – 0.7 | — |
disox | Dissolved Oxygen | Dissolved Oxygen | ppm | 1 | 0 – 15 | 6 – 7 | — |
How Warning Buffers Work
Some readings have a warning buffer — a zone just outside the target range where the engine generates a warning instead of a treatment action. This prevents over-treating readings that are only slightly off.
For example, Free Chlorine has a target range of 1–3 ppm and a warning buffer of +2 above. If FC reads 4.5 ppm, it's above the 3 ppm max but within the 5 ppm warning ceiling (3 + 2), so the engine returns a warning rather than telling you to drain the pool.
Wall Type Effects on Targets
For plaster, concrete, and pebble pools, the Calcium Hardness target is automatically raised to 200–275 ppm (from the default 175–225 ppm). These surfaces are more susceptible to etching from low-calcium water.
How Combined Chlorine (CC) is Calculated
Combined chlorine is not measured directly. The engine infers it:
CC = Total Chlorine (TC) − Free Chlorine (FC)
If CC is above the target threshold (0.1 ppm), it means chloramines are present. The engine's shock adjuster creates a delta that triggers a shock treatment to break down the chloramines through breakpoint chlorination.
All Treatments Reference
Chemical Treatments
| ID | Name | Type | Concentration | Wait Time | Description |
|---|---|---|---|---|---|
calc_hypo | Chlorine Granules | Dry Chemical | 67% | 15 min | Pre-dissolve in a bucket of water and pour into pool. |
na_hclo | Liquid Chlorine | Liquid Chemical | 10% | 60 min | Pour slowly in front of a return jet to help it mix. |
chlorine_tablets | Chlorine Tablets | Task | 100% | 60 min | Add tablets to your chlorinator or floaters. Adjust dials/knobs as needed. |
dichlor | Dichlor | Dry Chemical | 99% | 15 min | Add directly to the pool. |
bromine | Bromine Granules | Dry Chemical | 100% | 20 min | Broadcast directly over the pool surface in small batches. |
soda_ash | Soda Ash | Dry Chemical | 100% | 20 min | Broadcast over pool surface in small batches. Also slightly raises alkalinity. |
ph_increaser | pH Increaser | Dry Chemical | 100% | 20 min | Broadcast over pool surface in small batches. Also slightly raises alkalinity. |
m_acid | Muriatic Acid | Liquid Chemical | 31% | 20 min | Dilute 10:1 with water in an acid-resistant bucket. Turn off pump, pour around perimeter, then run pump for 5 hours. |
sodium_bisulfate | pH Decreaser (Dry Acid) | Dry Chemical | 100% | 20 min | Broadcast over pool surface. Brush any that settles on the bottom. Also lowers pH. |
baking_soda | Baking Soda | Dry Chemical | 100% | 20 min | Broadcast over pool surface in small batches. Also slightly raises pH. |
alk_increaser | Alkalinity Increaser | Dry Chemical | 100% | 20 min | Broadcast over pool surface in small batches. Also slightly raises pH. |
cal_chlor | Hardness Increaser | Dry Chemical | 100% | 480 min (8 hrs) | Broadcast calcium chloride over pool surface in small batches to avoid cloudy water. |
cya | Stabilizer (Conditioner) | Dry Chemical | 100% | 20 min | Dissolve in a 5-gallon bucket of pool water, pour into skimmer, run pump for several hours. |
salt | Pool Salt | Dry Chemical | 100% | 0 min | Broadcast evenly over pool surface. Run pump for 24 hours to dissolve. |
shock | Chlorine Shock | Dry Chemical | 100% | 480 min (8 hrs) | Add following manufacturer's directions. Shock at night for best results. Run filter for 8 hours. Retest before swimming. |
Task Treatments
These aren't chemicals — they're actions for the pool owner to take.
| ID | Name | Wait Time | Description |
|---|---|---|---|
swg_up | Increase SWG Output | 0 min | Turn up the dial on your salt chlorine generator cell. |
drain | Dilute Pool | 0 min | Partially drain and refill with fresh water. Retest afterward. |
drain_fc | Dilute Pool (FC) | 0 min | Stop adding chlorine and wait for it to lower naturally, or partially drain and refill. |
drain_cya | Dilute Pool (CYA) | 0 min | Stop adding stabilized chlorine (tablets/granules). Partially drain and refill with fresh water using a hose filter. |
drain_ch | Dilute Pool (CH) | 0 min | Partially drain and refill with fresh water using a hose filter. |
Special Treatments
| ID | Name | Purpose |
|---|---|---|
wait | Wait | Inserted automatically between treatments. The ounces field contains the number of minutes to wait. |
warning | Warning | Attached to readings that are slightly out of range but within the warning buffer. |
Substitutions by Formula
Each formula has a default treatment (the first in the list) for each reading direction. You can substitute any of the alternatives.
Chlorine Formula Substitutions
| Reading | Direction | Default | Alternatives |
|---|---|---|---|
| pH | Raise | ph_increaser | soda_ash |
| pH | Lower | sodium_bisulfate | m_acid |
| Total Alkalinity | Raise | alk_increaser | baking_soda |
| Total Alkalinity | Lower | sodium_bisulfate | m_acid |
| Free Chlorine | Raise | chlorine_tablets | calc_hypo, na_hclo |
| Free Chlorine | Lower | drain_fc | — |
| Cyanuric Acid | Raise | cya | — |
| Cyanuric Acid | Lower | drain_cya | — |
| Calcium Hardness | Raise | cal_chlor | — |
| Calcium Hardness | Lower | drain_ch | — |
| Combined Chlorine | Lower | shock | — |
Salt Formula Substitutions
| Reading | Direction | Default | Alternatives |
|---|---|---|---|
| pH | Raise | ph_increaser | soda_ash |
| pH | Lower | sodium_bisulfate | m_acid |
| Total Alkalinity | Raise | alk_increaser | baking_soda |
| Total Alkalinity | Lower | sodium_bisulfate | m_acid |
| Free Chlorine | Raise | swg_up | calc_hypo, dichlor |
| Free Chlorine | Lower | drain_fc | — |
| Cyanuric Acid | Raise | cya | — |
| Cyanuric Acid | Lower | drain_cya | — |
| Calcium Hardness | Raise | cal_chlor | — |
| Calcium Hardness | Lower | drain_ch | — |
| Combined Chlorine | Lower | shock | — |
| Salt | Raise | salt | — |
| Salt | Lower | drain | — |
Bromine Formula Substitutions
| Reading | Direction | Default | Alternatives |
|---|---|---|---|
| pH | Raise | ph_increaser | soda_ash |
| pH | Lower | sodium_bisulfate | m_acid |
| Total Alkalinity | Raise | alk_increaser | baking_soda |
| Total Alkalinity | Lower | sodium_bisulfate | m_acid |
| Bromine | Raise | bromine | — |
| Bromine | Lower | drain | — |
| Cyanuric Acid | Raise | cya | — |
| Cyanuric Acid | Lower | drain_cya | — |
| Calcium Hardness | Raise | cal_chlor | — |
| Calcium Hardness | Lower | drain_ch | — |
| Combined Chlorine | Lower | shock | — |
Minerals Formula Substitutions
Same as the Chlorine formula — only the FC target range differs (0.5–4 ppm instead of 1–3 ppm).
How the Calculation Engine Works
When you submit readings to the /api/calculate/ endpoint, the engine runs through these steps:
1. Determine Target Ranges
For each reading, the engine determines the ideal range by checking (in order of priority):
- Custom target levels you pass in the request (highest priority)
- Formula-level overrides (e.g., salt formula sets CYA to 60–80 ppm, minerals formula sets FC to 0.5–4 ppm)
- Wall type adjustments (plaster/concrete/pebble pools get CH target of 200–275 ppm)
- Default reading targets (lowest priority)
2. Calculate Deltas
For each reading, the engine compares the current value against the target range:
- Within target range — no action needed
- Outside target, within warning buffer — a warning is generated (no chemical treatment)
- Outside warning buffer — a delta is calculated (distance from the midpoint of the target range)
3. Run Adjusters
Adjusters handle chemical interactions between readings. They run after each reading is evaluated.
Shock Adjuster (chlorine, salt, minerals formulas):
- Infers combined chlorine: CC = TC − FC
- If CC exceeds the 0.1 ppm threshold, creates a delta that triggers shock treatment
- This is why both FC and TC readings are important — the difference tells the engine about chloramine levels
pH / TA Adjuster (all formulas):
- pH and total alkalinity are chemically linked — raising pH also raises TA, and vice versa
- The adjuster accounts for these cross-effects so the engine doesn't over-correct
4. Execute Treatments in Balance Order
The engine processes readings in a specific order. For each reading:
- Check if there's a delta (positive = too low, need to raise; negative = too high, need to lower)
- Select the default treatment, or the substitution if one was provided
- Run the treatment's dosing function to calculate the exact amount in ounces based on pool volume and delta
- Update all deltas to account for side effects
Why order matters: pH is always balanced first because it affects how effective other chemicals are. Alkalinity is second because it buffers pH. Chlorine/bromine is next for sanitization. CYA and CH come later since they change slowly and don't interact with other chemicals as much.
5. Insert Wait Periods
The engine automatically inserts "Wait" actions between treatments that need circulation time:
| Treatment | Wait Before Next |
|---|---|
| Chlorine Granules | 15 minutes |
| Dichlor | 15 minutes |
| Most dry chemicals | 20 minutes |
| Liquid Chlorine | 60 minutes |
| Chlorine Tablets | 60 minutes |
| Hardness Increaser | 8 hours |
| Shock | 8 hours |
Wait actions appear in the response with type: "special" and the ounces field contains the number of minutes to wait.
6. Combine Drain Actions
If multiple readings are dangerously high (e.g., both CYA and calcium hardness), the engine combines their individual drain treatments (drain_cya, drain_ch) into a single "Drain & Refill" action and sorts it to the top. One partial drain fixes multiple problems.
7. Append Warnings
Readings within the warning buffer are appended as warning actions at the end. The ounces field on warnings contains the delta from the target range (negative = below target, positive = above target).
Target Level Overrides
Every reading has a default target range, but you can override them per-request:
{
"target_levels": [
{ "id": "fc", "min": 3, "max": 5 },
{ "id": "ph", "min": 7.4, "max": 7.6 }
]
}This is useful when:
- A pool has special requirements (e.g., commercial pools need higher chlorine)
- A user has customized their ideal ranges
- A specific wall type or sanitization system calls for different targets
The Pool Object
The pool object tells the engine about the physical pool:
| Field | Description | Impact |
|---|---|---|
gallons | Pool volume | Directly scales all chemical amounts. A 20,000 gallon pool needs twice as much chemical as a 10,000 gallon pool. |
water_type | Sanitization method | One of: chlorine, salt_water, bromine, minerals, copper, ozone, uv. Informational — you select the formula explicitly via formula_id. |
wall_type | Pool surface material | One of: vinyl, plaster, fiberglass, concrete, pebble. Affects calcium hardness targets — plaster, concrete, and pebble pools need higher CH (200–275 ppm) to prevent surface etching. |
Understanding the Response
The response contains an ordered list of actions — the steps the pool owner should follow, in sequence.
Action Types
| Type | Meaning | ounces contains |
|---|---|---|
raise | A reading is too low — add a chemical to increase it | Amount of chemical in ounces |
lower | A reading is too high — add a chemical to decrease it | Amount of chemical in ounces |
special | A special action: shocking, draining, or waiting | Minutes to wait (for wait actions), or amount (for shock/drain) |
warning | A reading is slightly out of range but within the warning buffer | Delta from ideal (negative = too low, positive = too high) |
Treatment Options
Each action includes a treatment_options array listing all available treatments for that reading. The treatment field shows the currently selected one. You can use the IDs from treatment_options as substitutions values in future requests to switch treatments.
Example Response Walkthrough
For a 10,000 gallon chlorine pool with FC=0, pH=7.2, TA=80, CYA=0, CH=0:
- Raise FC — Add chlorine tablets (1 oz). Alternatives: chlorine granules, liquid chlorine
- Wait — 60 minutes for chlorine to circulate
- Raise CYA — Add 52 oz stabilizer to protect chlorine from UV
- Wait — 20 minutes
- Raise CH — Add 288 oz hardness increaser to prevent surface damage
Each step is a separate action in the actions array, in the order they should be performed.