Analyze local SCADA, historian, test-separator, and daily production exports for well surveillance, anomaly detection, optimization, and candidate ranking. Use when the user provides CSV or spreadsheet histories of rates, pressures, temperatures, choke settings, lift parameters, compressor data, plunger cycles, or test-separator results and asks why a well changed, where bottlenecks are, which wells to prioritize, or how to diagnose slugging, liquid loading, compression constraints, or lift underperformance. Trigger phrases include production surveillance, SCADA export, historian CSV, daily well data, test separator, candidate ranking, anomaly detection, rate drop, compressor bottleneck, or lift tuning.
Operational skill for interpreting local time-series data from production systems. It is built for engineers who already have the field exports but need a disciplined workflow to diagnose what changed and rank the next actions.
Important: Surveillance starts with event context. Pull known operational changes first: choke moves, compressor trips, lift changes, shut-ins, chemical changes, workovers, and separator reroutes.
def gor_scf_stb(gas_mscfd, oil_bopd):
"""Gas-oil ratio in scf/STB."""
if oil_bopd <= 0:
return None
return gas_mscfd * 1000.0 / oil_bopd
def water_cut_frac(water_bwpd, oil_bopd):
"""Water cut as a fraction of liquid."""
liquid = water_bwpd + oil_bopd
if liquid <= 0:
return None
return water_bwpd / liquid
def pressure_normalized_rate(q, p_ref, p_now):
"""
Simple screening normalization for constrained wells.
"""
if p_now <= 0:
return None
return q * p_ref / p_now
def moving_average(values, window):
if window <= 0 or len(values) < window:
return None
out = []
for i in range(window - 1, len(values)):
out.append(sum(values[i - window + 1:i + 1]) / window)
return out
def percent_change(old, new):
if old == 0:
return None
return 100.0 * (new - old) / old
def simple_decline_rate(q_start, q_end, days):
if q_start <= 0 or days <= 0:
return None
return (q_start - q_end) / q_start / days
Look for:
def candidate_score(rate_loss_pct, pressure_penalty_pct,
data_quality=1.0, workover_complexity=1.0):
"""
Simple ranking score: higher means more attractive candidate.
workover_complexity > 1 penalizes difficult candidates.
"""
base = max(rate_loss_pct, 0.0) + max(pressure_penalty_pct, 0.0)
return data_quality * base / max(workover_complexity, 0.1)
def pressure_penalty(current_whp, target_whp):
if current_whp <= 0:
return None
return 100.0 * max(current_whp - target_whp, 0.0) / current_whp
Rank candidates separately for:
Mixing those classes into one score usually hides the real action.
| Pattern | Likely issue |
|---|---|
| Rate drop after compressor suction pressure rises | Compression bottleneck |
| Rising casing pressure, unstable tubing pressure, intermittent gas | Liquid loading |
| Current draw rises while rate falls on ESP well | Pump wear, gas interference, or intake problem |
| Rod load indicators worsen with falling fillage | Pump-off or gas interference |
| Choke opened but rate does not respond | Reservoir or downstream critical-flow limit |
| Test-separator gains not reflected in allocated data | Allocation issue, not field change |
When using this skill, structure the answer as:
pnge:nodal-analysis-multiphase for inflow-outflow sensitivity.pnge:artificial-lift for lift-specific tuning.pnge:well-integrity-barriers when annulus or integrity signals appear.pnge:rta-production when the change looks reservoir-driven rather than operational.