Exercise 19.6
What Is the Appraisal Well Worth? - Value of Information
In Project 4, halve the area uncertainty (tighten the triangular distribution, as a successful appraisal well would) and recompute the STOIIP P10/P50/P90. By how much does the P10-P90 range shrink? If an appraisal well costs \$8 MM and the development decision hinges on whether P90 STOIIP clears a 30-MMSTB threshold, is the well worth drilling? Make the value-of-information argument with numbers.
---
Project 4 ended on a recommendation, not a number: the STOIIP P10-P90 range is driven by the area, so drill one appraisal well to tighten it before committing the development. This exercise puts a value on that well: the essence of a value-of-information decision.
The volumetric model stoiip_of, the input distributions, and the two area cases (AREA_BASE wide, AREA_TIGHT halved around the mode: what a successful appraisal well buys you) are in the do-not-edit block. Write two functions:
def sample_stoiip(area_args, seed, K=10000):
'''Monte-Carlo STOIIP. Draw order fixed: area, net, phi, sw, bo.'''
def appraisal_voi(seed=7, K=10000):
'''Compare the STOIIP range before vs after the appraisal tightens area.'''Exact procedure:
- In
sample_stoiip, seednp.random.default_rng(seed)and draw in this exact
order: area = rng.triangular(*area_args, K), net = rng.normal(34, 3.5, K), phi = rng.normal(0.24, 0.025, K), sw = rng.normal(0.30, 0.05, K), bo = rng.normal(1.25, 0.05, K), then return stoiip_of(area, net, phi, sw, bo). (Fixing the order means only the area knowledge changes between the two cases.)
- In
appraisal_voi, takenp.percentile(..., [10, 50, 90])(so index 0 is P90,
the low side) for AREA_BASE and AREA_TIGHT. Return a dict with p90_base, p50_base, p10_base, p90_tight, p50_tight, p10_tight, range_before, range_after, shrink_pct, where each range is P10 - P90 and shrink_pct = (1 - range_after / range_before) * 100.
Expose exactly: sample_stoiip, appraisal_voi, and voi = appraisal_voi().
> Think about it: the appraisal shrinks the P10-P90 range by about a quarter > and lifts P90 from ~30 (right at the threshold) to ~32 MMSTB. It moves the > conservative case clear of the line the decision hangs on. If the well costs > \$8 MM and clearing that threshold is what unlocks a development worth hundreds > of millions, is the information worth buying? What if the base-case P90 were > already 40, far above the line?
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
visibilityReveal reference solutionexpand_more
Try solving it yourself first — the hints walk you through it. The solution below is one valid approach; yours may differ and still be correct.
import numpy as np
# ── Verified Chapter 19 volumetric model + input distributions (do not edit) ─
def stoiip_of(area, net, phi, sw, bo):
"""STOIIP in MMSTB. 7758 = barrels per acre-foot."""
return 7758 * area * np.clip(net, 0, None) * np.clip(phi, 0, 1) * \
(1 - np.clip(sw, 0, 1)) / np.clip(bo, 1.05, None) / 1e6
# Area is the dominant uncertainty (sparse well control): a wide, right-skewed
# triangular. An appraisal well HALVES that spread around the mode (1150 acres).
AREA_BASE = (700, 1150, 1900)
AREA_TIGHT = (925, 1150, 1525)
THRESHOLD = 30.0 # MMSTB -- the development decision hinges on P90 clearing this
# ── end do-not-edit ──────────────────────────────────────────────────────
def sample_stoiip(area_args, seed, K=10000):
"""Monte-Carlo STOIIP. Draw order is fixed (area, net, phi, sw, bo) so that
changing ONLY the area distribution is a controlled experiment -- every other
input draws the same values for a given seed."""
rng = np.random.default_rng(seed)
area = rng.triangular(*area_args, K)
net = rng.normal(34, 3.5, K) # ft, field-wide net pay
phi = rng.normal(0.24, 0.025, K)
sw = rng.normal(0.30, 0.05, K)
bo = rng.normal(1.25, 0.05, K)
return stoiip_of(area, net, phi, sw, bo)
def appraisal_voi(seed=7, K=10000):
"""Quantify what tightening the area uncertainty (a successful appraisal well)
does to the STOIIP P10-P90 range. Returns a dict of percentiles and the shrink."""
base = np.percentile(sample_stoiip(AREA_BASE, seed, K), [10, 50, 90]) # P90, P50, P10
tight = np.percentile(sample_stoiip(AREA_TIGHT, seed, K), [10, 50, 90])
range_before = float(base[2] - base[0])
range_after = float(tight[2] - tight[0])
return dict(
p90_base=float(base[0]), p50_base=float(base[1]), p10_base=float(base[2]),
p90_tight=float(tight[0]), p50_tight=float(tight[1]), p10_tight=float(tight[2]),
range_before=range_before, range_after=range_after,
shrink_pct=(1 - range_after / range_before) * 100,
)
voi = appraisal_voi()
print(f"base STOIIP P90 {voi['p90_base']:.1f} P50 {voi['p50_base']:.1f} P10 {voi['p10_base']:.1f} (range {voi['range_before']:.1f})")
print(f"after appraisal P90 {voi['p90_tight']:.1f} P50 {voi['p50_tight']:.1f} P10 {voi['p10_tight']:.1f} (range {voi['range_after']:.1f})")
print(f"the appraisal shrinks the P10-P90 range by {voi['shrink_pct']:.0f}% and lifts P90 from "
f"{voi['p90_base']:.1f} to {voi['p90_tight']:.1f} MMSTB (threshold {THRESHOLD:.0f})")
lockCopying code is a Full Access feature.