============================================================ */ (function(){ 'use strict'; function init(){ var root = document.getElementById('zssb'); if(!root) return; /* ── State ──────────────────────────────────────── */ var s1Type = 'sleeper'; // 'sleeper' or 'offduty' var s2Type = 'sleeper'; /* ── Toggle button setup ────────────────────────── */ function setupToggle(slpId, offId, callback){ var slpBtn = document.getElementById(slpId); var offBtn = document.getElementById(offId); if(!slpBtn || !offBtn) return; slpBtn.addEventListener('click', function(){ slpBtn.classList.add('active'); offBtn.classList.remove('active'); callback('sleeper'); }); offBtn.addEventListener('click', function(){ offBtn.classList.add('active'); slpBtn.classList.remove('active'); callback('offduty'); }); } setupToggle('zssb-s1-sleeper', 'zssb-s1-offduty', function(t){ s1Type = t; }); setupToggle('zssb-s2-sleeper', 'zssb-s2-offduty', function(t){ s2Type = t; }); /* ── Format decimal hours → "Xh Ym" ─────────────── */ function fmtHrs(h){ if(isNaN(h) || h < 0) return '0h 0m'; var hh = Math.floor(h); var mm = Math.round((h - hh) * 60); if(mm === 60){ hh++; mm = 0; } if(hh === 0 && mm === 0) return '0h 0m'; if(hh === 0) return mm + 'm'; if(mm === 0) return hh + 'h'; return hh + 'h ' + mm + 'm'; } /* ── Add decimal hours to a time string ─────────── */ function addHrs(timeStr, hrs){ if(!timeStr || timeStr === '') return null; var parts = timeStr.split(':'); if(parts.length < 2) return null; var totalMins = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10) + Math.round(hrs * 60); totalMins = ((totalMins % 1440) + 1440) % 1440; var h = Math.floor(totalMins / 60); var m = totalMins % 60; return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m; } /* ── Convert 24hr "HH:MM" → 12hr "H:MM AM/PM" ─── */ function to12hr(t24){ if(!t24) return null; var p = t24.split(':'); var h = parseInt(p[0], 10); var m = p[1]; var ampm = h >= 12 ? 'PM' : 'AM'; h = h % 12; if(h === 0) h = 12; return h + ':' + m + ' ' + ampm; } /* ── Warning helpers ─────────────────────────────── */ function showWarn(msg){ var w = document.getElementById('zssb-warn'); var wm = document.getElementById('zssb-wmsg'); if(w && wm){ wm.textContent = msg; w.classList.add('show'); } } function hideWarn(){ var w = document.getElementById('zssb-warn'); if(w) w.classList.remove('show'); } /* ── Status bar progress ─────────────────────────── */ function setStatus(step){ var pills = ['zssb-pill-1', 'zssb-pill-2', 'zssb-pill-3']; for(var i = 0; i < pills.length; i++){ var el = document.getElementById(pills[i]); if(el){ el.classList[i < step ? 'add' : 'remove']('active'); } } } /* ════════════════════════════════════════════════ MAIN CALCULATION — FMCSA 49 CFR §395.1(g) ════════════════════════════════════════════════ */ function calc(){ hideWarn(); setStatus(2); /* Read inputs */ var drivenBefore = parseFloat(document.getElementById('zssb-driven').value) || 0; var drivenBetween = parseFloat(document.getElementById('zssb-driven-between').value) || 0; var s1h = parseFloat(document.getElementById('zssb-s1-hrs').value) || 0; var s1m = parseFloat(document.getElementById('zssb-s1-min').value) || 0; var s2h = parseFloat(document.getElementById('zssb-s2-hrs').value) || 0; var s2m = parseFloat(document.getElementById('zssb-s2-min').value) || 0; var s1Start = document.getElementById('zssb-s1-start').value; var s2Start = document.getElementById('zssb-s2-start').value; /* Convert to decimal hours */ var split1 = s1h + (s1m / 60); var split2 = s2h + (s2m / 60); /* ── Validation ────────────────────────────────── */ if(split1 <= 0 && split2 <= 0){ showWarn('Please enter the duration for at least one split rest period.'); return; } if(split1 < 0 || split2 < 0){ showWarn('Rest period durations cannot be negative.'); return; } if(drivenBefore < 0 || drivenBetween < 0){ showWarn('Driving hours cannot be negative.'); return; } var totalDriven = drivenBefore + drivenBetween; if(totalDriven > 11){ showWarn('Total hours driven (' + totalDriven.toFixed(2) + ' hrs) exceeds the 11-hour driving limit.'); return; } /* ── FMCSA Split Sleeper Berth Logic ───────────── RULE 1: Combined rest >= 10 hours RULE 2: The longer period must be >= 7 hrs AND in sleeper berth RULE 3: The shorter period must be >= 2 hrs (sleeper or off-duty) ─────────────────────────────────────────────────── */ var totalRest = split1 + split2; var combinedOk = totalRest >= 10; var longSplit = Math.max(split1, split2); var shortSplit = Math.min(split1, split2); var longIsS1 = split1 >= split2; var longType = longIsS1 ? s1Type : s2Type; /* Long split: must be >= 7 hrs AND sleeper berth */ var longOk = (longSplit >= 7) && (longType === 'sleeper'); /* Short split: must be >= 2 hrs (any type) */ var shortOk = (shortSplit >= 2); /* Single-split detection (only one period entered) */ var oneSplit = (split1 > 0 && split2 === 0) || (split1 === 0 && split2 > 0); var isCompliant = false; var reason = ''; if(oneSplit){ isCompliant = false; reason = 'Only one split period entered. Please enter both Split 1 and Split 2 durations to check full FMCSA compliance.'; } else { var failReasons = []; if(!longOk){ if(longSplit < 7){ failReasons.push('Longer split (' + fmtHrs(longSplit) + ') is under the required 7-hour minimum.'); } else { failReasons.push('Longer split (' + fmtHrs(longSplit) + ') must be in the sleeper berth, not off-duty.'); } } if(!shortOk){ failReasons.push('Shorter split (' + fmtHrs(shortSplit) + ') is under the required 2-hour minimum.'); } if(!combinedOk){ failReasons.push('Combined rest (' + fmtHrs(totalRest) + ') is under the 10-hour minimum required.'); } isCompliant = longOk && shortOk && combinedOk; if(isCompliant){ reason = 'Both splits meet FMCSA requirements. Your 14-hour clock is paused during both rest periods and restarts at the end of Split 2.'; } else { reason = failReasons.join(' '); } } /* ── Remaining drive time ───────────────────────── */ var driveRemaining = Math.max(0, 11 - totalDriven); /* ── Timeline data (if start times provided) ──── */ var timelineData = null; if(s1Start && s1Start !== ''){ var s1End = addHrs(s1Start, split1); var midDrive = s2Start && s2Start !== '' ? s2Start : (s1End ? addHrs(s1End, drivenBetween) : null); var s2End = midDrive ? addHrs(midDrive, split2) : null; timelineData = { s1Start: to12hr(s1Start), s1End: to12hr(s1End), s2Start: s2Start && s2Start !== '' ? to12hr(s2Start) : (midDrive ? to12hr(midDrive) : null), s2End: to12hr(s2End), resumeTime: s2End ? to12hr(s2End) : null }; } /* ── Render results ──────────────────────────────── */ renderResults(isCompliant, reason, { split1: split1, split2: split2, totalRest: totalRest, longSplit: longSplit, shortSplit: shortSplit, longOk: longOk, shortOk: shortOk, combinedOk: combinedOk, longType: longType, drivenBefore: drivenBefore, drivenBetween: drivenBetween, totalDriven: totalDriven, driveRemaining:driveRemaining, s1Type: s1Type, s2Type: s2Type, oneSplit: oneSplit }, timelineData); setStatus(3); } /* ════════════════════════════════════════════════ RENDER RESULTS ════════════════════════════════════════════════ */ function renderResults(isCompliant, reason, d, tl){ /* Element refs */ var resEl = document.getElementById('zssb-res'); var banner = document.getElementById('zssb-banner'); var bannerStatus= document.getElementById('zssb-banner-status'); var bannerReason= document.getElementById('zssb-banner-reason'); var bannerSvg = document.getElementById('zssb-banner-svg'); var cardsEl = document.getElementById('zssb-cards'); var breakdownEl = document.getElementById('zssb-breakdown'); var timelineEl = document.getElementById('zssb-timeline'); var tlWrap = document.getElementById('zssb-timeline-wrap'); /* ── Compliance Banner ──────────────────────────── */ if(d.oneSplit){ banner.className = 'compliance-banner non-compliant'; bannerStatus.textContent = 'Incomplete — Enter Both Splits'; bannerSvg.innerHTML = ''; } else if(isCompliant){ banner.className = 'compliance-banner compliant'; bannerStatus.textContent = '✅ FMCSA Compliant — Valid Split'; bannerSvg.innerHTML = ''; } else { banner.className = 'compliance-banner non-compliant'; bannerStatus.textContent = '❌ Non-Compliant — Invalid Split'; bannerSvg.innerHTML = ''; } bannerReason.textContent = reason; /* ── Summary Cards ──────────────────────────────── */ var cards = []; if(!d.oneSplit){ cards.push({ v: fmtHrs(d.split1), u: d.s1Type === 'sleeper' ? 'SLEEPER' : 'OFF-DUTY', n: 'Split 1 Duration', cls: d.s1Type === 'sleeper' ? 'blue' : 'orange' }); cards.push({ v: fmtHrs(d.split2), u: d.s2Type === 'sleeper' ? 'SLEEPER' : 'OFF-DUTY', n: 'Split 2 Duration', cls: d.s2Type === 'sleeper' ? 'blue' : 'orange' }); cards.push({ v: fmtHrs(d.totalRest), u: 'TOTAL REST', n: 'Combined Off-Duty', cls: d.combinedOk ? 'green' : 'red' }); cards.push({ v: fmtHrs(d.driveRemaining), u: 'REMAINING', n: 'Drive Time Left', cls: d.driveRemaining > 4 ? 'green' : (d.driveRemaining > 2 ? 'orange' : 'red') }); } cardsEl.innerHTML = cards.map(function(c){ return '
' + '
' + c.v + '
' + '
' + c.u + '
' + '
' + c.n + '
' + '
'; }).join(''); /* ── Breakdown Table ────────────────────────────── */ var rows = []; if(!d.oneSplit){ rows.push({lbl: 'Split 1 (' + (d.s1Type === 'sleeper' ? 'Sleeper Berth' : 'Off-Duty') + ')', val: fmtHrs(d.split1), cls: ''}); rows.push({lbl: 'Split 2 (' + (d.s2Type === 'sleeper' ? 'Sleeper Berth' : 'Off-Duty') + ')', val: fmtHrs(d.split2), cls: ''}); rows.push({lbl: 'Combined Rest Total', val: fmtHrs(d.totalRest) + (d.totalRest >= 10 ? ' ✓' : ' ✗ (Need ≥10h)'), cls: d.combinedOk ? 'ok' : 'fail'}); rows.push({lbl: 'Longer Split ≥7 hrs in Sleeper Berth', val: d.longOk ? '✓ Pass' : '✗ Fail', cls: d.longOk ? 'ok' : 'fail'}); rows.push({lbl: 'Shorter Split ≥2 hrs (any type)', val: d.shortOk ? '✓ Pass' : '✗ Fail', cls: d.shortOk ? 'ok' : 'fail'}); rows.push({lbl: 'Hours Driven Before Split 1', val: fmtHrs(d.drivenBefore), cls: 'info'}); rows.push({lbl: 'Hours Driven Between Splits', val: fmtHrs(d.drivenBetween), cls: 'info'}); rows.push({lbl: 'Total Hours Driven', val: fmtHrs(d.totalDriven) + ' / 11 hrs max', cls: d.totalDriven < 11 ? 'ok' : 'fail'}); rows.push({lbl: 'Drive Time Remaining After Splits', val: fmtHrs(d.driveRemaining), cls: d.driveRemaining > 0 ? 'ok' : 'fail'}); rows.push({lbl: '14-Hour Clock Paused During Splits', val: isCompliant ? 'Yes — Both periods excluded' : 'Not applicable (fix issues above)', cls: isCompliant ? 'ok' : 'fail'}); } breakdownEl.innerHTML = rows.map(function(r){ return '
' + '' + r.lbl + '' + '' + r.val + '' + '
'; }).join(''); /* ── Timeline ───────────────────────────────────── */ if(!d.oneSplit){ tlWrap.style.display = 'block'; var items = []; items.push({ dot: 'drive', label: 'Started Driving', desc: 'Drove ' + fmtHrs(d.drivenBefore) + ' before first rest period.' }); items.push({ dot: 'sleep', label: 'Split 1 Begins' + (tl && tl.s1Start ? ' at ' + tl.s1Start : ''), desc: fmtHrs(d.split1) + ' ' + (d.s1Type === 'sleeper' ? 'in Sleeper Berth' : 'Off-Duty') + (tl && tl.s1End ? ' → Ends: ' + tl.s1End : '') }); if(d.drivenBetween > 0){ items.push({ dot: 'drive', label: 'Resumed Driving Between Splits', desc: 'Drove ' + fmtHrs(d.drivenBetween) + ' between the two rest periods.' }); } items.push({ dot: 'sleep', label: 'Split 2 Begins' + (tl && tl.s2Start ? ' at ' + tl.s2Start : ''), desc: fmtHrs(d.split2) + ' ' + (d.s2Type === 'sleeper' ? 'in Sleeper Berth' : 'Off-Duty') + (tl && tl.s2End ? ' → Ends: ' + tl.s2End : '') }); if(isCompliant){ items.push({ dot: 'done', label: '14-Hour Clock Restarts' + (tl && tl.resumeTime ? ' at ' + tl.resumeTime : ''), desc: 'Both splits complete. ' + fmtHrs(d.driveRemaining) + ' drive time remaining.' }); } else { items.push({ dot: 'off', label: 'Non-Compliant — Cannot Resume', desc: 'Fix the split issues above before resuming driving.' }); } timelineEl.innerHTML = items.map(function(it){ return '
' + '
' + '
' + '
' + it.label + '
' + '
' + it.desc + '
' + '
'; }).join(''); } else { tlWrap.style.display = 'none'; } /* ── Show results panel ─────────────────────────── */ resEl.classList.add('show'); setTimeout(function(){ resEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 100); } /* ════════════════════════════════════════════════ RESET ════════════════════════════════════════════════ */ function resetAll(){ /* Clear number inputs */ var numIds = ['zssb-driven', 'zssb-driven-between', 'zssb-s1-hrs', 'zssb-s1-min', 'zssb-s2-hrs', 'zssb-s2-min']; numIds.forEach(function(id){ var el = document.getElementById(id); if(el) el.value = ''; }); /* Clear time inputs */ ['zssb-s1-start', 'zssb-s2-start'].forEach(function(id){ var el = document.getElementById(id); if(el) el.value = ''; }); /* Reset toggle states */ s1Type = 'sleeper'; s2Type = 'sleeper'; ['zssb-s1-sleeper', 'zssb-s2-sleeper'].forEach(function(id){ var el = document.getElementById(id); if(el) el.classList.add('active'); }); ['zssb-s1-offduty', 'zssb-s2-offduty'].forEach(function(id){ var el = document.getElementById(id); if(el) el.classList.remove('active'); }); /* Hide warning and results */ hideWarn(); var resEl = document.getElementById('zssb-res'); if(resEl) resEl.classList.remove('show'); setStatus(1); } /* ── Event Listeners ──────────────────────────────── */ var calcBtn = document.getElementById('zssb-btn'); if(calcBtn) calcBtn.addEventListener('click', calc); var rstBtn = document.getElementById('zssb-reset'); if(rstBtn) rstBtn.addEventListener('click', resetAll); /* Enter key triggers calculation */ var numFields = ['zssb-driven', 'zssb-driven-between', 'zssb-s1-hrs', 'zssb-s1-min', 'zssb-s2-hrs', 'zssb-s2-min']; numFields.forEach(function(id){ var el = document.getElementById(id); if(el) el.addEventListener('keydown', function(e){ if(e.key === 'Enter') calc(); }); }); } /* end init() */ /* ── Safe DOM-ready execution ───────────────────────── */ if(document.readyState === 'loading'){ document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
Saturation Index Calculator
Langelier Saturation Index (LSI) for pools, spas & water systems — instant results.
Water Chemistry Inputs
Water pH
Measured pH of the water
Calcium Hardness
As CaCO₃ (ppm / mg/L)
Total Alkalinity
As CaCO₃ (ppm / mg/L)
Water Temperature
°F or °C
TDS (Total Dissolved Solids)
Optional (ppm). Default: 1000
Water Use Type
Affects ideal target range
!
Please enter valid values for pH, Calcium Hardness, Total Alkalinity, and Temperature.
LSI Results
Langelier Saturation Index (LSI)
0.00
Balanced
Formula, References & Notes
  • LSI Formula: LSI = pH − pHs  (Langelier, 1936)
  • pHs Formula: pHs = (pK2 − pKs) + pCa + pAlk + pT
  • pCa = log factor of Calcium Hardness (mg/L as CaCO₃)
  • pAlk = log factor of Total Alkalinity (mg/L as CaCO₃)
  • pT = temperature correction factor (varies by °C)
  • (pK2 − pKs) = constant ≈ 2.5 at standard ionic strength; TDS-adjusted here.
  • Ideal LSI range: −0.3 to +0.3 (pools); 0.0 to +0.5 (drinking water)
  • Source: AWWA, Pool & Hot Tub Alliance (PHTA), WHO Water Quality Guidelines
  • For regulatory compliance, verify results with certified laboratory testing.

Saturation Index Calculator: Find Your Water Balance Instantly

Water that is too corrosive eats away at pipes and pool surfaces; water that is too scale-forming leaves ugly calcium deposits and clogs equipment. The Saturation Index Calculator on ZoCalculator.com gives you an instant reading of your water's chemical balance — so you know exactly where you stand before a problem starts. Whether you manage a swimming pool, a spa, or a municipal water system, this free tool delivers your result in seconds.


What This Calculator Tells You

Enter your water chemistry values and the tool instantly outputs:

  • Langelier Saturation Index (LSI) score — your key water-balance number
  • Balance status — whether your water is corrosive, balanced, or scale-forming
  • pH contribution to the index
  • Temperature correction factor (pT) — adjusts for water temperature in °F or °C
  • Calcium hardness factor (pCa) — reflects dissolved calcium levels
  • Total alkalinity factor (pAlk) — accounts for the water's buffering capacity
  • Recommended adjustment direction — raise or lower which parameter to restore balance

How the Calculator Works (The Formula & Logic)

The tool uses the Langelier Saturation Index (LSI) formula, developed by Dr. Wilfred Langelier in 1936 and still the global standard for evaluating water balance.

LSI = pH − pHs

Where pHs is the saturation pH — the theoretical pH at which water is perfectly in equilibrium with calcium carbonate. It is calculated as:

pHs = pK2 − pKs + pCa + pAlk + pT

In plain English:

  • pH = your measured water pH
  • pCa = a factor derived from your calcium hardness reading (mg/L)
  • pAlk = a factor derived from your total alkalinity reading (mg/L as CaCO₃)
  • pT = a temperature correction factor (higher temperatures reduce pHs)
  • pK2 − pKs = a constant that accounts for the chemistry of carbonate equilibrium (typically around 2.5 at standard conditions)

The calculator handles all the logarithmic lookups and table values internally — you just enter your real-world readings.


Standard LSI Ratings & Classifications

LSI ScoreWater Balance StatusMeaning
Below −0.5Aggressively CorrosiveDissolves metal, concrete, plaster
−0.5 to −0.3Mildly CorrosiveSlight etching risk over time
−0.3 to +0.3Balanced (Ideal Range)Water is in equilibrium ✅
+0.3 to +0.5Mildly Scale-FormingLight calcium deposits possible
Above +0.5Aggressively Scale-FormingHeavy scaling, cloudy water

For residential and commercial pool applications, most operators target an LSI between −0.2 and +0.2 for a comfortable margin on both sides.


Step-by-Step Practical Example

Let's calculate saturation index for a typical outdoor swimming pool in summer.

Given values:

  • pH: 7.4
  • Calcium Hardness: 250 mg/L
  • Total Alkalinity: 100 mg/L (as CaCO₃)
  • Water Temperature: 80°F (27°C)
  • TDS: 1,000 mg/L (standard pool water)

Step 1 — Look up factor values:

  • pCa (for 250 mg/L) ≈ 1.90
  • pAlk (for 100 mg/L) ≈ 1.90
  • pT (for 80°F) ≈ 0.60
  • (pK2 − pKs) constant ≈ 2.50

Step 2 — Calculate pHs:

pHs = 2.50 + 1.90 + 1.90 − 0.60 = 5.70

Wait — this is the simplified form; the full standard formula gives:

pHs = (pK2 − pKs) + pCa + pAlk + pT = 2.5 + 1.9 + 1.9 + 0.6 = 6.9

Step 3 — Calculate LSI:

LSI = 7.4 − 6.9 = +0.5

Result: This pool is at the upper edge of scale-forming territory. The operator should slightly lower pH or reduce alkalinity to bring the LSI closer to +0.2.


How to Use Zo Calculator's Saturation Index Tool

  1. Enter your water pH — use a reliable test kit or digital meter for accuracy.
  2. Enter calcium hardness — input your reading in mg/L (ppm).
  3. Enter total alkalinity — input in mg/L as CaCO₃.
  4. Enter water temperature — choose °F or °C; the tool adjusts the pT factor automatically.
  5. Enter TDS (optional) — total dissolved solids, if known; defaults to 1,000 ppm for pool water.
  6. Click "Calculate" — your LSI score, saturation pH, and balance classification appear instantly on ZoCalculator.com.
  7. Read the recommendation — the tool tells you which parameter to adjust and in which direction.

Practical Applications and Real-World Uses

  • Swimming pool operators use the pool saturation index calculator to prevent surface etching and protect vinyl liners, plaster, and tile grout.
  • Spa and hot tub owners rely on LSI checks more frequently because high temperatures accelerate both scaling and corrosion.
  • Municipal water treatment plants use the Langelier Saturation Index to ensure distributed water doesn't corrode lead or copper service lines.
  • Industrial cooling tower managers use LSI to minimize scale buildup on heat exchange surfaces and extend equipment life.
  • Plumbers and water treatment professionals use it to assess whether a building's supply water needs a softener, conditioner, or pH adjustment.
  • Home well-water owners use the index to determine if their groundwater is eating away at plumbing before visible damage appears.

Important Notes & Technical Limitations

  • Not a substitute for professional testing. The calculator produces an estimate based on the values you enter. Lab-certified water analysis is required for regulatory compliance or health-critical decisions.
  • Assumes standard carbonate chemistry. The LSI model assumes calcium carbonate is the primary scaling agent. Waters with high silica, sulfate, or iron content may behave differently than the index predicts.
  • Temperature sensitivity. LSI shifts significantly with temperature. Outdoor pool and spa values should be recalculated seasonally — at minimum, at major temperature transitions (spring/fall).
  • TDS effect is minor but real. At very high TDS levels (above 3,000 ppm), the ionic strength of the water affects carbonate equilibrium in ways the simplified LSI formula does not fully capture. The Ryznar Stability Index (RSI) may be more appropriate in those cases.

Helpful References & Sources

  • Water Quality Association (WQA) — wqa.org — industry standards for water treatment and chemical balance
  • U.S. Centers for Disease Control and Prevention (CDC) — cdc.gov — pool water chemistry guidelines for public health safety
  • ASHRAE — ashrae.org — standards for cooling tower water treatment and scale/corrosion control in HVAC systems

🙋 Frequently Asked Questions (FAQs)

What is the Langelier Saturation Index?

The Langelier Saturation Index (LSI) is a numerical score that indicates whether water is in equilibrium with calcium carbonate, corrosive (tends to dissolve it), or scale-forming (tends to deposit it). A score of 0 represents perfect balance, while negative values indicate corrosive water and positive values indicate scale-forming potential. It was developed by water chemist Dr. Wilfred Langelier in 1936 and remains the most widely used water-balance standard worldwide.

What is a good Langelier Saturation Index for a pool?

For swimming pools, the ideal LSI range is between −0.3 and +0.3, with most professionals targeting the tighter band of −0.2 to +0.2. Water below −0.3 can etch plaster, corrode metal fittings, and irritate swimmers' eyes and skin. Water above +0.3 is prone to calcium scaling on surfaces, cloudy water, and clogged filters.

How do I use a pool saturation index calculator?

To use a pool saturation index calculator, you need four measurements from your pool: pH, calcium hardness (ppm), total alkalinity (ppm), and water temperature. Enter all four values into the tool — like the one on ZoCalculator.com — and it calculates your LSI score and tells you whether your water is balanced, corrosive, or scale-forming, along with which chemistry to adjust.

What parameters affect the saturation index?

The four primary parameters that determine the LSI are pH, calcium hardness, total alkalinity, and water temperature. Of these, pH has the most direct and immediate influence because it appears directly in the LSI formula. Total dissolved solids (TDS) can be included as a minor correction factor for higher-accuracy results.

What happens if the saturation index is too low (negative)?

A strongly negative LSI means your water is corrosive. For pools, this translates to etching of plaster and concrete, pitting of metal equipment, and a rough, uncomfortable pool surface over time. In drinking water or plumbing systems, aggressively corrosive water can leach lead and copper from pipes, which is a serious public health concern. Raising pH, calcium hardness, or alkalinity will push the LSI in a positive direction.

What happens if the saturation index is too high (positive)?

A high positive LSI indicates scale-forming water, which deposits calcium carbonate on pool surfaces, pipe interiors, heater elements, and filter media. This reduces equipment efficiency, creates rough, staining surfaces, and can lead to expensive maintenance. Lowering pH or total alkalinity — or using a sequestering agent — brings the LSI back toward balance.

How often should I calculate the saturation index for my pool?

For residential pools, recalculating your saturation index at least once per week during swim season is a sound practice, especially after heavy rainfall, high bather loads, or significant temperature changes. Commercial pool operators are often required by health codes to monitor water chemistry daily. Spas and hot tubs, because of their higher operating temperatures, should be checked every 2–3 days.

Is the Langelier Saturation Index the same as the Ryznar Stability Index?

No — these are two related but distinct indices. The Langelier Saturation Index (LSI) uses the formula pH − pHs and is the older, more widely cited standard. The Ryznar Stability Index (RSI) uses the formula 2 × pHs − pH and is considered more predictive for industrial water systems at extreme chemistry conditions. For most pool and drinking water applications, the LSI is the appropriate tool to use.

Can I calculate the saturation index for drinking water?

Yes. The LSI was originally developed for drinking water distribution systems to determine whether water would corrode pipes or deposit scale in distribution infrastructure. Many municipal utilities still use it as part of corrosion-control programs required under the EPA's Lead and Copper Rule. The same four inputs — pH, calcium hardness, alkalinity, and temperature — apply to tap and well water calculations.

What is the difference between total alkalinity and pH in the saturation index?

pH measures the concentration of hydrogen ions and directly reflects how acidic or basic the water is. Total alkalinity measures the water's buffering capacity — its ability to resist changes in pH. In the saturation index formula, both contribute independently: pH appears directly as the main variable, while total alkalinity is converted to a logarithmic factor (pAlk) that represents carbonate concentration. You can have a "correct" pH with low alkalinity and still have unstable, hard-to-manage water chemistry.


Explore Related Calculators on Zo Calculator