How we work out what a water softener costs
Every installed range on this site is one arithmetic expression, evaluated at three consistent corners of its own uncertainty, over 6,303 published bands covering 573 labour-survey areas as of 2026-10-04. This page writes the expression out, names every number in it, shows what kind of number each one is, publishes the accuracy scoreboard, and states what the method cannot do.
The model
InstalledCost = Equipment + Labour + SiteComplexity + Ancillaries
Labour = hours x loadedHourlyRate
loadedHourlyRate = published median hourly wage for the area
x burdenMultiplier
hours = baseInstallHours + addedHoursForComplexity(class)The labour term is a wage a statistical agency publishes multiplied by a billing convention we declare, and the two have completely different standing. The wage is measured and cited; the multiplier is not a figure the wage survey supplies and no statistical agency publishes it. Both are shown below with their own provenance, read from the signed artifacts rather than described here.
Worked at the national rung: the published median hourly wage for the trade is $30.67, and the plausible burden range carries the loaded rate a household is billed to $76.68–$107.35 an hour. A published band is built at the “preplumbed” complexity class, which adds no hours at all: §4.1 makes site complexity something you declare about your own house, never something we infer, so a band contains none of it.
Ancillaries — reverse osmosis, sediment pre-filter, carbon tank, iron filter, neutraliser, salt delivery, service contract — are separate removable lines and none of them is priced anywhere in this dataset. They render as unavailable rather than as zero.
The answer this model produces, with the documented quotes beside it, is on the national water softener cost page.
The rules the generator records
These are not a description of the build written afterwards. They are the strings the band generator writes into cost-bands.json when it runs, reproduced here verbatim.
- bandBasisRule
- A band is the INSTALLED cost of one class of softener in one area: equipment for that (capacity class x build class) plus local loaded labour at the base install hours. It carries no site complexity — §4.1 makes that class user-declared and never inferred, so the band is built at "preplumbed", whose adder is exactly 0 hours, and the four adders stay a separate declared line on the estimator. It carries no ancillary, because §4.1 requires each to be a separate removable line and no ancillary is priced anywhere in this tree. It carries no self-install parts allowance, because §4.2 tier 1's "+ parts" has no signed figure and tiers 2 and 3 have no parts line at all. §4.2's other two tiers are derived at render time by priceTiers() from these same artifacts.
- modelledWidthRule
- A modelled band's three points are the model evaluated at three consistent corners of its own input uncertainty, never at a +/- percentage of a midpoint (§13 T1.4, C0-4): p10 = the class's cheapest indexed SKU at the lowest plausible burden multiplier and the fewest plausible base install hours; p50 = the class's median indexed SKU at the multiplier's midpoint and the signed base hours; p90 = the class's dearest indexed SKU at the highest plausible multiplier and the most plausible base hours. So the interval means the span the installed total covers when all three declared ranges move together, and NOT a fitted quantile of real quotes — that claim needs observations and a passing L5, and it is what an observed band is. estimateInstalledCost() propagates the burden range and the equipment spread; the base-hours range cannot ride with them because LabourEstimateBasis.baseInstallHours is a scalar per evaluation, so the estimate is taken once at each hour bound and the matching corner read off each. Because the installed total is monotone non-decreasing in all three inputs, p10 <= p50 <= p90 holds by construction rather than by assertion.
- equipmentSpreadRule
- The equipment term is propagated at the class's real min / median / max, not collapsed to the median SKU — a decision §13 T1.4 leaves open, taken deliberately and recorded here because it changes every published width. A band for (48,000 grains x standard) is a claim about that CLASS of equipment, not about one SKU: a buyer in that class buys some standard 48k unit, not the median one. Collapsing the term would publish an interval narrower than the model's own inputs justify — overconfident, the direction §7 L5 calls dangerous — and would leave the published width describing a machine nobody is buying. Like the other two ranges, this spread is measured rather than chosen: it is the dispersion actually present in equipment-index.json for that class, so the C0-4 rule against an arbitrary percentage is satisfied on the same terms.
- observedWidthRule
- An observed band's width is the FITTED log-normal half-width from cost-model-params.json, evaluated for the cell's geometry by the same bandFor() §7 L5 measured coverage against — so what publishes is what was gated. It is never the propagated corner span of a modelled band: a measured 80% interval and a modelled span are different claims and are never computed alike.
- determinismNote
- No value here comes from the clock or from a random source: asOf is an input, every collection is sorted by a stable key, and every figure is a pure function of the committed inputs. Two runs on the same inputs are byte-identical and the committed file is a fixed point of this generator.
Every constant, and what kind of number it is
26 constants stand behind the arithmetic above. 23 of them are conventions we declare — nobody measured them, and nothing here claims otherwise — and 3 are cited to a source. The kind beside each row is read from the signed artifact, so this split is a property of the data and not of the sentence you just read.
That includes the numbers a reader is most likely to assume were measured: the burden multiplier and its range, the base install hours, all four site-complexity adders, and all four ten-year running-cost assumptions. Every one of them is a declared convention. Two of those four — the salt price and the water and sewer rate — no longer price anything a page publishes: the running cost now reads both from dated sources, shown under “What is actually measured” below. Each carries an explicit range rather than a single number, and that range is propagated through the model instead of being hidden inside a midpoint. Their kinds, resolved one by one from the signed file:
- base_install_hours — declared convention
- burden_multiplier_seed — declared convention
- burden_multiplier_range — declared convention
- complexity_added_hours_preplumbed — declared convention
- complexity_added_hours_standard — declared convention
- complexity_added_hours_newline — declared convention
- complexity_added_hours_trenching — declared convention
- tco_salt_price_per_pound_assumption — declared convention
- tco_salt_pounds_per_regeneration_assumption — declared convention
- tco_regeneration_water_gallons_assumption — declared convention
- tco_water_and_sewer_rate_assumption — declared convention
| Constant | Value | Unit | Provenance |
|---|---|---|---|
| gallons_per_person_per_day | 75 | US gallons per person per day | citedImported from the sizing SSOT; its source is published with the sizing calculator. |
| default_regen_interval_days | 7 | days between regenerations | citedImported from the sizing SSOT; its source is published with the sizing calculator. |
| iron_compensation_gpg_per_ppm | 5 | grains per gallon added per 1 mg/L (ppm) of dissolved iron | citedImported from the sizing SSOT; its source is published with the sizing calculator. |
| common_softener_capacities_grains | 24,000 / 32,000 / 40,000 / 48,000 / 64,000 | grains | declared conventionTapWaterData editorial policy · §2, §10.12 |
| complexity_added_hours_preplumbed | 0 | added labour hours | declared conventionTapWaterData editorial policy · §4.1 |
| complexity_added_hours_standard | 2 | added labour hours | declared conventionTapWaterData editorial policy · §4.1 |
| complexity_added_hours_newline | 5 | added labour hours | declared conventionTapWaterData editorial policy · §4.1 |
| complexity_added_hours_trenching | 12 | added labour hours | declared conventionTapWaterData editorial policy · §4.1 |
| base_install_hours | 3 (range 2–4) | labour hours for one installation, before the complexity adder | declared conventionTapWaterData editorial policy · §4.1 |
| burden_multiplier_seed | 3 | multiple of the BLS OEWS median hourly wage for SOC 47-2152 (Plumbers, Pipefitters, and Steamfitters) | declared conventionTapWaterData editorial policy · §4.1, owner decision C0-4 |
| burden_multiplier_range | 2.5–3.5 | multiple of the BLS OEWS median hourly wage for SOC 47-2152 (Plumbers, Pipefitters, and Steamfitters) | declared conventionTapWaterData editorial policy · §4.1, owner decision C0-4 |
| staleness_manufacturer_price_days | 90 | days | declared conventionTapWaterData editorial policy · §10.2 |
| staleness_bls_labour_months | 18 | months | declared conventionTapWaterData editorial policy · §10.2 |
| staleness_observation_months | 24 | months | declared conventionTapWaterData editorial policy · §10.2, owner decision C0-3 |
| mad_quarantine_threshold | 3 | median absolute deviations from the class median | declared conventionTapWaterData editorial policy · §5.3 |
| published_interval_coverage | 0.8 | fraction of real installed quotes the published interval contains | declared conventionTapWaterData editorial policy · §1, §4.2 |
| l5_coverage_window | 0.76–0.88 | fraction of holdout observations falling inside the published band | declared conventionTapWaterData editorial policy · §7 L5 |
| l5_max_mape | 0.18 | median absolute percentage error of the band midpoint, as a fraction | declared conventionTapWaterData editorial policy · §7 L5 |
| l5_max_absolute_signed_bias | 0.08 | median signed error, as a fraction, in either direction | declared conventionTapWaterData editorial policy · §7 L5 |
| l5_holdout_fraction | 0.2 | fraction of document-class observations withheld from the fit | declared conventionTapWaterData editorial policy · §7 L5 |
| min_observations_for_observed_band | 8 | document-class observations in a geography | declared conventionTapWaterData editorial policy · §4.4, §7 L6, §10.5 |
| tco_horizon_years | 10 | years | declared conventionTapWaterData editorial policy · §4.3 |
| tco_salt_price_per_pound_assumption | 0.28 (range 0.15–0.5) | US dollars per pound | declared conventionTapWaterData editorial policy · §4.3, operating note 2026-08-23 §9 |
| tco_salt_pounds_per_regeneration_assumption | 10 (range 6–16) | pounds per regeneration | declared conventionTapWaterData editorial policy · §4.3, operating note 2026-08-23 §9 |
| tco_regeneration_water_gallons_assumption | 60 (range 35–100) | US gallons per regeneration | declared conventionTapWaterData editorial policy · §4.3, operating note 2026-08-23 §9 |
| tco_water_and_sewer_rate_assumption | 12 (range 4–24) | US dollars per 1,000 gallons | declared conventionTapWaterData editorial policy · §4.3, operating note 2026-08-23 §9 |
What is actually measured
Two model inputs are measurements rather than conventions: the wage the labour term multiplies, and the equipment prices the equipment term reads. Both are dated, and both are shown with their dates wherever a figure derived from them appears. The running cost is built from dated sources as well, listed at the end of this section.
- Regional labour
- U.S. Bureau of Labor Statistics, Occupational Employment and Wage Statistics (OEWS), May 2025 estimates
- Occupation
- Plumbers, Pipefitters, and Steamfitters (47-2152)
- Vintage
- May 2025 reference period (OEWS M2025), released 2026-05-12, retrieved 2026-08-23
- Licence
- Public domain (U.S. Bureau of Labor Statistics)
Equipment prices are manufacturer direct list prices, read from each manufacturer's own published storefront and stored with the URL and the date they were read. The current index holds 22 units, of which 22 are inside the freshness ceiling and 0 were excluded as stale; the most recent read was 2026-10-04. No retailer price enters a cost band.
Two editorial rules have been amended. National brand names may now appear, beside sourced and dated evidence, on the brand price pages and on the renting-versus-buying page; every other page in this cluster, this one included, names no brand, and a local dealer's name appears on no page at all. Brand is never an input: no band, multiplier or running cost reads which company sold a unit. And a retailer price may now be published, with its source and the date it was read, under the same 90-day ceiling that applies to every equipment price; the salt price in the running cost is one, and so is every listing behind the equipment-only ranges, set out below.
The retailer listings behind the equipment-only prices
The 2 equipment-only ranges on the water softener cost page rest on 6 listings from US online retailers, read on 2026-10-04–2026-10-05. Each is listed here with the price it showed for the equipment alone, a link to the listing it was read from and the date it was read. None of them enters a cost band.
Iron filter combined with a softener
| Price | Source | Read on |
|---|---|---|
| $4,553 | retailer listing | 2026-10-05 |
| $5,506 | retailer listing | 2026-10-05 |
| $1,892 | retailer listing | 2026-10-05 |
Softener bundled with reverse osmosis
| Price | Source | Read on |
|---|---|---|
| $1,710 | retailer listing | 2026-10-04 |
| $997 | retailer listing | 2026-10-04 |
| $901 | retailer listing | 2026-10-04 |
Wages are joined to a location through a committed county-to-survey-area crosswalk and a fixed fallback chain. No rate is ever interpolated between areas, and the rung a figure came from is stored and displayed rather than hidden.
- fallbackChainRule
- §5.2's chain — metro -> state nonmetro -> state -> national — is already resolved inside labour-index.json, which stores the rung each rate came from in level. This file carries medianHourly, level and sourceAreaCodes through untouched. No rate is chosen, averaged or interpolated here; §5.2 forbids interpolating between metros outright, and every median remains a figure BLS published for the single area named in sourceAreaCodes. Where level is worse than "metro", §7 L6 requires the page to say which rung was used, and level is what it reads.
- stalenessRule
- §10.2, with "now" injected as _meta.asOf and never read from a clock. Manufacturer prices: a SKU past 90 days is removed BEFORE the class statistics are taken, so it cannot move a min, a median or a max, and its id is listed in the cell's staleSkuIdsExcluded — excluded and flagged, never silently used. A class with nothing left publishes no band. BLS labour: the build aborts when the OEWS release date is more than 18 months before asOf, rather than emitting bands over a vintage past its ceiling. Quote observations: the 24-month ceiling is applied by T1.3 before anything is fitted, so nothing past it can reach an override here. Each cell publishes both the latest and the oldest pricedOn behind it, so a page cannot present the freshest date as if it were true of the whole basis.
The running cost, and where each part comes from
Salt, regeneration water and electricity are priced from national figures, each with its source and the date it was read; the salt dose and the ends of the drain-volume range are the 2 quantities still declared rather than sourced. Electricity is an upper bound at the control valve's rated draw, not a measurement. For the household the national answer is sized for:
Salt: $5.71–$17.87 a month
6–16 lb of salt per regeneration (our declared assumption: the dose depends on the valve's salt setting) at $8.76–$10.28 a 40 lb bag across 3 retailers, median $10.18. Shelf prices are store-specific.
Sources: salt price (retailer, read on 2026-10-04).
Regeneration water: $2.56–$7.31 a month
About 50 gallons to drain per regeneration (the extension estimate; 35–100 gallons is our declared range around it), at the 2024 national average combined water and sewer rate of $16.83 per 1,000 gallons — a national average, not your utility's tariff.
Sources: water per regeneration (government, read on 2026-10-04); water and sewer rate (government, read on 2026-10-04).
Electricity (upper bound): up to $0.24–$2.89 a month
Up to 1.31–15.76 kWh a month at rated draw, priced at the national average residential rate of 18.31 cents per kWh. Upper bound: control valve assumed to draw its rated/stated power 24 h a day; the controller idles below its rating and only drives its motor during regeneration. Not a measured figure.
Sources: control-valve electrical rating (manufacturer, read on 2026-10-04); electricity price (government, read on 2026-10-04).
Two kinds of band, never conflated
A modelled band is the arithmetic above, available everywhere the wage survey reaches. An observed band is fitted to real installed quotes and published only at 8 or more document-class observations that also clear the calibration gate. Today 6,303 bands are modelled and 0 are observed. Every published range says which kind it is, on the page, next to the number.
The published interval is always a 80% central interval, never a point price. A point price is unfalsifiable and always wrong; an interval with a stated sample size is a claim that can be measured, and that measurement is the scoreboard below.
- bandKindRule
- §4.4's resolution order, resolved at the granularity §6 keys a band by. The L5 GATE stays per geography — §7 L5 measures coverage on a geography's holdout, and splitting the fit per cell would fragment the pooling that makes the burden multiplier identifiable at all — but the observed CLAIM is per (capacityClass x buildClass) cell, because §6 keys n there and §1 says we state how many observations the published range rests on. So a cell publishes an observed band only when its OWN document-class count clears the floor of 8 inside a geography that passed; a cell below the floor publishes the modelled band while the rest of its geography stays observed, rather than borrowing a count no quote in that cell backs. Both halves are carried by types rather than discipline: T1.3 writes an override only for a geography that passed and lists in it only the cells that cleared the floor, and observedSampleSize() is the only way to mint the branded count ObservedBand requires. An override below the floor, at either level, is an artifact contradiction and stops the build naming §7 L1. A cell that clears the floor is refused as well when the observed interval it would publish is NARROWER than the modelled interval built for the same cell: the plausible burden range, the plausible base-hours range and the class's equipment spread are all still uncertain when a fit is taken, so an interval inside the modelled one claims to have resolved uncertainty its own declared inputs still carry — the overconfident direction §7 L5 exists to stop, and the shape a corpus of one repeated total produces. It is a comparison of two quantities this generator already computes, never a floor on the fitted dispersion, because a floor would publish a width nobody measured. One asymmetry is deliberate and recorded rather than hidden: a non-clearing cell inside a PASSING geography takes modelledInterval, and therefore the national multiplier burdenRangeFrom() publishes rather than that geography's fitted one — a modelled band's contract is the propagated corner span of the three declared ranges and nothing else, so it may not carry a fitted parameter it is not claiming to have measured against. A geography never silently downgrades: the kind changes, and with it the label a reader sees.
- publishableRule
- Every emitted band is publishable, and the states that are not are structurally unrepresentable rather than filtered out: a cell with no in-ceiling equipment emits no band at all, a geography that failed or could not demonstrate L5 has no override and therefore publishes its modelled band, and a modelled band is publishable on day one by §4.4. So §7 L1's "no band with publishable:true that failed L5" holds because a failed L5 cannot produce a band here, not because a flag was set correctly. Where L5 was measured and did not publish, area.calibration.disclosure carries T1.3's sentence saying so.
- honestAbsenceRule
- §7 L6. The indexed SKUs do not populate every (capacity class x build class) cell, and an unpopulated cell emits NO BAND in any area — never a zero, never a fabricated price, never a neighbouring cell's price borrowed across. Those cells appear in "equipment" with kind "unavailable" and a reason, so the absence is explicit and auditable rather than inferred from a missing key. A page renders them as unavailable; §14's "never an empty state" is about the page, not a licence to invent data.
How the numbers are checked
Eight layers, each of which either passes or fails the build. A gate that has never been proven to fire is not a gate, so every one of them is driven against deliberately corrupted inputs as well as clean ones.
L0 — Source integrity
Every upstream pull is schema-validated before it can overwrite a committed artifact, a row-count drop guard blocks a shrinking file, every price carries a live source URL and a fetch date, and the wage archives are pinned to a checksum that aborts the build on drift.
L1 — Index invariants
The permanent home of the defect register: every capacity must be a rung of the shipped sizing ladder, every published area must exist in the labour index, every interval must be ordered and finite, no band may claim 8 observations it does not have, and no price may be past its freshness ceiling.
L2 — Golden set
Frozen hand-computed cases — a known unit at a known price on a known date, a known area at a known published wage, and fully hand-worked installed totals. A model change that moves one has to say why in its commit message.
L3 — Live ground-truth sampling
Monthly, a sample of units is re-read live from the manufacturer's own page and compared with the stored price, and the agreement rate is published above with its date and sample size. No run has produced a rate yet, and the scoreboard above says so rather than printing a number.
L4 — Adversarial probes
A copy of the pipeline is deliberately corrupted six ways — a wrong list price, a fabricated area, a poisoned quote cluster, a band published under the observation floor, a stale price and an inverted interval — and each corruption must make the gate exit non-zero naming the invariant it broke. A gate never proven to trip is not a gate.
L5 — Estimate calibration
The core gate. A fifth of document-class observations are held out by a stable hash of their id, the parameters are fitted on the rest, and the published interval has to contain between 76% and 88% of the holdout, with midpoint error at or below 18% and signed bias within 8%. Coverage below the floor means the band is too narrow — overconfident, the dangerous direction. A geography that fails falls back to its modelled band with the fallback disclosed; nothing is edited in place.
L6 — Honest absence
The layer that protects the reader. Below the observation floor no observed band is published at all; where a hardness value is a county ambient estimate rather than a utility-reported one the estimate says so; where the wage came from a coarser rung than the local metro, the page names the rung it used.
L7 — Feedback becomes a permanent test
Every correction — from a reader, from the trade, or from our own spot-check — becomes a golden case or an index invariant before the fix merges. A defect that can recur silently has not been fixed.
The scoreboard
Fitted 2026-10-04, against the May 2025 reference period (OEWS M2025) wage vintage. The corpus holds 0 quote observations — 0 document-class, 0 structured and 0 free-form — of which 0 were fitted.
L3 — live source agreement
Agreement between the prices we store and the prices those manufacturers publish today: Not measured.
No live sample has been recorded yet. The reading half of §7 L3 re-opens each manufacturer page in a browser, which this harness deliberately does not do: reading is an injected seam, so the run is an operator step in the refresh runbook and only the comparison is code. Until a recorded reading set exists there is no agreement rate and none is published — publishing any figure here would say we re-opened those pages and found out, and we have not. The coverage report's own top-level sections are _meta, l5, l3, and its l3 block records that state rather than a number.
L5 — interval calibration
No geography has an observed band, and none can have one yet. The corpus holds no quote observations at all, so there is nothing to fit, nothing to hold out and nothing to measure against. Coverage, error and bias below therefore read “Not measured” rather than zero: a zero would be a measured result, and no measurement has been taken.
Every range on this site is consequently a modelled estimate. That is the honest state of the product on the day it launched, and it is stated here rather than implied by an absence.
| Geography | Document-class observations | Held out | Interval coverage | Midpoint MAPE | Signed bias | Outcome |
|---|---|---|---|---|---|---|
| United States (every geography pooled)0 submitted · 0 held in review · 0 past the freshness ceiling · 0 fittedno reference observation reached §5.3, so there is no class median and no bound — every candidate is retained because nothing was measured that could exclude one | 0 | 0 | Not measured | Not measured | Not measured | not-demonstrable |
No individual geography appears above. A geography enters this table once observations exist for it; the row shown is the national pool, which is where the first ones will land.
Why the national pool is not-demonstrable
- 0 document-class observations back this band — 0 in the corpus less 0 held in §5.3 review — below the floor of 8 (§4.4, §7 L6), so there is no observed band to calibrate
- the deterministic 0.2 split left 0 observations to fit on and 0 to measure against, so coverage cannot be measured on data the fit did not see
United States (every geography pooled) falls back to its modelled estimate because L5 could not be measured for it — 0 document-class observations back this band — 0 in the corpus less 0 held in §5.3 review — below the floor of 8 (§4.4, §7 L6), so there is no observed band to calibrate; the deterministic 0.2 split left 0 observations to fit on and 0 to measure against, so coverage cannot be measured on data the fit did not see.
What L5 can and cannot show, stated rather than implied. It measures our published band against OUR CORPUS, and a corpus is not the world: a cluster that survives §5.3 quarantine sits on both sides of the split and moves the fit and the holdout together, so the signed-bias gate can read near zero while the midpoint has been dragged. What the gates do catch is a corpus whose shape has stopped matching one log-normal population; what they cannot catch is a corpus that is uniformly and consistently wrong. That is why §14 names three independent barriers: per-IP and per-session caps limit a submitter's share, MAD quarantine keeps what is grossly out of family out of the fit, and L5 is the backstop. Note also the sample-size floor the window itself implies. Coverage is a count over a 20% holdout, so no attainable value lies inside 0.76-0.88 until the holdout reaches five quotes — roughly 25 document-class observations — and sampling noise dominates far beyond that: measured over 200 synthetic replications of an honest corpus through this exact pipeline, the coverage gate is cleared 37.5% of the time at n=100, 74.5% at n=400 and 92.5% at n=2,000. Below a few hundred document-class observations a geography keeps publishing its modelled band, which is the conservative direction and the honest one.
Limitations
Stated plainly, because a methodology page that hides its own weaknesses is worse than none. Each of these is a real property of the current dataset, not a disclaimer.
Every band is modelled. None is calibrated on local quotes.
6,303 of 6,303 published bands are modelled, 0 are observed, and the corpus behind them holds 0 document-class observations. Until quotes arrive, these ranges describe what the inputs imply, not what buyers were charged.
An observed band needs roughly 25 observations, not 8.
8 is the honesty floor for publishing an observed band at all. It is not the point at which the calibration gate can be cleared: coverage is a count over a 20% holdout, so no attainable value falls inside 76%–88% until the holdout reaches 5 quotes — about 25 observations in the geography — and sampling noise dominates well past that. Geographies will therefore publish modelled bands for a long time. That is the safe direction, and it means a kill criterion written around eight observations is measuring the wrong number.
The modelled twin-tank build rests on very few prices.
The major national dealer-channel brands publish no price at all, so the dealer channel is published as a summary of documented quotes and never modelled. The modelled premium build class is the direct-to-consumer twin-tank build, carried by the few dual-tank units whose makers publish a price — 4 units across 3 of 5 capacity classes, with as few as 1 standing behind a class (32,000 grains: 1; 48,000 grains: 1; 64,000 grains: 2). At 24,000 and 40,000 grains there is no twin-tank figure at all: 34 of the 51 state pages show two priced routes and a stated absence for the third. Wherever the build does appear, that basis is disclosed beside it.
The ten-year running-cost range is wide, and national.
It runs $1,021–$3,368 over 10 years, its top about 3.3 times its bottom. The spread comes from the retailer salt-price range, the 2 declared quantities — the salt dose and the ends of the drain-volume range — and an electricity span that is an upper bound at rated draw, all taken at their low ends together and then their high ends together. The water and sewer rate is a national average, not your utility's tariff. Narrower ranges would need a local tariff and a measured dose, not a narrower guess.
8 inputs are still unsourced.
4 of them stand on a declared interim assumption and 4 are simply omitted from every figure. An assumption is not a source, so none of them has been struck off the list — they stay recorded as owed:
- salt_price_per_pound (§4.3) — A local delivered price of softener salt per pound, for the reader's own region and salt type.
- salt_pounds_per_regeneration (§4.3) — Pounds of salt consumed per regeneration, as a function of capacity class.
- regeneration_water_gallons (§4.3) — Gallons of water sent to drain per regeneration, for each indexed unit.
- water_and_sewer_rate (§4.3) — Household water and sewer rate, per 1,000 gallons, by geography — what regeneration water actually costs the owner.
- filter_change_cost_and_cadence (§4.3) — Replacement cartridge price and change interval for each ancillary that has one (sediment pre-filter, carbon tank, RO).
- service_contract_annual_fee (§4.3) — Annual dealer service-contract fee.
- self_install_parts_allowance (§4.2) — The allowance for the fittings, flexible connectors, bypass, drain line, sanitiser and shut-offs a self-installer buys alongside the softener — §4.2 tier 1 is equipment at list PLUS parts, and nothing in this tree prices the parts.
- ancillary_prices (§4.1) — A price for each of the seven §4.1 ancillaries: reverse osmosis, sediment pre-filter, carbon tank, iron filter, neutraliser, salt delivery and service contract.
Calibration measures our band against our corpus, not the world.
The gates detect a corpus whose shape has stopped matching one population. A corpus that is uniformly and consistently wrong — for example one an organised submitter has skewed at scale — is not detectable by any coverage gate, which is why submission caps and the 3-deviation quarantine exist alongside it rather than behind it.
Cite this
The ranges, the model and the scoreboard are free to reuse, including commercially, under Creative Commons Attribution 4.0 (CC BY 4.0), as long as you credit TapWaterData with a link. Quote the band kind and the sample size with the number: a range without them is not the claim we are making.
TapWaterData Water Softener Cost Dataset, 2026-10-04. https://www.tapwaterdata.com/water-softener-cost — CC BY 4.0
- Dataset:
- TapWaterData Water Softener Cost Dataset
- Bands generated:
- 2026-10-04
- Equipment prices read:
- 2026-10-04
- Wage vintage:
- May 2025 reference period (OEWS M2025), released 2026-05-12
- Calibration fitted:
- 2026-10-04
- Upstream terms:
- Public domain (U.S. Bureau of Labor Statistics)
The hardness values the sizing chain reads have their own sources and methodology, documented separately and cited there rather than restated here.
By TapWaterData Editorial.
Related pages
Get the Weekly Water Brief
One email a week on water testing, treatment, and what water-quality data means for your home.
One email a week. Unsubscribe anytime.