WellSim Manual
Nodal analysis, minimum connected reserves, and production forecasting for oil & gas wells — a web port of the field-proven M. El-Ashry Excel toolset, with the macros replaced by deterministic solvers and every formula pinned against the original workbook cells.
What’s built
| Module | Oil | Gas |
|---|---|---|
| 1 — Well model (nodal analysis) | Natural ✓ · Gas lift ✓ · ESP full stack ✓ (123 pumps in 4 catalogues, matching, design proof) · Water Well tab ✓ (producer + injector) | Natural ✓ |
| 2 — Reserve estimate | ✓ Havlena–Odeh MB · static-gauge MB · reservoir limit | ✓ p/Z prod data · p/Z from SITHP · reservoir limit · p/Z from memory gauges |
| 3 — Forecast | ✓ Tarner · Walsh generalized-MB (Rv) — saturation-tracking MB + nodal | ✓ p/Z tank + nodal, history overlay |
| 4 — Lift selection (artificial lift) | ✓ ESP / Gas lift / Sucker rod / Jet / PCP — life-of-well envelope screen + UDC economics | — |
Project-wide principles: Darcy IPR is the default and dominant J for both fluids (the test J calibrates a matched permeability at your judged skin); two pressure records (Pri immutable anchor, Pr working); explicit physics only — Brill & Beggs Z everywhere, no goal-seek; well-head temperature always calculated (Ramey); gas impurities are inputs wherever gas properties appear.
Run & test
node src/server/server.js # serves http://localhost:3355 npm test # 356 tests node scripts/validation-sweep.mjs # 43 module-vs-workbook sensitivity cases
The server uses only Node built-ins; the UI is plain HTML/JS (Plotly for charts). Documentation lives in the repo: README.md, docs/user-guide.md, docs/equations.md, docs/deploy.md. A standalone portable build (WellSim.exe, no install, cases saved beside the exe) is produced from this same source tree by build.ps1.
UI conventions
- Selection modules: where the workbook had parallel sheets, one radio selection is active and the rest hide (lift type, IPR basis, module, reserve pressure-source).
- Side tables beside each chart hold the plotted numbers with a Copy button (tab-separated, pastes into Excel).
- Your inputs survive a refresh. Everything typed is autosaved in the browser and restored on the next load, and the first solve runs on the restored case rather than the demo. Reset (header) forgets it and reloads the demo well. The store is per-browser and never leaves your machine — Save as / Open remain the way to move a case between machines. WellSim is also an installable PWA: the UI, charts and manual are cached so it opens offline, while calculations are never cached, since a stale reservoir answer is worse than none.
- Charts size themselves to the space they are in. Each is measured after it draws and corrected: the canvas to its container (so it never laps the table beside it), the title to the canvas (shrunk, then wrapped rather than made unreadable), and the legend clear of the x-axis title. They re-fit on font load, page load, resize and rotate, so a reload settles straight into its final layout. Result rows follow the active selection — the ESP Sensitivity view replaces the model-match charts and leaving it restores them; rows belonging to another lift are cleared rather than left showing the previous run.
- There is no current-rate input. The well model SOLVES for the rate — the operating point is the IPR/VLP intersection — so a typed rate would either be ignored or quietly contradict the answer. Rates are entered only where they are genuine data: the calibration test rate, the prod-data table, and the ESP/gas-lift matches.
- Production tables accept clipboard paste (button or Ctrl+V in a cell); column order
Date, FTHP, rate, CGR|GOR, WGR|WC, Pwf; header lines auto-skip; up to 200 rows. - Dates:
17-Nov-14,17-Nov-2014 13:00:00,05/03/2014 06:30, ISO, or a day serial. Impossible dates are rejected with the row number — nothing silently rolls over. dt = days from the first row; time-of-day honoured. - Header bar: the Oil Well | Water Well | Gas Well tabs, then Save as · Open · Help. A server-case link appears only when the deployment reports a supported case store.
- Save as / Open: the whole case — every input, selection and production table across all three tabs — saves to a JSON file and restores in one click; program-filled grey cells stay program-filled. Both are local to your machine and never touch the server. Save as opens your browser’s Save dialog, so you choose the folder and the file name, and Open starts in that same folder next time. On a phone there is no such dialog in any browser, so Save as opens the system share sheet instead — choose Save to Files (iOS) or Files/Drive (Android) and you still pick the folder. Where neither exists (Firefox, older Safari) the case is still saved locally, through a download, to wherever that browser files downloads. Open on a phone uses the system document picker, so you browse to the file wherever you put it.
- Printing: there is no header button — use the browser’s own print command. The print stylesheet still keeps the results only — charts, tables, summary cards and banners on a clean white page (inputs, navigation and buttons hide); the dialog’s “Save as PDF” makes the shareable report.
- Server cases: the legacy JSON company store is disabled by default while the secure organization/membership store is built. A typed company name is not proof of membership. When explicitly enabled for controlled migration, registration is invite-only. The portable program uses the same panel for its local
cases/folder. - Visitor use stays complete: every calculation, browser autosave, Save as and Open workflow works without an account.
- Mobile: below 640 px the layout stacks and the action button stays pinned.
Oil — Well model
Lift types: Natural flow · Gas lift · ESP (one active).
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 700 psi · WC 50% · GOR 5000 scf/stb · ID 2.992 in · roughness 0.00006 |
| Trajectory | top perf 2810 mAH · kick-off 1910 m · deviation 7° |
| Fluids & PVT | 46 API · γg 0.842 · Rsi 700 scf/stb · Tres 201°F · μ tubing 6 cp · water SG 1.05 · Pb blank = calc |
| Heat transfer | soil 90°F · U 3 BTU/hr·ft²·°F · OD 3.5 in · Cp 0.51 |
| IPR (Darcy) | Pri 3550 · K 50 mD · H 42.653 ft · Re 1640.5 · Rw 0.5104 · skin 0 |
| Test | 2100 stb/d @ 700 psi · Pwf blank = get Pwf |
| Gas lift | injection 2490.92 mTVD · sweep to 2 MMscf/d in 10 steps |
| ESP | pump 2993.08 mAH (= 2985 mTVD on the demo trajectory) · ΔP 1325.16 psi (manual) · tubing gas blank = formation · the ESP demo case sets the pump at 2985 mAH (back on natural/gas lift) |
Workflow
- Solve well — IPR + VLP, operating point (rate, Pwf, calculated WHT), AOF, well-head PQ curve with WHT on the second axis.
- Calibrate from test — get Pwf marches the test Pwf from FTHP if blank; the Jones/test J is computed Pri-anchored; you judge skin (guidance table by drilling/completion method) and the program back-solves the matched K so J(Darcy) = J(test).
- Match head factor from test (under Match factors, all three tabs) — the Calibrate button matches the IPR; this one matches the march. It solves the Matching head so the marched pressure meets a typed measurement, with Matching friction held where you set it (1 = untouched); both are bounded to 0.8–1.2. Natural, gas-lift and water wells match the marched Pwf to the typed Test Pwf (a grey get-Pwf value is not a measurement); ESP wells match the marched discharge pressure to the measured Pdis and report Pint as a consistency check (Pint is Pdis minus the pump dP, so a large Δ points at the pump or its wear); gas wells use the first test row. A target the bounds cannot reach is pinned at the bound and flagged — “check friction, PVT or the test rate” — never silently accepted. Missing inputs are named. The matched factor is written into the input; re-solve to use it.
- Sensitivities — VLP parameter sets (blank = base) and future pressures (0.75/0.5/0.25 × the current Pr, refilled as you edit Pr or Pri unless you have typed your own) with the future-J chain. The VLP columns follow the fluid and the ACTIVE LIFT TYPE: oil natural
FTHP · GOR · W.C · tubing ID; oil gas lift+ injection gas; oil ESP+ frequency (Hz); water naturalFTHP · tubing ID; water gas lift+ injection gas; water ESP+ frequency; water injectorinjection THP · injected-water temperature · tubing ID. Typed values survive a lift switch. The chart carries the current-Pr IPR (heavy teal) — the curve every solution is solved against — then the future-pressure family. Pri is not drawn: on a depleted well it is history, and a second high curve only competes with the one the answers belong to (the pressure AXIS still tops out at Pri, so runs stay comparable). Each set’s solved NODE is marked with a diamond, and a set that changes the water cut also gets its own thin IPR, because oil rate = gross × (1−W.C) gives it a different curve in rate terms. Every node is therefore a visible intersection of two drawn curves, and the numbers match the nodal-solution table beneath the chart, which every module carries. - Sensitivity pressure axis — the IPR/VLP sensitivity chart tops its pressure axis at Pri on oil, water and gas wells: the inflow side can never exceed the initial reservoir pressure, and the VLP tail that does (high rates the well could never reach) would otherwise stretch the scale and squash the families. The injector keeps an auto axis — injection deliberately pushes ABOVE Pri, so capping it would clip the very curves it exists to show.
- Pump @ solution point — wherever an ESP node is solved (the model match AND every sensitivity case) the pump’s full state is reported in one identical parameter set: pump · stages · frequency (with the curve’s reference frequency) · wear · head total and per stage · ΔP · rate at the pump before and after separation · composite gradient · free gas at intake · separator efficiency · thrust with its down/BEP/up window · hydraulic power · ΔP convergence. In the sensitivity view the sets sit side by side, one column each, so the trade-off reads straight across.
- Every sensitivity set is solved — a table under the chart gives each VLP set its own NODAL SOLUTION against the current IPR (rate, Pwf, WHP, calculated WHT). On ESP wells it also carries the frequency, solved ΔP, head, intake/discharge and thrust per set, and two more charts appear: the pump curve per sensitivity (each set at its own frequency, node starred, over the shared thrust envelope) and the traverse per sensitivity (each set’s top-down march and IPR back-calc with its ΔP step at pump depth). Oil and water alike.
- ESP frequency sensitivity — with a pump selected (oil or water — both tabs read the same four catalogues) the coupled ΔP is re-solved per rate at each frequency (affinity-scaled curve, intake state and all); with Manual ΔP the quoted ΔP is scaled by the affinity law (f/f₀)². Beyond the pump’s flow range the head is zero, so the frequency curves legitimately merge.
- Injector sensitivities — the injector has its own family: available BHIP vs rate per set against the injectivity line at future pressures. Injection water is incompressible with a fixed viscosity, so its temperature moves the BOTTOMHOLE TEMPERATURE, not the pressure — the BHT families are drawn on the second axis.
- Gas-lift performance — injection sweep, optimum, incremental dQ/dInj.
- ESP — the full pump stack: four pump catalogues in the background, 123 pumps, picked in two steps — catalogue, then pump with add-new-pump (custom per-stage curve — its thrust-marker rows are coloured: row 4 blue = down-thrust limit, row 6 gold = BEP, row 8 red = up-thrust limit, the fixed rows the physics reads) and Manual ΔP fallback; affinity scaling 30–60 Hz with the down-thrust/BEP/up-thrust envelope; intake free-gas block with the default-ON gas separator (95%, “Separator required” above 10% free gas, tubing GLR cut by the BE74 fraction); the coupled solve closes ΔP→march→intake→head→ΔP and matches the bottom-up traverse (Pwf from the IPR at CONSTANT Pres) to the top-down one at the intake — PI is the IPR match factor. Match stages (first run, new pump, wear 0) solves the installed stage count with a DESIGN PROOF: all stage-dependent march pressures must stay above the design floor (input, default 300 psi) — the minimum binds at the pump intake, and a match that would starve it is capped at the stage count where intake = floor, and the anchor Pwf is written grey into the test-Pwf cell (the workbook macro’s C22 — overwrite it to anchor the match on a measured value); Match wear (actual Pint/Pdis) applies the wear factor only — the implied PI and matched K are reported as QC, never written to your inputs (on the demo’s actual gauges the QC recovers the workbook’s PI 2.7 as 2.698); Match separator efficiency (actual Pint/Pdis) is the wear match’s sibling — the same measured ΔP solving the OTHER unknown: wear is held at its input (0 = new pump) and the gas-separator efficiency is solved by Brent on 0–100% so the theoretical ΔP at the measured intake state reproduces the measured one (one measured ΔP fixes one unknown — match η at start-up on a new pump, then hold it and match wear over the run life). It draws the η-sweep (pump ΔP vs separation, the measured ΔP as a dashed line, the gas-locked band shaded — the separation below which intake gas runs Qgross@pump off the curve and the pump delivers no head), reports free gas before/after separation, and on success writes the matched efficiency into the separator input so every downstream run uses it. An optional test Pwf measured at the perfs anchors PI and turns the back-march from Pint into a consistency check. Out-of-range cases are named: a measured ΔP above the 100% curve cannot be gas (check stages, frequency or PI); below the 0% curve it is wear, or worse. On the demo gauges at wear 0 it recovers the workbook’s 95% as 95.16%. Two ESP views: Model match with the FINAL charts (IPR vs the coupled ESP-VLP nodal plot, wellhead PQ & WHT, PumpCurve with the results block beside, and the Traverse on its own row with measured markers — also shown in Manual-ΔP mode, where the input ΔP appears as the highlighted step at pump depth); and Sensitivity — future-Pres cases at 0.9 / 0.8 / 0.7 × Pr (shallower than the 0.75/0.5/0.25 used elsewhere, because a pump still has to lift at modest depletion), each FULLY solved: the Darcy future J, then a complete coupled ESP solve. The chart draws the current-Pr IPR (heavy teal), each case’s IPR and the coupled ESP VLP — one curve, since pump, tubing and THP do not change with Pr — so every solved node is a visible crossing marked by a diamond, with the pressure axis topped at Pri as elsewhere. The table lists the ESP state AT that node — rate, Pwf, Pint, Pdis, ΔP, head, Qg@pump, gradient, free gas %, WHT and thrust — which is how you see the pump working harder and the intake falling as the reservoir depletes. ESP cases also appear in the main VLP/IPR sensitivity block (frequency column, coupled solve per set), but that block solves at the CURRENT Pr only.
ESP_DataBase; Borets 2015 (13); SLB REDA 2020 (24); Novomet (17). The three vendor catalogues were recovered from the vector geometry of the vendor curves, so each pump was cross-checked against evidence the tracer never read — Borets against the workbook pumps it shares model names with (~3% on head), SLB and Novomet against each page’s own printed Recommended Operating Range, whose rate axis is calibrated without reading that range so the check stays independent. Pumps that failed the check were left out rather than shipped unproven (Novomet ships 17 of 42 model+design pairs; 11 of the rest are unrecoverable because their axis digits are vector outlines with no numbers left to calibrate against). Manual ΔP and Custom pump are not catalogue entries and stay in the list whichever catalogue is selected. A saved case stores the pump, never the catalogue — the catalogue is derived from the pump name on load, so cases saved before catalogues existed still restore.Water — Well model
Well type: Producer | Injector · producer lift types: Natural flow · Gas lift · ESP.
The oil-tab structure without the Reserve/Forecast modules (lift types, get-Pwf calibration and sensitivities included), running the same modified-Griffith march at its limiting case: API fixed at 10° (SG = 1.000 exactly), w.c. 100 %, GOR = Rsi = 0, Pb = 0. Rates are gross water (bbl/d); the IPR is built in as the pure linear Darcy form (no Vogel curvature) with the Darcy J on water properties (μ = 0.5 cp, Bw = 1); future-pressure IPR sensitivities keep J constant. Defaults: FTHP 200 psi (producer) · 2000 bbl/d · Pri 4800 psi. Head-dominated: it flows naturally only when Pr beats THP + the static water column (≈ 0.446 psi/ft at SG 1.05) — otherwise “no-intersection” is the physics, not an error.
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 200 psi (producer) · injection THP 2000 psi (injector) · ID 2.992 in · roughness 0.00006 |
| Trajectory | top perf 2810 mAH · kick-off 1910 m · deviation 7° |
| Fluids | water SG 1.05 · lift-gas SG 0.842 · Tres 201°F · injection water temp 90°F |
| Heat transfer | soil 90°F · U 3 BTU/hr·ft²·°F · OD 3.5 in · Cp 0.51 |
| IPR | basis Darcy | User PI · Pri 4800 · K 50 mD · H 42.653 ft · Re 1640.5 · Rw 0.5104 · skin 0 · fracture gradient 0.7 psi/ft (injector parting check; blank = off) |
| Test | 2000 bbl/d @ 200 psi · Pwf blank = get Pwf |
| ESP | pump 2993.08 mAH · 145 stages @ 50 Hz · wear 0 · no separator (water is gas-free) · Manual ΔP 1325.16 psi |
Oil — Reserve estimate
Prod data & macro
Per-row Pwf (input-or-march) → Pr by composite-Vogel inversion iterated with J_2 → Havlena–Odeh MB. Headline N = AVERAGE(F/Eo); slope of F vs Eo as cross-check; F–Eo crossplot + N vs Np charts.
Model column (per row): which well model solves that date’s Pwf and which J backs out its Pr. Natural — plain march, Darcy future J. Gas lift — lifted march at the row’s own GL inj (blank = panel), Pr with the typed PI. ESP — coupled pump solve at the row’s own Hz (blank = panel), Pr with the typed PI. User — type both Pwf and Pr. New rows take the active lift type; a change fills down. A row naming a model the panel cannot supply stops with that row named. The Forecast continues the well as the last modelled row.
Static Pres history
Measured memory-gauge pressures typed directly (Date | Pres) — no IPR/VLP anywhere. Np/Gp from the prod-data cumulative. The strongest data when surveys exist.
Reservoir limit
m = −slope(Pwf, t), Ct = Cg·Sg+Co·So+Cw·Sw+Cf, STOIP = q̄/(Ct·m). Defaults Sg 0.1 / So 0.8 / Sw 0.15. Early lower bound.
Gas — Well model
Defaults
| Group | Fields (default) |
|---|---|
| Well & flow | FTHP 1625 psi · CGR 57.436 · WGR 3.846 stb/MMscf · ID 2.992 in · base roughness 0.0021 in |
| Trajectory | top perf 3013 mAH · kick-off 690 m · deviation 23.65° |
| Fluids & PVT | cond. 48.7 API · γg 0.763 · N₂ 1.2% · CO₂ 3% · H₂S 2 ppm · Tres 232°F · μcond 2 cp · σ 30 dyn/cm |
| Reservoir | Pri 3800 psi · Pr blank = Pri |
| IPR (Darcy) | K 5 mD · H 80 ft · Re 1640.5 · Rw 0.5104 · skin 0 |
| Multi-rate test | 2440/5.192 · 2000/10.002 · 1625/14.137 (psi / MMscf/d), Pwf blank = get Pwf |
IPR basis (one active): Darcy Pr² (default, dominant) or C & n (grey = fitted from the test). Calibration fits C&n, computes the test J, and matches Darcy K at your judged skin. Impurities feed the sour pseudo-critical route everywhere.
The reserve survey tables — oil memory-gauge, gas SITHP and gas memory-gauge — take Paste from clipboard, Import CSV, +10 rows and Clear, and grow to the data just like prod_data. Clipboard cells are read in the table’s own input-column order; a first line starting with a letter is treated as a header and skipped (a 17-Nov-14 date is not).
Gas — Reserve estimate
Prod data & macro
Per-row Pwf (input-or-march) → Pr = √(1000q/J + Pwf²) → explicit Z → Gp trapezoid → p/Z vs Gp line → minimum connected GIIP.
Model column (per row): Model — the workbook’s row: Pwf typed or marched from FTHP, Pr backed out of the IPR (a Pr left in the cell is replaced). User — type both Pwf and Pr, as from a build-up gauge on that date; no march, no IPR. Rows are independent — nothing fills down. A User row missing either value stops with that row named. The same rows feed the reservoir limit and the Forecast’s start state.
Pres from SITHP
Static shut-in surveys — Date | STHP | SITHT | CGR | WGR; no rate column, a shut-in row is q = 0 by definition. Pres by the static gas march: gas-head-only stations on the geothermal profile from SITHT at the head to Tres at the perfs, per-station Z on that line — so the shut-in head temperature shapes the whole column, not an average (1.2% vs the workbook case). SITHT blank = the well-model soil temp, shown grey. Gp from prod cumulative; no IPR/VLP.
Reservoir limit
GIIP = q̄/(Ct·m)/1000. Defaults Sg 0.85 / So 0 / Sw 0.15; Cg grey = from two Bg points; slim table + Pwf-decline chart.
Pres from memory gauges
Measured static Pr — no march, no IPR: the gauge reading IS the datum. Inputs Date | Pr; calculated z | Gp | p/Z. Each reading must be a stabilized build-up corrected to datum. Defaults are the pressures selection 2 computes, so the two routes cross-check (both 123.80 Bscf).
Oil — Forecast (Tarner | Walsh)
Saturation-tracking material-balance forecast with a Method selector (one active). N and the start state (date, Np, Pres) are grey input-or-calculated, chained off the Reserve module — the date defaults to the last prod-data date, and both methods book an anchor row at that date so the curve begins there and joins the history. The forecast stream continues the well as last measured: Forecast THP, Forecast W.C and Forecast GOR are grey input-or-calculated, defaulting to the last prod row and overridable. All three are wellbore conditions held for the whole run — the GOR feeds the lift march every step, so a well producing free gas keeps that gas helping it flow, while the charted F GOR stays the MB reservoir GOR; both methods share the same inputs and the same Pwf source selection: nodal at forecast THP (VLP-coupled, default) or fixed minimum Pwf. Per step, both solve the same two residuals deterministically: pressure closes the gas balance (Gp by GOR trapezoid = Gp from MB), saturation closes the MB saturation; rel-perm from the sheet’s Kro(So)/Krg(Sg) polynomials; Brent/alternation to convergence instead of GoalSeek loops, the rate coupled to the trial pressure. PVT (Rs/Bo/Bg/μ) is evaluated at the trial pressure inside the solve — the training-material policy (“all PVT data evaluated at the assumed reservoir pressure”).
Tarner (default)
The classic method (workbook Tarner sheet): standard MB, So = (1−Swi)(N−Np)Bo/(N·Boi(1−ctΔp)), GOR = Rs + (Krg/Kro)(μoBo/μgBg), rate from the mobility PI J1 = 0.00708Kh/(ln(Re/Rw)−0.75+S).
Walsh (generalized MB, Rv)
Walsh’s generalized material balance (PETSOC 95-01-07) with the volatilized oil-gas ratio Rv(P): (Bg−Rv·Bo) terms in both the Gp and So equations, Foo-corrected GOR, and the CONSTANT calibrated PI on total mobility (qt = J·λt·Δp, qo = qt·λo/(λt·Bo)). Rv from the workbook’s tuned polynomial. Quirks preserved: μg = μo (the sheet’s Gas-Visc column reads the oil-viscosity cell); Bg in cf/scf.
Defaults: step 30 d · THP, W.C and GOR from the last prod row (300 psi / 60% / 5000 scf/stb on the demo well) · min Pwf 500 · Swi 0.15 · Cw 2.63e-6 · Cf 3.25e-6 · abandonment 50 stb/d · max 60 steps. Validation: 7 cell-pinned tests reproduce the Walsh sheet’s first solved row to 9+ digits; replaying the sheet’s Pwf column tracks its 64-step trajectory within ≈1% on pressure (EUR −2.2%, attributable to the sheet reading PVT from its parallel Tarner column). Chart: history + forecast (rate, GOR, Pres) on a calendar axis, table below.
Gas — Forecast
p/Z tank coupled to the nodal model, chained off the end of history: start date / start Gp / start Pres are grey input-or-calculated (last prod date, cumulative, minimum solved Pres). Defaults: step 30 d · FTHP 300 psi · plateau 12 MMscf/d · abandonment 1 MMscf/d · max 60 steps · GIIP & pᵢ/Zᵢ grey = from the reserve fit.
Each step: Pr from the p/Z line (first step anchors on start Pres) → Darcy IPR at Pr → nodal operating point at forecast FTHP → q = min(op, plateau) → Gp advances. Terminates on abandonment / depletion / dead well / max steps, reporting EUR and recovery %. The chart overlays history + forecast on a calendar-date axis; the results table sits below it.
Oil — Lift selection (artificial lift)
A first-pass screening advisory that ranks five artificial-lift methods — ESP · Gas Lift · Sucker Rod · Jet Pump · PCP — for a well described at three life snapshots — Initial, half the horizon, and the horizon, which the table header lets you set at 1, 2, 3 or 4 years (the workbook’s case is Initial / +6 months / +1 year); the middle column’s label follows at half, and changing the horizon re-screens at once. It ports the “Artificial Lift Method Selection” workbook: a technical envelope screen, then a UDC economic screen, then the analyst’s call. Reached from the Oil tab’s module selector (Well model / Reserve / Forecast / Lift selection); it computes on open and re-runs on any edit.
Technical screen (life-of-well). Each snapshot’s eight parameters — gross rate, to-perf. depth (m), GLR, WHP, water cut, GOR, max well deviation, max dog-leg — are tested against each method’s global Min–Max envelope bands (the workbook’s limits sheet, version-stamped on every run). A method is technically applicable only when it passes every parameter at every snapshot; the matrix marks each cell pass / partial (passes at some snapshots) / fail, so a method that is fine now but marginal later stays visible. The four envelope charts plot the well’s design line (the three snapshots) against every method’s box, grouped as the workbook’s four levels — L1 Depth (m)+Rate, L2 WHP+GLR, L3 W.C+GOR, L4 Max deviation+Max dog-leg. Four inputs are life-constant and are typed once, under Initial: the bubble point, the to-perf. depth, the max well deviation and the max dog-leg belong to the fluid and the wellbore rather than to time, so the two later columns carry the Initial value (shown greyed) and the handler screens the same constant at every snapshot. Two of the eight are calculated, not typed, and are shown as live grey rows above the inputs that follow as you type: the gross rate is the composite-Vogel Q from Pwf/Pr/Pb/J, and the GLR follows from the GOR and the water cut — GLR = GOR × (1 − W.C/100), since the GOR is gas per barrel of OIL and the GLR gas per barrel of TOTAL liquid, so a typed GLR could only contradict its own inputs. The workbook’s own figures are exactly this: 400 × 0.98 / 0.80 / 0.50 = 392 / 320 / 200 across the three snapshots, so deriving it reproduces the sheet rather than departing from it.
Economic screen (UDC). The cumulative oil over the chosen horizon is the trapezoid of the oil rate (Q·(1−W.C)) at the three snapshots — the same shape at every horizon, so a 2-year horizon on the same three rates integrates to exactly twice the 1-year cum — the well’s production, so the same cumulative applies to every method. Technical acceptance comes first: only the methods that survive the envelope screen and this well’s conditions are costed at all — a method that cannot be run on the well has no economics worth reading, and pricing it would invite a cheap-but-undeployable option to sit beside the real candidates (the sucker rod is the cheapest of the five on the demo, and rightly never appears). The dropped ones are listed under the table as not costed — technically rejected first, naming which of the two reasons applied, so nothing is silently absent. Each survivor’s UDC = capex / horizon cum OIL + opex, in $ per barrel of oil; the economical method is the cheapest of them at or under the UDC limit. The denominator is oil, never gross liquid — on the demo well that is 369,581 stb against a gross 427,963 stb, and costing on gross would quietly understate the ESP’s UDC by 0.18 $/bbl, by more as the well waters out. Capex is a per-method input; ESP and Gas Lift are engine-backed elsewhere in WellSim, the other three are marked “clipboard” (analyst figures). A per-method cumulative may be pasted to override the common one.
Gates. Four tick-boxes rule a method out on top of the envelope screen. Three of them stop a method for a facility or a metallurgy reason rather than an operating envelope; the fourth is the analyst’s own judgement. In every case the reason travels with the exclusion: no gas compression nearby rules out Gas Lift (it has no source of injection gas — removable by tying in compression); the well flowing naturally rules out Sucker Rod (a rod pump belongs on a well that can no longer flow unaided); and high H2S/CO2 rules out both Sucker Rod and PCP — sour service attacks the rod string by sulphide stress cracking and the stuffing box, while H₂S and CO₂ swell and harden the PCP’s stator elastomer until it chunks (removable by an elastomer qualified for that gas composition). The jet pump is deliberately NOT excluded by sour gas: the published screening tables rate it well on corrosion — no moving parts downhole, a carbide nozzle and throat, and inhibitor can ride down continuously in the power fluid — and its real weakness, free gas at the throat, is already screened by the GLR band. What sour gas does raise for a jet pump is a surface-facilities question, sour returns through an open power-fluid loop, so it travels as a named warning beside the result rather than as an exclusion. The fourth box, Exclude jet pump, is not a well condition at all but the analyst’s own call, and it says so: a jet pump lifts by momentum exchange between a high-velocity power-fluid jet and the produced fluid, and that mixing is irreversibly lossy — overall efficiency runs about 20–30 %, against 50–60 % for an ESP or a rod pump — so the power fluid must be pumped at high rate and pressure, buying a surface plant (HP triplex pumps, power-fluid tankage and treating, an extra string or flowline) and the energy bill with it, while the throat needs enough intake submergence to stay off its cavitation limit. Ticking it excludes the jet pump with that reason attached; unticking screens it again. The two kinds of exclusion stay distinguishable, and nothing is removed silently: the matrix verdict reads PASS, out (outside its envelope) or excluded (clears its envelope, ruled out by this well’s conditions or by the analyst), the reasons are listed under the matrix, and a method that clears its bands only to be gated is named in the warnings — the actionable case, since compression can be installed where sour service cannot be typed away. The economics may only choose from what survives both: on the demo well, clearing the gas-compression box rules Gas Lift out and the pick falls to the Jet pump at 3.79 $/bbl.
Constants & conversions
| Constant | Value |
|---|---|
| scf → m³ | 0.02831684639 |
| bbl → m³ | 0.158987304 |
| Water density | 998.9926968 kg/m³ ≡ 62.36509524 lb/ft³ |
| psi → Pa | 1 / 0.000145037738 |
| π (trajectory cosine) | 22/7 (Excel legacy, preserved) |
| π (areas) | 3.14 |
| Pb calibration constant | 2.1045604254721 |
Oil PVT (Standing-metric family)
With TC = (T−32)·5/9, ρₒ = 141.5/(131.5+API)·998.9926968 kg/m³, rs in m³/m³.
Pb [psi] = 125·[ (582·rsi/γg)^0.83 · 10^(0.00164·Tc − 1768/ρo) − 1.4 ] / 14.5 · 2.1045604254721 Rs(P<Pb) : rs = (γg/582) · [ (8e-6·P_Pa + 1.4) · 10^(1768/ρo − 0.00164·Tc) ]^(1/0.83); Rs = Rsi above Pb Bo_sat = 0.9759 + 12e-5 · (177·rs·√(γg/ρo) + 2.25·Tc + 40)^1.2 Bo(P>Pb) = e^(3e-6·(Pb−P)) · Bo_sat(Rsi) μ_od (Glaso) = 3.141e10 · T_F^(−3.444) · (log10 API)^a , a = 10.313·log10(T_F) − 36.447 Beggs–Robinson : A = 10.715·(Rs+100)^−0.515 , B = 5.44·(Rs+150)^−0.338 , μo = A·μod^B Vasquez–Beggs P>Pb: μ = μob·(P/Pb)^m , m = 2.6·P^1.187·10^(−3.9e-5·P − 5)
Gas PVT
Sweet pcrits (oil family): Tpc = 169 + 314·γg Ppc = 708.75 − 57.7·γg Sour route (gas wells) : γhc = (γg − 0.9672·yN2 − 1.5195·yCO2 − 1.1762·yH2S)/(1 − Σy) Tc_hc = 187 + 330·γhc − 71.5·γhc² Pc_hc = 706 + 51.7·γhc − 11.1·γhc² Kay mixing (+227.3/547.6/672.4 T; +493/1071/1306 P) + Wichert–Aziz ε correction
Brill & Beggs Z (explicit — the ONLY Z model): A = 1.39·(Tpr−0.92)^0.5 − 0.36·Tpr − 0.101 B = (0.62−0.23·Tpr)·Ppr + (0.066/(Tpr−0.86) − 0.037)·Ppr² + 0.32/10^(9(Tpr−1))·Ppr⁶ C = 0.132 − 0.32·log10(Tpr) D = 10^(0.3106 − 0.49·Tpr + 0.1824·Tpr²) Z = A + (1−A)/e^B + C·Ppr^D
μg = μ_base(γg,T)/Tpr · exp( Dempsey poly16(Ppr,Tpr) ) (a0 = −2.462 … a11 = 0.00441, sheet column V) ρg = 28.97·γg·P / (Z·10.73·(T+460)) [lb/ft³] b (march) = 0.0283·Z·(T+460)/(P+14.5) Bg (res. limit) = 0.00504·5.61·(T+460)·Z/P [cf/scf] Bg (oil MB) = 0.0283·Z·(T+460)/(P+14.5)/5.615 [bbl/scf]
Quirk preserved: the oil family anchors Tpr at station 1; the gas march uses local Tpr.
Trajectory, temperatures & marches
TVD = kickoff + (AH − kickoff)·cos(θ·(22/7)/180)
— also places the ESP pump: its setting depth is entered as MEASURED
depth (mAH) and converted to TVD on this trajectory
Ramey WHT (always calculated):
K10 = (OD_ft·3.14·U)/((w/24)·Cp) K11 = e^(−K10·L₁) (L₁ = FIRST station spacing — sheet quirk)
T_shelf(x) = T_soil + (T_res−T_soil)·TVD(x)/TVD_tot
bottom-up: T(i) = T_shelf(i) + (T(i+1) − T_shelf(i))·K11
Chen friction (Fanning): 1/√f = −4·log10[ ε/3.7065 − (5.0452/Re)·log10( ε^1.1098/2.8257 + (7.149/Re)^0.8981 ) ] dP_fric/dx = (1/144)·f·w²/(7.413e10·d_ft⁵·ρ_mix)·matchFriction Head acts on ΔTVD, friction on ΔAH — explicit Euler, 25–30 stations, no iteration.
Oil march (modified Griffith) — one march for natural / gas lift / ESP:
E_L = 1 − 0.38·(1 + v_m/0.8 − √disc) (Griffith, author's 0.38) Ashry head factor (applied to the hydrostatic term): f_WC = 7.27117418e-6·WC² − 1.19359442e-3·WC + 1.00062546 GOR≤400 : f_GOR = −6.59987105e-7·GOR² + 3.56896045e-4·GOR + 1.02495053 400–5000 : f_GOR = 1.97914840e-8·GOR² − 1.61983769e-4·GOR + 1.09560265 (capped at 5000) F_head = (f_GOR·f_WC)^0.82
Segments: natural 29 stations; gas lift 15+14 around the injection point; ESP 26 + pump node + 2 (ΔP at the pump, separated tubing gas above, back-march for intake with properties at max(P, 100)).
Gas march (modified Gray) — Gray holdup with condensate + water loading, effective-roughness friction (base 0.0021 in), local Tpr. Bit-parity pins: BHP!D51 = 3598.66511095252 @ 1e-8; three real get Pwf points (3414.02 / 2913.97 / 2647.82 psi).
IPR
Oil — composite Vogel (q_gross; q_oil = q_gross·(1−WC/100)): Pr>Pb, Pwf≥Pb : q = J·(Pr − Pwf) Pr>Pb, Pwf<Pb : q = J·(Pr − Pb) + (J·Pb/1.8)·(1 − 0.2·Pwf/Pb − 0.8·(Pwf/Pb)²) Pr≤Pb : q = (J·Pr/1.8)·(1 − 0.2·Pwf/Pr − 0.8·(Pwf/Pr)²) Darcy J (dominant): J = 0.00708·K·h / [ μo·Bo·(ln(Re/Rw) − 0.75 + S) ] Calibrate: user judges skin → closed-form matched K so J_Darcy = J_test (Pri-anchored) User PI basis: type J + Pres directly (ESP workbook route, "Iput PI"); K becomes the DERIVED cell — same closed-form match against the typed J, grey = program-filled. Geometry optional. Lift-type switch loads that workbook’s demo case on a pristine form. Future J (J_2x): μo·Bo re-evaluated at the future Pr, with Beggs–Robinson A/B at the CURRENT-Pr Rs (sheet quirk). Pins: J_21–23 = 5.27146844059 / 5.8313895807703 / 6.78094220552862
Gas: Darcy Pr² : q [MMscf/d] = J·(Pr² − Pwf²)/1000 J_test = 1000·q/(Pri² − Pwf²) From K : J = 703e-6·K·h / [ μg·Z·(T+460)·(ln(0.472·Re/Rw) + S) ] C & n : q = C·(Pr² − Pwf²)^n (log-log least squares) Pr from test: Pr = √(1000·q/J + Pwf²) | Pr = √((q/C)^(1/n) + Pwf²)
Multi-layer (optional): per-layer rates at common Pwf; equivalent record gas-exact (J_t = ΣJᵢ, Pr₀ = √(ΣJᵢPrᵢ²/ΣJᵢ)); oil Pr-average solves ΣQᵢ = 0. Every layer is drawn on the IPR/VLP chart beside the total (thin dashed, labelled with its own Pr), together with IPR (layers summed) — the true commingled sum, which the solid collapsed IPR matches only at the solution point on oil but at every pressure on gas, where the collapse is exact. Layer curves and their sum come from ONE rate evaluation per grid point, so they add up by construction. Crossflow is shown, not hidden: a layer whose Pr sits below the flowing pressure takes fluid in, so its rate goes NEGATIVE — drawn red and dotted, labelled CROSSFLOW, its table row tinted, and its share of gross signed so the column still sums to 100%. The rate axis is floored at zero so a crossflow excursion cannot squeeze the operating range into half the plot.
Nodal analysis
Operating point: Brent root of R(q) = VLP(q) − IPR(q), highest-rate crossing (stable branch) Well-head curve: WHP(q) = Pwf_IPR(q) − Pwf_VLP(q) + FTHP, with calculated WHT per rate get_Pwf: any blank test/production Pwf is marched from its FTHP at its own rate and ratios
Reserves
GAS — condensate & the gas-equivalent balance (ALL four selections): SG_c = 141.5/(131.5 + API_c) MW_c = 42.43·SG_c/(1.008 − SG_c) GE = 133.01·SG_c/MW_c [Mscf/STB] (API 49.3 → SG 0.78263274336, MW 147.346636758) q_c = q_gas·CGR [STB/d] N_p,c = trapezoid(q_c·dt)/1e6 [MMstb] G_p,c = N_p,c·GE [Bscf] G_p,tot = G_p + G_p,c ← the balance is written on TOTAL gas; no condensate API → GE = 0 (dry) GAS — p/Z (selections 1 & 2): Pr per row (closed form) → Z → Gp trapezoid → least-squares p/Z vs G_p,tot GIIP [Bscf] = −(p/Z)ᵢ / slope (minimum connected, total-gas basis) SITHP statics: gas-head-only station march, geothermal T from SITHT at the head to Tres, per-station Z survey routes read N_p,c off the prod-table timeline, same clock as Gp GAS & OIL — reservoir limit (selection 3): m = −slope(Pwf vs t) over ALL rows Cg = (Bg₁ − Bg₂)/Bg₁/(P₂ − P₁) (first & last rows; input-or-calculated) Ct = Cg·Sg + Co·So + Cw·Sw + Cf GIIP [Bscf] = q̄/(Ct·m)/1000 STOIP [MMstb] = q̄/(Ct·m)/1e6
OIL — Havlena–Odeh (selections 1 & 2, solution-gas drive → minimum STOIIP): Pr per row: composite-Vogel inversion fixed-point iterated with J_2 (macro loops 5×) Np, Gp trapezoids (produced gas: Rsi above Pb, row GOR below); Rp = Gp/Np F = Np·( Bo + (Rp − Rs)·Bg ) Eo = Bo + (Rsi − Rs)·Bg − Boi Headline N = AVERAGE(F/Eo) over rows with Np > 0 (sheet AD1) Cross-check N = SLOPE(F vs Eo) incl. the exact (0,0) anchor (sheet V1) Selection 2 evaluates the same MB at MEASURED gauge pressures — no IPR/VLP.
Forecast (gas)
start: date = last prod date, Gp = cumulative, Pres = MIN solved Pres, cond cum = history
CGR = the LATEST prod row’s CGR (all overridable)
per Δt: G_p,tot = G_p + N_p,c·GE
(p/Z)_target = (p/Z)ᵢ·(1 − G_p,tot/GIIP) ← driven by TOTAL gas
Pr = start Pres (first step) | Brent-invert P/Z(P) = target
Darcy IPR at Pr → nodal op at forecast FTHP → q = min(q_op, plateau)
q_c = q·CGR [STB/d]
Gp += q·Δt/1000 N_p,c += q_c·Δt/1e6
stops: abandonment | depleted | died | max-steps
→ EUR (dry Gp), EUR cond (MMstb and Bscf), EUR TOTAL, recovery % on the total basis
(depleting on dry Gp alone drains the tank too slowly and overstates the EUR)
Forecast (oil — Tarner & Walsh)
Both march the macro's GoalSeek PAIR per step, alternated to convergence:
AE: Gp by the producing-GOR trapezoid over Np = Gp from the MB → solve P (Brent)
AF: the assumed So = So from the MB → solve So (substitution)
Pwf frozen for the step; PVT at the TRIAL pressure; 25 passes, |ΔP|<1e-6, |ΔSo|<1e-10
shared: Kro(So), Krg(Sg) = the sheet 6th-order polynomials (Tarner!F16/G16), verbatim
Sg = max(0, 1 − Swi − So); So clamped to [0.05, 1 − Swi]
ct = (Cw·Swi + Cf)/(1 − Swi)·(Pri − P) Swi .15, Cw 2.63e-6, Cf 3.25e-6 /psi
Np += qo·Δt/1e6; Gp += (GOR_prev + GOR_new)/2 · ΔNp
Pwf 'vlp' = nodal vs the REAL oil march at forecast FTHP, floored at minPwf
'fixed' = constant minPwf (the sheet's active behaviour)
an anchor row at the start date (Δt = 0) joins the forecast to its history
Tarner — solution-gas MB, MOBILITY Darcy PI (no μ·B; mobility enters through λ):
J1 = 0.00708·K·h/(ln(Re/Rw) − 0.75 + S) λt = Kro/μo + Krg/μg λo = Kro/μo
qo = J1·(λo/Bo)·(P − Pwf)
So = (1 − Swi)·(N − Np)·Bo / (N·Boi·(1 − ct)) (Tarner!Y)
Gp = N·(Rsi − Rs) − (N·Boi − (N − Np)·Bo)/Bg + N·Boi·ct/Bg + Np·Rs (Tarner!O)
GOR = Rs + (Krg/Kro)·(μo·Bo)/(μg·Bg) (Tarner!P)
Bg in bbl/scf; μg at reservoir T, sweet pseudo-criticals, LOCAL Tpr
Walsh — generalized (volatile-oil) MB with Rv, on the CONSTANT calibrated PI J:
Rv(P) = the sheet 6th-order polynomial (Walsh!AH) / 1000 [bbl/scf]
qt = J·λt·(P − Pwf); qo = qt·λo/(λt·Bo) J = Walsh!$I$13 = J_2
Gp = (N·(Boi·(1 − Rv·Rs) − (Bg − Rv·Bo)·Rsi − (Bo − Rs·Bg)) + Np·Bo − Np·Rs·Bg)
/ (Bo·Rv − Bg) + (N·Boi/Bg)·ct/(1 − Swi) (Walsh!O)
So = (1 − Swi)·((1 − Np/N)·Bo·Bg − Boi·Rv·Bo)
/ (Boi·(Bg − Rv·Bo)·(1 − ct)) (Walsh!Y)
Foo = 1/(1 + (Bo·Krg·μo·Rv)/(Bg·Kro·μg)) (Walsh!AI)
GOR = Foo·(Rs + (Krg/Kro)·(μo·Bo)/(μg·Bg)) (Walsh!P)
QUIRKS KEPT: μg = μo (Walsh!W reads the OIL-visc cell BHP!AQ)
Bg in cf/scf, NOT bbl/scf — the Walsh MB is written in those units
start Gp = Np₀·Rsi (Tarner starts at 0)
stops: qo < abandon (50 stb/d) | Np ≥ .999·N | P ≤ max(1.02·minPwf, 120) | dead | max-steps
→ EUR, recovery %
LIMIT: neither MB carries a water term — the Forecast W.C affects LIFT only, never
the balance. On a high-water-cut well that is a real limit of the method.
Documented deviations from the workbooks
- Z factor: the sheets GoalSeek Hall–Yarborough (left unconverged in places); WellSim is explicit Brill & Beggs. Station parity pinned at 1e-9 with sheet Z injected; march drift bands: natural <2%, gas-lift <5.5%, ESP <3–5%; gas march bit-exact.
- Solvers: Brent replaces GoalSeek and the forecast’s quadratic-LSQ intersection — same intent, exact crossing.
- Forecast plateau Pwf: reported at the produced (constrained) rate; the sheet’s unconstrained-intersection quirk is not replicated.
- Pressure update: exact p/Z inversion instead of the one-step-lag Z estimate.
- Oil reservoir-limit Cg anchors: first & last rows (sheet pinned rows 5 & 14) — near-immaterial for oil Ct.
- WHT: always calculated (Ramey); the input-THT path was removed by design decision.
- Rounding artifacts preserved where parity needs them: π = 22/7 trajectory cosine; the
rouhgscartifact available as a test override. - Oil-forecast PVT evaluation point: Rs/Bo/Bg/μ are evaluated at the TRIAL pressure inside each step’s solve, for both methods. The saved Tarner sheet froze PVT at initial values; the training material directs otherwise, and the Walsh Gp residual moves with pressure almost entirely through the PVT (Bg, Rs, Rv) — freezing it leaves no root.
- Walsh PVT table pressure: the sheet reads its PVT table at the parallel TARNER sheet’s pressures (a shared-table convenience of the workbook); WellSim reads it at Walsh’s own solved pressure.
- Per-row Model / User in the gas prod_data (WellSim extension, 25 Sep 2026): the gas reserve workbook backs every row’s Pr out of the IPR and has no typed-Pr convention. The Model column lets a date that carries a measured build-up pressure enter it directly (User: Pwf and Pr typed) while every other date solves exactly as the sheet does (Model). No fill-down, because the usual case is one gauge on one date. Rows without a model are Model rows.
- Per-row well model in prod_data (WellSim extension, 6 Sep 2026): no workbook switches the well model within one production history — each sheet solves every row with its own lift and its own J. The Model column lets a history that crossed a lift change (natural → gas lift → ESP) be solved row by row with the model and the J that applied on that date, and the Forecast starts from the last modelled row. Rows without a model solve exactly as the sheets do.
- Lift-selection depth is METRES: the workbook’s
limitssheet gives depth as 1000–3700 for an ESP and so on, and it means metres — its Level-1 chart axis reads “Depth, m”, and its demo well is 3200, the same ~3200 m well the oil model carries at 2810 mAH. The port had labelled those numbers ft, so the row now reads To perf. Depth, m (screening bands v1.3, 8 Sep 2026). No number moved: a run that typed 3200 meaning depth scores exactly as before, but a depth typed in feet for that well — 10,500 — now correctly screens out instead of being read as a 10,500 m well would be. A case saved before the rename keeps its typed depth. - Lift-selection geometry floors: the workbook’s
limitssheet starts every method’s deviation AND dog-leg bands at 0.1, which puts a perfectly vertical, perfectly straight well OUTSIDE all five envelopes at once — the opposite of the truth, since a vertical hole with no dog-leg is the easiest case any of them will ever see. WellSim uses 0 for both (screening bands v1.2, 8 Sep 2026): a lift method is limited by how MUCH a hole bends, never by how little. The bands version is stamped on every run, so an older screen still reproduces. - Lift-selection UDC term: the workbook’s
Cost×10⁶/CUM + Opex×CUM/CUMcarries a ×CUM/CUM that is identically 1; WellSim drops it — UDC = capex/cum + opex.
Troubleshooting
| Symptom | Cause & fix |
|---|---|
| “unparseable date row N” | Fix that row; nothing was computed. Use d-MMM-yy, dd/mm/yyyy hh:mm:ss, ISO, or a day number. |
| “no depletion signal” | The fitted p/Z (or MB Eo) is not declining — more data spread needed, or the volume genuinely is not closing. |
| Huge oil MB N | Undersaturated path + window too long. Use the early-production window (days–weeks) or trust the static-gauge selection. |
| Gas march error: CGR + WGR > 0 | Gray’s liquid terms divide by liquid rate — use a small CGR (0.1 stb/MMscf) for essentially dry gas. |
| Reserve/forecast uses the wrong well | Both always read the current Well-model inputs — calibrate the well model first. |
Contact
Questions, bug reports, or a workbook you would like ported — write to muhamad.elashry@gmail.com.