The failure pattern
Among CCR submissions our advisors have reviewed, the single most common rejection cause is a unit inconsistency: a contaminant value reported in parts per billion when the MCL is stated in milligrams per liter, or vice versa.
It happens because lab PDFs often report values in the unit native to the instrument, which may not match the unit the rule requires for the CCR. A drinking water analyst sees "0.010" in a lab report and types "0.010 mg/L" — but the row header read "µg/L." That sample just got 10× worse than it actually was.
The regulatory requirement
40 CFR 141.2 defines ppm, ppb, ppt, and ppq. Appendix A to Subpart O specifies the units each regulated contaminant must be reported in within the CCR. For most inorganic contaminants, the units are mg/L or ppm. For most organic contaminants, the units are µg/L or ppb. The conversions are not arbitrary:
- 1 ppm = 1 mg/L = 1,000 ppb = 1,000 µg/L
- 1 ppb = 1 µg/L = 0.001 mg/L
A contaminant with a federal MCL of 0.010 mg/L has an equivalent MCL of 10 ppb. Reporting the level as "0.010 ppb" is off by a factor of 1,000.
Three real-world patterns
Pattern 1: Lab PDF in µg/L, CCR template in mg/L
Most common for arsenic, chromium, and the organic contaminants. The lab reports arsenic at "8.2 µg/L"; the CCR template expects mg/L; the operator types "8.2 mg/L" thinking they're just copying the value. The published CCR shows arsenic at 8,200 ppb — above the MCL of 10 ppb by a factor of 820.
Pattern 2: Nitrate as N vs as NO₃
Nitrate can be reported as nitrate-nitrogen (NO₃-N) or as nitrate (NO₃). The federal MCL is 10 mg/L as N, equivalent to 44.3 mg/L as NO₃. A lab result of "40 mg/L NO₃" is fine (9.0 mg/L N, below MCL), but if someone sees 40 mg/L and assumes it's expressed as N, they'll flag it as an exceedance and potentially trigger a violation disclosure that isn't warranted.
Pattern 3: Combined lead + copper in one row
Some older templates combine lead and copper into a shared "Lead and Copper" row with shared units. Lead is typically reported in ppb (MCL Action Level = 15 ppb for the pre-LCRI period, 10 ppb for LCRI-triggered utilities); copper is typically reported in mg/L (Action Level = 1.3 mg/L). Combining them in a single row with a single unit silently gets at least one of them wrong.
The architectural fix
Normalize every sample to a canonical unit before any comparison, calculation, or display. All samples flow through a single normalization function that:
- Parses the source value and unit from the lab record.
- Converts to the canonical unit for that contaminant (per Appendix A).
- Logs the original value, the conversion factor, and the normalized value so the audit trail shows the math.
Once the sample is normalized, MCL comparisons, percentile calculations, and CCR table rendering all operate on canonical units. There is no second place where units matter. The conversion happens once, is auditable, and never happens again.
1water.ai ships this as a pure function called normalize_samples. It's
unit-tested independently of the LLM; the agent never attempts to
convert units on its own.
What this means for your review
When you review your CCR draft, the fastest check you can do is:
- Pull up Appendix A for each contaminant in your table.
- Confirm the unit in your row matches Appendix A.
- Spot-check two or three values against the original lab PDF — not the lab summary, the actual row from the source document.
If you find one unit error, you have at least a 50% chance of having another. Review the whole table.
Start your CCR on a tool that doesn't make this error
Start a free 60-day trial. Every value in the contaminant table carries cell-level provenance — you can click and see the source PDF, page, row, and extracted value. Units are normalized before the table renders.