Site Background Concentrations
Background Concentration Formula Guidance
This document explains how TypeScript-based formulas can be used to calculate background concentration values dynamically. It covers the purpose of background concentration formulas, the calculation workflow, available utility methods, common use cases, fallback strategies, validation checks, and implementation guidance for reliable background calculations.
The Background Concentration Formula feature allows you to create custom TypeScript functions that dynamically calculate the background concentration for a chemical analyte. Instead of storing a fixed background value, you can define logic that computes the appropriate concentration from nominated reference locations, historical results, selected percentiles, non-detect handling rules, and qualifier exclusions.
Pre-Requisites
Formulas are written in TypeScript (a form of JavaScript). You should become familiar with TypeScript prior to attempting to create your own formulas. ESdat Support offers a fee-based service to create formulas for organizations that don't have this capability.
How It Works
- You write a calculate() function that receives a CalculationContext containing the current sample and background concentration being evaluated.
- Your function uses utility methods to retrieve historical results for a selected location, calculate percentiles, and apply non-detect or qualifier handling rules.
- Your function returns a BackgroundCalculationResult with the calculated concentration, unit, and description.
Why Use Background Concentration Formulas?
| Scenario | Without Formula | With Formula |
|---|---|---|
| Percentile-based baseline | Calculate percentiles manually from exported historical results | Formula calculates the selected percentile from qualifying historical results |
| Average background concentration | Manually average recent results and update the background value | Formula retrieves recent qualifying results and calculates the average automatically |
| Site-specific threshold | Maintain separate static background values for each location or reference area | Formula compares the current sample against data from the nominated reference location |
| Non-detect and qualifier handling | Apply exclusions or substitutions manually outside the system | Formula applies configured non-detect and qualifier rules consistently during calculation |
Key Concepts
Data Retrieval
Background concentration formulas retrieve historical chemistry data through utility methods on the Utils class. The main retrieval methods support recent-result lookups, percentiles, selected reference locations, total-or-filtered basis, matrix type, non-detect handling, and qualifier exclusions.
- getNPreviousResultsForLocation() — retrieves qualifying historical result values for a selected location and chemical code.
- getNPreviousPercentileResultsForLocation() — calculates a percentile from qualifying historical results using the same lookup criteria.
Result Structure
Each background concentration formula returns a result object that communicates both the calculated value and how it was derived.
- Concentration — the calculated background concentration, or null if a value cannot be calculated.
- Unit — the unit of measurement returned with the calculated concentration.
- Description — a human-readable explanation of the calculation, assumptions, exclusions, and fallback handling.
- Options — controls whether non-detects are excluded or substituted and which qualifiers are excluded.
Best Practices
- Specify the reference location explicitly rather than assuming the current sample location should be used.
- Handle missing or insufficient historical data gracefully by returning null with a clear description.
- Document how non-detects and qualifiers are handled, especially when substituting non-detect values or excluding rejected results.
- Use the unit, matrix type, and total-or-filtered basis expected by the background concentration record.
- Keep formulas simple, readable, and easy to review by using descriptive constants and clear result descriptions.
- Validate formulas against edge cases such as no historical data, few qualifying results, all non-detects, or changed site conditions.
Users define the calculation logic by editing the calculate() function. The system runs this function automatically whenever a background concentration is evaluated.
Utility Function Reference
Utils.getNPreviousResultsForLocation(params)
Retrieves the specified number of most recent qualifying historical result values for a location and chemical code.
Utils.getNPreviousResultsForLocation({ SampleDateTime: Date, LocationCode: string, Well?: string | null, ChemCode: ChemCode, Unit: string, TotalOrFiltered?: 'T' | 'F', MatrixType?: string | null, NumberOfResults: number, Options?: Options }): number[]
Utils.getNPreviousPercentileResultsForLocation(params)
Calculates a percentile from the qualifying historical results returned by the same lookup criteria used by getNPreviousResultsForLocation.
Utils.getNPreviousPercentileResultsForLocation({ SampleDateTime: Date, LocationCode: string, Well?: string | null, ChemCode: ChemCode, Unit: string, TotalOrFiltered?: 'T' | 'F', MatrixType?: string | null, NumberOfResults: number, Percentile: number, Method?: PercentileMethod, Options?: Options }): number | null
Available percentile methods:
'R1' | 'EmpiricalInvCDF' | 'SAS3' | 'R2' | 'EmpiricalInvCDFAverage' | 'SAS5' | 'R3' | 'Nearest' | 'SAS2' | 'R4' | 'California' | 'SAS1' | 'R5' | 'Hazen' | 'Hydrology' | 'R6' | 'Weibull' | 'Nist' | 'SPSS' | 'SAS4' | 'R7' | 'Excel' | 'Mode' | 'S' | 'R8' | 'Median' | 'Default' | 'R9' | 'Normal'
Options Object
Controls how non-detect results and qualifier codes are handled before historical values are returned or used in percentile calculations.
Options: { ExcludeNonDetects?: boolean, ExcludeQualifiers?: string[], NonDetectMultiplier?: number }
Formula Development Guidance
1. Select the Reference Location
Important: The sample's own Location_Code and Well are available in the context, but do not assume they should be used unless you specifically want to compare against that same location's history.
- A nominated reference/background well
- A composite of multiple background locations
- A regulatory standard or fixed value
The user should explicitly specify which location code to use.
2. Handle Non-Detects Appropriately
- Exclude them entirely using ExcludeNonDetects: true.
- Use a substitution multiplier such as NonDetectMultiplier: 0.5 for DL/2.
- ExcludeNonDetects takes precedence over NonDetectMultiplier.
| Method | Multiplier |
|---|---|
| Replace with 0 | Not directly supported |
| Replace with DL/2 | NonDetectMultiplier: 0.5 |
| Replace with DL/√2 | Not directly supported |
3. Filter by Qualifiers
- 'R' — Rejected
- 'J' — Estimated value
- 'UJ' — Estimated non-detect
Multiple qualifiers on a single result are semicolon-delimited, such as "J;R".
4. Choose an Appropriate Sample Count
- Too few samples may not be statistically meaningful.
- Too many samples may include data from different site conditions.
- Typical range: 10–20 samples.
- Consider seasonality.
- Ensure sufficient older data exists.
5. Unit Conversion Awareness
- Always specify the correct Unit.
- Always use the unit expected by the background concentration record.
- Common units: mg/L, µg/L, mg/kg, µg/kg.
6. Keep Formulas Simple
- Simple percentile calculations.
- One utility call when possible.
- Descriptive variable names.
7. Check for Null Results Before Returning a Calculated Concentration
if (concentration != null) { return { Concentration: concentration, Unit: UNIT, Description: '...' } } return { Concentration: null, Unit: UNIT, Description: 'No historical data available' }
8. Common Patterns and Recipes
Fixed Value Background
return { Concentration: 0.5, Unit: 'mg/L', Description: 'Fixed background concentration' }
Simple Average of Last N Results
Use getNPreviousResultsForLocation and calculate the average manually.
Percentile With Exclusions
Utils.getNPreviousPercentileResultsForLocation({ Options: { ExcludeNonDetects: true, ExcludeQualifiers: ['R', 'J'] } })
9. Validation Checklist
- Handle cases where no historical data exists.
- Consider very few results.
- Verify filtered and total results.
- Verify different matrix types.
Best Practices Checklist
Use this checklist after selecting the utility method and calculation approach.
1. Use Descriptive Constants at the Top
const UNIT = 'mg/L' const SAMPLES = 15 const PERCENTILE = 95 const REFERENCE_LOCATION = 'BG-01'
2. Exclude Rejected Data
Options: { ExcludeQualifiers: ['R'] }
3. Document Non-Detect Handling
Options: { NonDetectMultiplier: 0.5, ExcludeQualifiers: ['R'] }
4. Validate Before Using Returned Values
const concentration = Utils.getNPreviousPercentileResultsForLocation({}) if (concentration == null) { return { Concentration: null, Unit: UNIT, Description: 'Insufficient historical data for calculation' } }
5. Use Meaningful Descriptions
Description: `95th percentile of last ${SAMPLES} results at ${REFERENCE_LOCATION}` Description: `80th percentile at ${REFERENCE_LOCATION} (non-detects replaced with DL/2)`
6. Choose the Right Percentile Method
| Scenario | Recommended Method |
|---|---|
| General environmental data | R8 |
| Regulatory compliance (Excel-compatible) | Excel |
| Hydrological/water quality data | Hazen or Hydrology |
7. Consider Sample-Size Requirements
- Minimum of 10 samples.
- 15-20+ samples preferred.
- Fewer than 5 samples may require an alternative approach.
8. Match Units to Historical Data
Always use the unit that matches your historical results.
9. Keep Formulas Simple
- One utility call when possible.
- Simple arithmetic.
- Clear linear logic.
10. Be Explicit About Limitations
Document limitations and handle known constraints gracefully.
11. Consider Matrix and Basis
Use Matrix_Type and Total_or_Filtered to ensure comparable results.
12. Test Edge Cases
- No historical data exists.
- All historical results are non-detects.
- Location has changed over time.
- Chemical is not commonly detected.
Write formulas that handle these gracefully rather than crashing or returning misleading values.