Complex Environmental Standards Formulas

Environmental Standards Formula Guidance

This document explains how TypeScript-based formulas can be used to calculate environmental standard action levels dynamically. It covers the purpose of formulas, the calculation workflow, available utility methods, reusable formula patterns, fallback strategies, validation checks, and common implementation pitfalls.

The Formula feature allows you to create custom TypeScript functions that dynamically calculate environmental standard action levels (threshold values). Instead of storing a fixed value like "5 mg/L", you can define logic that computes the appropriate limit based on site-specific conditions, water chemistry, or historical data.

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 sample details and the environmental standard being evaluated.
  • Your function uses utility methods to pull data from the system: background concentrations, other sample results, historical records, pH, etc.
  • Your function returns an ActionLevelResult with the computed value, units, and a description.
  • The system calls your formula every time a sample is evaluated against that environmental standard.

Why Use Formulas?

ScenarioWithout FormulaWith Formula
pH-dependent limitsStore many static values for each pH rangeOne formula that calculates the correct limit
Site-specific backgroundManually update thresholdsFormula reads background automatically
Hardness-dependent metalsHard-code lookup tablesFormula calculates from actual hardness
Consecutive exceedance rulesManual review requiredFormula detects exceedance patterns
Statistical thresholdsStatic percentile valuesFormula computes from historical data

Key Concepts

Data Retrieval

Formulas can pull data from the system using these methods:

  • getBackgroundConcentration() - site background values
  • getSampleResult() - other analytes in the same sample
  • getPH() - resolved pH from field, lab, or default
  • getPercentileResultsForLocation() - historical statistics
  • checkConsecutiveExceedances() - trend/pattern detection
  • getPreviousResultsForLocation() - raw historical results

Result Structure

Each formula returns:

  • ActionLevel - the calculated threshold (or null if incalculable)
  • Units - the unit of measurement
  • Description - human-readable explanation
  • Options: ActionLevelMin, ActionLevelPrefix, ActionLevelSuffix

Best Practices

  • Handle missing data gracefully and return null with a clear description.
  • Use the most conservative available value when data is unavailable.
  • Provide detailed descriptions so users understand how the value was derived.
  • Clamp values to valid ranges when equations have applicability limits.
  • Minimize unnecessary API calls and only fetch data when needed.

Utility Function Reference

The following section lists the available utility functions on the Utils class. Each entry includes a short description, expected parameters, and the return type.

Utils.getBackgroundConcentration(params)

Retrieves the site background concentration for a ChemCode.

Utils.getBackgroundConcentration({  ChemCode: ChemCode,  Unit: string,  TotalOrFiltered?: 'T' | 'F',  MatrixType?: string,  FallbackToSampleResult?: boolean }): number | null

Utils.getSampleResult(params)

Gets a result value from the same chemistry sample for a different ChemCode.

Utils.getSampleResult({  ChemCode: ChemCode,  Unit: string,  TotalOrFiltered?: 'T' | 'F',  Options?: ChemistryResultLookupOptions }): number | null

Utils.getPH(params)

Resolves pH for the current sample using a priority order.

Utils.getPH(): { pH: number | null; pHSource: string } Utils.getPH({ FallbackValue: number }): { pH: number; pHSource: string }

Utils.getPercentileResultsForLocation(params)

Gets a percentile value from historical chemistry results for a location.

Utils.getPercentileResultsForLocation({  LocationCode: string,  Well?: string | null,  ChemCode: ChemCode,  Unit: string,  TotalOrFiltered?: 'T' | 'F',  MatrixType?: string | null,  Percentile: number,  Method?: PercentileMethod | null,  PriorSamplesOnly?: boolean,  MaxResultCount?: number,  ExcludeNonDetects?: boolean,  ExcludeQualifiers?: string[],  NonDetectMultiplier?: number }): 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

Utils.getPreviousResultsForLocation(params)

Gets prior chemistry results at the current sample location, ordered newest-first.

Utils.getPreviousResultsForLocation({  ChemCode: ChemCode,  Unit: string,  MinDate?: Date | null,  MaxDate?: Date | null,  NumberOfResults?: number,  TotalOrFiltered?: 'T' | 'F',  MatrixType?: string | null,  MinDepth?: number | null,  MaxDepth?: number | null,  Options?: HistoricalResultLookupOptions }): PriorLocationResult[]

Utils.checkConsecutiveExceedances(params)

Returns true when the current sample and prior results form a consecutive exceedance streak.

Utils.checkConsecutiveExceedances({  ChemCode: ChemCode,  Unit: string,  Threshold: number,  ConsecutiveCount: number,  GroupBy?: 'Sample' | 'Month' | 'Quarter',  TotalOrFiltered?: 'T' | 'F',  MatrixType?: string | null,  Options?: HistoricalResultLookupOptions }): boolean

Formula Development Guidance

1. Common Patterns and Recipes

Missing Data Handling:

if (value == null) {  return {    ActionLevel: null,    Units: 'mg/L',    Description: 'Required data not available'  }; } const doc =  Utils.getSampleResult({    ChemCode: 'DOC',    Unit: 'mg/L'  }) ?? 0.5;

Hardness-Dependent Metal Equation (Copper example):

const actionLevel =  Math.exp(0.8545 * Math.log(hardness) - 1.465);

pH-Dependent Ammonia Calculation:

const { pH } = Utils.getPH({ FallbackValue: 7.0 }); const fraction =  1 / (1 + 10^(pH - 9.25));

2. Data Priority and Fallback Strategies

Data SourcePriorityWhen to Use
Site backgroundHighestFor naturally occurring substances
Current sample resultMediumWhen background is unavailable
Historical percentileMediumFor statistical thresholds
Hard-coded defaultLowestLast resort fallback

3. Unit Conversion Awareness

  • All utility methods require explicit Unit parameters.
  • Results may fail to convert if there is no conversion path between units.
  • Always use the same unit expected by the environmental standard.
  • Common units: mg/L, µg/L, mg/kg, and "-" for pH or conductivity.

4. Qualifier Handling Best Practices

const QUALIFIERS_TO_EXCLUDE = ['R', 'J', 'UJ', 'B']; Utils.getSampleResult({  ChemCode: 'SomeChemCode',  Unit: 'mg/L',  Options: {    ExcludeQualifiers: ['R', 'J']  } });

5. Performance Considerations

  • Minimize API calls.
  • Use lazy evaluation.
  • checkConsecutiveExceedances() is more efficient than manual iteration.
  • Each getSampleResult() call is a separate query.

6. Debugging Tips

  • Use the Description field to show intermediate values.
  • The pHSource value explains where pH originated.
  • When clamping, document original and adjusted values.
  • Test missing data, extreme values, and edge cases.

7. Validation Checklist

  • Handle all possible null returns from Utils methods.
  • Verify values are within equation applicability ranges.
  • Ensure units are consistent.
  • Verify fallback logic produces reasonable results.
  • Provide a clear description.
  • Confirm equation coefficients match the published standard.

8. Common Pitfalls

PitfallSolution
Using chemical name instead of ChemCodeAlways use the exact ChemCode string.
Forgetting FallbackToSampleResult: trueBackground values will not fall back automatically.
Not clamping valuesEquations may produce invalid results outside calibrated ranges.
Hard-coding numeric constants without explanationAdd comments showing the source of constants.
Complex logic in a single functionBreak logic into well-named helper variables.