# 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`](/format/spec/v2/object-schemas#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](/format/spec/v2/overview#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](/format/spec/v2/object-schemas#corrections-since-v23)). Every function on this page already does that. If you iterate the events yourself, start from the effective list:

```ts
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

```ts
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](/format/spec/v2/format-reference#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](/format/spec/v2/format-reference#level-sign-convention)).

## Drawdown and specific capacity

```ts
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](/format/spec/v2/object-schemas#derived-parameters--computation-reference).

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:

```ts
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:

| Function                        | Returns                                                                                                                                 |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `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.                                                                |

```ts
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

```ts
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`](/format/spec/v2/object-schemas#logger_series-since-v24)). 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](/utils/guides/logger-import).
