Exercise 14.7
Non-Productive Time Analysis - Productive vs NPT & Invisible Lost Time
Categorize drilling data into productive time (drilling) and non-productive time (trips, connections, stuck pipe, circulation, waiting). Calculate the percentage of total well time in each category. What is the invisible lost time (time spent drilling inefficiently vs. the best-in-class offset)?
---
We'll work this on an OML-58 development well whose operations log has already been rolled up by activity. Drilling time is the only productive time on the rig; every other line (connections, trips, circulation, a stuck-pipe event, and rig waiting) is non-productive time (NPT).
The activity log and the benchmark are embedded for you as constants:
OPS_LOG = [
('drilling', 92.0),
('connection', 14.0),
('trip', 22.0),
('circulation', 8.0),
('stuck_pipe', 48.0),
('waiting', 10.0),
]
PRODUCTIVE_ACTIVITIES = {'drilling'} # the ONLY productive bucket
BEST_IN_CLASS_DRILL_HOURS = 70.0 # offset on-bottom hours for the same footageYour tasks:
- Write
categorize_time(ops_log, productive=PRODUCTIVE_ACTIVITIES)that returns
a dict with EXACTLY these keys:
'productive_hours': sum of hours whose activity IS inproductive.'npt_hours': sum of hours whose activity is NOT inproductive.'total_hours': sum of all hours in the log.'productive_pct':productive_hours / total_hours * 100.'npt_pct':npt_hours / total_hours * 100.
Any activity name that is not in productive counts as NPT. Do not hard-code the NPT activity names.
- Write
invisible_lost_time(ops_log, best_drill_hours, productive=PRODUCTIVE_ACTIVITIES)
that returns a float: (actual productive hours) - best_drill_hours. This is the time spent drilling inefficiently versus the best-in-class offset: hours that never show up as NPT on the morning report, yet were lost all the same.
- Compute and expose these output variables (the tests read them by name):
``python time_summary = categorize_time(OPS_LOG) productive_pct = time_summary['productive_pct'] npt_pct = time_summary['npt_pct'] ilt_hours = invisible_lost_time(OPS_LOG, BEST_IN_CLASS_DRILL_HOURS) ``
> Think about it: productive_pct + npt_pct must always equal 100. Notice > that the well drilled 92 productive hours but a best-in-class offset needed only > 70, so 22 hours vanished into slow drilling that no NPT code ever captured. > Which is the bigger prize on this well: trimming the 102 hours of visible NPT, > or closing the 22-hour invisible-lost-time gap?
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.
# ── OML-58 operations log + benchmark (do not edit) ──────────────────────
# Rolled-up rig time by activity (hours). Drilling is the only PRODUCTIVE
# bucket; everything else (connections, trips, circulation, stuck pipe,
# waiting) is non-productive time (NPT).
OPS_LOG = [
('drilling', 92.0),
('connection', 14.0),
('trip', 22.0),
('circulation', 8.0),
('stuck_pipe', 48.0),
('waiting', 10.0),
]
PRODUCTIVE_ACTIVITIES = {'drilling'} # the ONLY productive bucket
BEST_IN_CLASS_DRILL_HOURS = 70.0 # offset on-bottom hours for the same footage
def categorize_time(ops_log, productive=PRODUCTIVE_ACTIVITIES):
"""Split an operations log into productive vs non-productive time.
Returns a dict with keys:
'productive_hours', 'npt_hours', 'total_hours', 'productive_pct', 'npt_pct'
Any activity NOT in `productive` is counted as NPT.
"""
productive_hours = sum(h for activity, h in ops_log if activity in productive)
total_hours = sum(h for activity, h in ops_log)
npt_hours = total_hours - productive_hours
productive_pct = productive_hours / total_hours * 100
npt_pct = npt_hours / total_hours * 100
return {
'productive_hours': productive_hours,
'npt_hours': npt_hours,
'total_hours': total_hours,
'productive_pct': productive_pct,
'npt_pct': npt_pct,
}
def invisible_lost_time(ops_log, best_drill_hours, productive=PRODUCTIVE_ACTIVITIES):
"""Hours lost to slow drilling vs the best-in-class offset.
= (actual productive hours) - best_drill_hours
"""
actual = sum(h for activity, h in ops_log if activity in productive)
return actual - best_drill_hours
time_summary = categorize_time(OPS_LOG)
productive_pct = time_summary['productive_pct']
npt_pct = time_summary['npt_pct']
ilt_hours = invisible_lost_time(OPS_LOG, BEST_IN_CLASS_DRILL_HOURS)
print("time summary:", time_summary)
print("productive %:", productive_pct, " npt %:", npt_pct)
print("invisible lost time (hr):", ilt_hours)
lockCopying code is a Full Access feature.