The rule
40 CFR § 141.80(c)(3) specifies how to calculate the 90th-percentile value for lead and copper. The calculation is deterministic: it does not involve averaging, and it does not interpolate between samples.
Here's the rule in plain terms:
Sort the samples by value, ascending. Count how many samples you have (n). Compute ceil(0.9 × n). Take the value at that ordinal position. That's the 90th-percentile.
The value of the sample at that position becomes the reported 90th-percentile for compliance purposes. Nothing is averaged, nothing is interpolated.
Worked example 1: 10 samples
Samples collected (first-draw tap water, in ppb): 1.2, 2.3, 3.1, 4.0, 4.5, 5.2, 6.8, 7.4, 12.5, 14.1.
Already sorted, ascending. n = 10. 90th-percentile position: ceil(0.9 × 10) = ceil(9) = 9. Value at position 9 (1-indexed): 12.5 ppb.
Reported 90th-percentile value: 12.5 ppb.
The current lead Action Level is 15 ppb (pre-LCRI) or 10 ppb (post-LCRI, depending on trigger). 12.5 ppb is below 15 but above 10; the triggering and reporting depends on which period your system is in.
Worked example 2: 25 samples
Samples: 0.8, 1.1, 1.3, 1.9, 2.0, 2.4, 2.8, 3.1, 3.4, 3.9, 4.2, 4.5, 5.0, 5.8, 6.2, 6.9, 7.1, 7.8, 8.4, 9.2, 10.1, 11.4, 12.8, 13.9, 16.5.
Already sorted. n = 25. 90th-percentile position: ceil(0.9 × 25) = ceil(22.5) = 23. Value at position 23: 12.8 ppb.
Reported 90th-percentile: 12.8 ppb.
Worked example 3: The exceedance case
Samples: 2, 3, 4, 5, 6, 8, 10, 12, 18, 25.
n = 10, ceil(0.9 × 10) = 9. Value at position 9: 18 ppb.
The 90th-percentile is 18 ppb, which is above the 15 ppb pre-LCRI Action Level. This is a Lead Action Level Exceedance, which triggers:
- Mandatory corrosion control study (if not already in place)
- Public education per § 141.85
- Potential lead service line replacement requirements
- Mandatory disclosure in the CCR using Appendix A language
What tools get wrong
We tested four CCR tools during diligence. Two of them computed the average of the samples above the 90th-percentile point instead of the sample value at the 90th-percentile position — a distinctly different number. One of them interpolated between samples at non-integer positions. One returned an off-by-one error when n was not a multiple of 10.
The correct implementation is three lines of code:
function ninetiethPercentile(samples: number[]): number {
const sorted = [...samples].sort((a, b) => a - b);
const position = Math.ceil(0.9 * sorted.length);
return sorted[position - 1];
}
We unit-test this function against a fixture set derived from § 141.80(c)(3) worked examples in EPA guidance. It's a pure function; the LLM never touches it.
The provenance audit
When 1water.ai computes a 90th-percentile for your CCR, the audit trail records:
- Every sample, with its lab PDF source, page, row, and extracted value
- The unit normalization applied
- The sorted sample list
- The 90th-percentile position formula (ceil(0.9 × n))
- The selected sample and its value
- The comparison against the applicable Action Level
When a state reviewer asks, "show me the math behind this number," you can show them the full trail. That's the bar we set.