Guides

Water levels and pumping tests

Read the current static and dynamic levels, compute drawdown and specific capacity, prepare pumping test plots and respect ledger corrections with @welldot/utils.

Water levels and pumping tests live in hydrodynamic_events, a ledger of field records (hydrodynamic_events). The file stores what was measured: levels, rates, elapsed times. Drawdown, specific capacity and the plots are computed from those readings, never stored (design principles).

Corrections come first

An event can retract an earlier one with corrects. A retracted event stays in the file but must be left out of every derivation (corrections). Every function on this page already does that. If you iterate the events yourself, start from the effective list:

import {
  getEffectiveHydrodynamicEvents,
  getRetractedEventIds,
} from '@welldot/utils';

const events = getEffectiveHydrodynamicEvents(well); // every event not retracted
const retracted = getRetractedEventIds(well); // Set of retracted ids, chains included

Current levels

import {
  getLatestPumpingDynamicLevel,
  getLatestStaticLevel,
  isFlowingArtesian,
} from '@welldot/utils';

getLatestStaticLevel(well); // 3.94: latest static level, by UTC instant
getLatestPumpingDynamicLevel(well); // 52.75: latest level measured while pumping
isFlowingArtesian(well); // true when the latest static level is above ground
  • getLatestStaticLevel compares events by absolute instant, not by their position in the array, as the spec requires (ordering and ties).
  • getLatestPumpingDynamicLevel skips air-lift events and falls back to a newer aquifer_analysis[].dynamic_level.
  • A negative level is above ground. isFlowingArtesian is the v2.1 way to detect a flowing well, replacing the deprecated well_type: "artesian" (level sign convention).

Drawdown and specific capacity

import {
  calculateDrawdown,
  calculateSpecificCapacity,
  calculateUnitDrawdown,
  lastReading,
} from '@welldot/utils';

const test = events.find(e => e.type === 'constant_rate');
if (test?.type === 'constant_rate' && test.static_level !== undefined) {
  const end = lastReading(test); // { elapsed: 1440, depth: 52.75 }
  const rate = test.steps?.[0]?.rate; // m³/h
  if (end && rate) {
    const s = calculateDrawdown(end.depth, test.static_level); // 50.75 m
    calculateSpecificCapacity(rate, s); // 0.29 m²/h (Q/s)
    calculateUnitDrawdown(s, rate); // 3.44 h/m² (s/Q)
  }
}

calculateSpecificCapacity and calculateUnitDrawdown throw a RangeError on a zero denominator instead of returning Infinity. The spec lists the formulas these follow in Derived parameters.

Interpreted values (transmissivity, storativity, Jacob coefficients) are not computed here; they are the analyst's result, stored in aquifer_analysis. Read the latest one with:

import { getLatestAquiferAnalysisField } from '@welldot/utils';

getLatestAquiferAnalysisField(well, 'transmissivity'); // m²/s, from the newest analysis that has it

And use them with calculateFormationLoss(jacobB, Q) (B·Q), calculateWellLoss(jacobC, Q) (C·Q²) and calculateHydraulicConductivity(T, thickness).

Plotting a test

The profiler's test charts are built from these series, and you can feed them to any chart library:

FunctionReturns
availablePumpingCharts(event)Which plots the event has data for: hydrograph, semilog, recovery, stepTest.
pumpingTimeline(event)Every step and recovery reading on one test clock, with drawdown from the static level: { staticLevel, steps, pumpingEnd, recovery }.
semilogDrawdownSeries(event)Drawdown against log time per step, for a Cooper-Jacob plot.
theisRecoverySeries(event)Residual drawdown against t/t′, for a Theis recovery plot.
stepTestSeries(event)Drawdown, s/Q and Q/s at the end of each step, for a step-drawdown plot.
import { availablePumpingCharts, semilogDrawdownSeries } from '@welldot/utils';

availablePumpingCharts(test); // ['hydrograph', 'semilog', 'recovery']
semilogDrawdownSeries(test)[0];
// { step: 0, rate: 14.76, points: [{ elapsed: 1, drawdown: 27.91 }, …] }

Smaller helpers for tables: allStepReadings, stepRate, recoveryReadingsCount, stepHasReadings, derivedStepDuration.

Pumps and levels together

import {
  calculateSubmergence,
  getCurrentPump,
  getPumpInstalledAt,
} from '@welldot/utils';

getCurrentPump(well)?.intake_depth; // the installation without removed_at
getPumpInstalledAt(well, '2019-05-20T08:00:00-03:00'); // the pump in place at that instant
calculateSubmergence(well); // intake depth minus dynamic level; negative = intake above water

Data logger series

logger_series events hold continuous sensor readings (logger_series). They are neither tests nor static levels, so the functions above ignore them as sources of static or dynamic level. To import a logger export, see Logger import.