Exerciseschevron_rightChapter 19chevron_right19.6
fitness_center

Exercise 19.6

What Is the Appraisal Well Worth? - Value of Information

Level 2
Chapter 19: Real-World Projects
descriptionProblem

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:

  1. In sample_stoiip, seed np.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.)

  1. In appraisal_voi, take np.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?

lightbulbHints (0/3)

Stuck? Reveal hints one at a time — they progress from nudge to near-solution.

codeYour solution
main.py
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.