Event detection — V8.1 algorithm
The EventDetector (algorithm version V8.1)
implements a fully automatic, multi-stage pipeline that classifies every
sample of a gauge record as one of three labels:
|
Producing period (pressure dropping toward flowing bottom-hole) |
|
Shut-in period (pressure recovering toward \(P_{\text{res}}\)) |
|
Edge effects (RIH/POOH), pauses, transients not suitable for PTA |
The algorithm has been validated on Rhourde Nouss (Algeria) DSTs and offshore Qatar North Field tests.
Pipeline overview
Phase |
Step |
Purpose |
|---|---|---|
0 |
Hampel-filter despike → Savitzky–Golay smoothing → noise floor \(\hat\sigma\) |
Suppress single-sample spikes; obtain smoothed pressure and its time derivative; estimate the per-test noise level. |
1 |
Reservoir-pressure plateau detection |
Identify the most stable, highest-pressure region and report it as \(P_{\text{res}}\). |
2 |
RIH / POOH edge masking |
Trim away gauge-going-into-hole and out-of-hole transients. |
3 |
Spike-boundary + turning-point detection (validated on \(\pm 5\hat\sigma\)) |
Find candidate event boundaries from large derivative excursions; verify each by sustained pressure change. |
4 |
Zone classification using net-\(\Delta P\) signed logic |
Label each zone DD / BU / pause according to the sign and magnitude of net pressure change. |
5 |
Pause absorption → same-type merge → edge trimming |
Clean up tiny gaps, merge adjacent same-type zones, drop leading/trailing artefacts. |
5b |
V8.1: Post-plateau tail trim (H→I→J spike before POOH) |
Detect and remove the characteristic “spike before pull” artefact at the tail of long buildups. |
Phase 0 — Smoothing & noise floor
A vectorised Hampel filter (rolling median ± 3 ⋅ MAD by default) removes single-sample outliers without smearing the signal:
where \(m_i\) is the local median, \(\sigma_i = 1.4826\,\text{MAD}_i\),
and \(k = 3\) (configurable via hampel_sigma).
The despiked series is then Savitzky–Golay smoothed with an adaptive window (default \(\sim 0.5\%\) of the series length, polynomial order 3) to obtain \(p_{\text{smooth}}\) and its first derivative \(dp/dt\).
The noise floor is
and is the unit of all subsequent thresholds.
Phase 1 — Reservoir pressure
The reservoir pressure \(P_{\text{res}}\) is estimated as the mean
of the highest stable plateau in \(p_{\text{smooth}}\). A plateau is a
contiguous region of \(\geq 30\) points (default p_res_min_pts)
where \(|dp/dt|\) is below the 20th percentile of all
\(|dp/dt|\) values.
If no plateau is found the 95th percentile of \(p_{\text{smooth}}\) is used as a fallback.
Phase 2 — Edge masking
Two pointers — pta_start and pta_end — bracket the portion of
the record where the pressure is “near” \(P_{\text{res}}\). The
threshold defaults to \(0.85\,P_{\text{res}}\) (or \(0.70\) for
low-pressure shallow tests). Anything outside this window is forced to
non_pta.
Phase 3 — Boundary detection
Candidate boundaries are unioned from two independent detectors:
dp/dt spikes — points where \(|dp/dt|\) exceeds the 95th percentile (configurable via
spike_percentile).Pressure turning points — sign changes of \(d^2p/dt^2\) on a heavily smoothed copy of the signal, retained only if the prominence exceeds \(10\,\hat\sigma\).
Each candidate is then validated: the median pressure in \([\,b - W,\,b\,)\) and \([\,b,\,b + W\,)\) must differ by \(> 5\,\hat\sigma\). Otherwise the candidate is dropped as noise.
Phase 4 — Zone classification
The validated boundaries partition [pta_start, pta_end] into zones.
For each zone the signed net pressure change
is computed (over 5-point windows on each side). The zone is labelled:
Condition |
Label |
Notes |
|---|---|---|
\(|\Delta p_{\text{net}}| < \max(15,\,5\hat\sigma)\) or duration < 6 min |
|
Will be absorbed in Phase 5 |
\(\Delta p_{\text{net}} < 0\) |
|
|
\(\Delta p_{\text{net}} > 0\) |
|
Phase 5 — Cleanup
Three passes run in sequence:
Pause absorption — every
pausezone is merged into its larger PTA neighbour (or split-merged if neighbours are different types).Same-type merge — adjacent zones with identical labels are joined; tiny non-PTA gaps (\(< 20\) pts) between same-type events are absorbed.
Edge trimming — leading buildups that start above \(0.80\,P_{\text{res}}\) (RIH artefact) and trailing drawdowns followed only by
non_pta(POOH artefact) are demoted to non-PTA.
The whole process iterates until the label vector is stable.
Phase 5b — Tail trim (V8.1)
Long buildups often exhibit a characteristic late-time spike just before the gauge is pulled. The V8.1 tail-trim heuristic targets this artefact:
For each buildup with duration \(\geq 4\) h and \(\geq 200\) pts:
Estimate the dominant pressure level \(p_{\text{plateau}}\) from the mode of a 60-bin histogram.
Compute the on-plateau coverage on the late half of the event: \(\text{cov}_{\text{late}} = \text{frac}(|p - p_{\text{plateau}}| < 4\hat\sigma)\). Skip if \(< 30\%\).
Find the last sample on plateau; require that this sample lies in the second \(40\%\) of the event (otherwise it’s not a tail-spike but a real recovery).
Compute the tail deviation \(\delta_{\text{tail}} = \max|p_{\text{tail}} - p_{\text{plateau}}|\).
If \(\delta_{\text{tail}} > 8\hat\sigma\) and the tail duration \(> 0.30\) h, demote the tail to non-PTA.
This recovers a clean buildup whose Bourdet derivative is no longer contaminated by the pull-induced spike.
Configuration
All thresholds live in EventDetectorConfig:
from welltest_pta import EventDetectorConfig, WellTest
cfg = EventDetectorConfig(
hampel_sigma=3.0,
spike_percentile=95.0,
min_pta_dp_psi=15.0,
min_pta_duration_hr=0.10,
tail_trim_enabled=True,
tail_trim_min_dur_hr=4.0,
tail_trim_dev_n_sigma=8.0,
)
wt = WellTest.from_file("DST.txt", cfg=cfg)
For the full list of fields see EventDetectorConfig.
When to override the auto-detector
V8.1 is robust on standard DSTs but can struggle with:
Tests with very short drawdowns (\(< 6\) min) — increase
min_pta_duration_hr.Multiphase wells where the plateau pressure drifts slowly — relax
tail_trim_min_plateau_frac.High-noise mechanical gauges — increase
hampel_sigmato 4–5 andspike_percentileto 97–98.
Always check the cross-validation score (see Cross-validation) before
trusting auto-detection on a critical test. If the score drops below
60/100, fall back to manual splitting via
split_manual().