Exerciseschevron_rightChapter 19chevron_right19.5
fitness_center

Exercise 19.5

Find the Real Underperformer - Difficulty-Normalised Drilling Cost

Level 2
Chapter 19: Real-World Projects
descriptionProblem

In Project 3, change well E's formation hardness to match well D's (make it a hard hole too) and re-rank. Does E stay the worst once it is no longer the easiest hole? Then add a seventh well of your own design that is fast but in soft rock: where does it rank raw vs. difficulty-normalised, and what does that teach about benchmarking new crews?

---

The fastest way to teach a drilling crew the wrong lesson is to rank them by raw cost-per-foot: the team that drilled the deepest, hardest hole looks worst, and the team that drilled a shallow, soft one slowly looks fine. Project 3's fix was to divide difficulty out first, then rank what is left: the part the crew actually controls.

The six-well SPEC and the DAYRATE are in the do-not-edit block. Each well is (id, TD, hardness, efficiency, npt). Write one function:

def benchmark(spec):
    '''Cost-benchmark wells two ways and name the worst by each measure.
    Returns (rows, raw_worst, norm_worst).'''

Exact procedure (mirror the chapter):

  1. For each well, rop = 110 / hard * eff; days = (td / rop / 24) / (1 - npt);

cost = days * DAYRATE.

  1. rows[wid] = {'cpf': cost / td, 'cpf_norm': (cost / td) / hard, 'cost': cost}

cpf is raw cost-per-foot, cpf_norm divides hardness back out.

  1. raw_worst = the well with the highest cpf; norm_worst = the well with the

highest cpf_norm (use max(rows, key=...)).

  1. return rows, raw_worst, norm_worst.

Then call it once at module level into the names the tests read:

rows, raw_worst, norm_worst = benchmark(SPEC)

> Think about it: raw cost-per-foot flags well B, but B only looks bad > because it drilled the second-deepest, second-hardest hole. Divide difficulty > out and the real laggard is well E: a shallow hole drilled at 70% > efficiency with 52% non-productive time. If you re-ran this after making E's > rock as hard as the deepest well's, would E still be the underperformer? And > what does that tell you about which number to put in the lessons-learned report?

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.

# ── Verified Chapter 19 drilling-benchmark spec + cost model (do not edit) ─
# Each well: (id, TD ft, formation hardness, drilling efficiency 0-1, non-productive-time fraction)
SPEC = [("A", 9000, 1.0, 0.92, 0.32), ("B", 11000, 1.6, 0.78, 0.44), ("C", 9500, 1.1, 0.95, 0.30),
        ("D", 12500, 1.9, 0.97, 0.28), ("E", 8800, 0.9, 0.70, 0.52), ("F", 10200, 1.4, 0.85, 0.38)]
DAYRATE = 280000.0   # $/day full rig spread
# ── end do-not-edit ──────────────────────────────────────────────────────


def benchmark(spec):
    """Cost-benchmark a list of wells two ways: raw cost-per-foot, and a
    DIFFICULTY-NORMALISED cost-per-foot that divides formation hardness out.

    Returns (rows, raw_worst, norm_worst):
      rows[wid] = {'cpf': float, 'cpf_norm': float, 'cost': float}
      raw_worst  = well with the highest RAW cpf  (penalises the hardest hole)
      norm_worst = well with the highest NORMALISED cpf (the true underperformer)
    """
    rows = {}
    for wid, td, hard, eff, npt in spec:
        rop = 110 / hard * eff                       # on-bottom ROP: rock AND practices
        days = (td / rop / 24) / (1 - npt)           # non-productive time inflates calendar days
        cost = days * DAYRATE
        rows[wid] = dict(cpf=cost / td, cpf_norm=(cost / td) / hard, cost=cost)   # divide difficulty OUT
    raw_worst = max(rows, key=lambda w: rows[w]["cpf"])
    norm_worst = max(rows, key=lambda w: rows[w]["cpf_norm"])
    return rows, raw_worst, norm_worst


rows, raw_worst, norm_worst = benchmark(SPEC)

for w in sorted(rows, key=lambda x: rows[x]["cpf_norm"]):
    print(f"  {w}: raw $/ft {rows[w]['cpf']:6.0f}   normalised $/ft {rows[w]['cpf_norm']:6.1f}")
print(f"raw cost/ft would flag well {raw_worst} (just the hardest hole)")
print(f"difficulty-normalised, the real underperformer is well {norm_worst}")

lockCopying code is a Full Access feature.