Skip to content

Bottomhole Pressure Example

bhp data

In this guide, our main focus is on leveraging the whitson+ API for uploading bottomhole pressure related data. The example can be broken down in four steps.

  • Uploading Wells and Wellheader Data, e.g. wellnames, UWI, etc.

  • Uploading Wellbore Data:, e.g. well deviation surveys, top/bottom perforations and

  • Uploading Production Data:, e.g. oil, gas and water rates.

  • Running BHP Calculations:, e.g. to be used in FMB, RTA, nodal or numerical modeling.

BHP Input Data Schema Visualization

bhp input data model

This data schema shows how the different data tables are related.

Difference between WELL_ID and WELL_DATA_ID

well data id

WELL_ID and WELL_DATA_ID serve different purposes. WELL_ID uniquely identifies a specific well, while WELL_DATA_ID is assigned to each wellbore configuration. Over time, multiple configurations can occur. For instance, initially flowing through casing and later installing tubing would result in two separate wellbore configurations, each with its own WELL_DATA_ID and installation DATE

Connect BHP data to whitson+: Python Example

What does the bhp.py file do?

This file shows an example of how one connect BHP data to the whitson+ domain. The file uses a helper class WhitsonConnection in whitson_connect.py provided here. The example data is from a standard whitson+ MASS UPLOAD example that can be downloaded here. Modifying the code to e.g. pulling data from a SQL table is straight forward, as exemplified below.

Connect BHP data to whitson+: Python Example

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import whitson_connect
import pandas as pd

CLIENT = "your_domain_here" #This is the company suffix in your whitson urls ex. 'courses' in courses.whitson.com
CLIENT_ID = "your_client_id_here" # Available on request
CLIENT_SECRET = "your_client_secret" # Available on request
PROJECT_ID = "your_project_id_here" # This is the int numeric value of the project, available from the URL like '638' in https://courses.whitson.com/fields/5/projects/638/

whitson_connection = whitson_connect.WhitsonConnection(
    CLIENT, CLIENT_ID, CLIENT_SECRET
)

whitson_connection.access_token = whitson_connection.get_access_token_smart()

excel_file = 'docs\\files\\onboarding\\mass-upload-example\\whitson+_mass_well_upload_field_Eagle-Ford_20231110.xlsx'

df = pd.read_excel(excel_file, sheet_name='Well Data', header=1).drop(0)

# ------------------------------------------------------------------------------------------------------------------
# 1. Upload Wells
# ------------------------------------------------------------------------------------------------------------------

for index, row in df.iterrows():
        well_name = row['Well Name']
        uwi_api = row['UWI | API']

        # Add more things here if wanted, all attributes here: https://internal.whitson.com/api-external/swagger/#/Base%20Data/post_api_external_v1_wells
        well_info = {"project_id": PROJECT_ID, "name": well_name, "uwi_api": uwi_api}
        whitson_connection.create_well(payload=well_info)

whitson_wells = whitson_connection.get_wells(PROJECT_ID)

# ------------------------------------------------------------------------------------------------------------------
# 2.1 Upload well deviation surveys
# ------------------------------------------------------------------------------------------------------------------

deviation_df = pd.read_excel(excel_file, sheet_name='Deviation Survey Data').drop(0)

payloads_by_well = {}

for index, row in deviation_df.iterrows():
    well_name = row['Well Name']
    md_value = row['MD']
    tvd_value = row['TVD']

    if well_name in payloads_by_well:
        payloads_by_well[well_name].append({"md": md_value, "tvd": tvd_value})
    else:
        payloads_by_well[well_name] = [{"md": md_value, "tvd": tvd_value}]

for well_name, payload in payloads_by_well.items():
    well_id = whitson_connection.get_well_id_by_wellname(whitson_wells, well_name)
    whitson_connection.edit_well_deviation_survey(well_id=well_id, payload=payload)

# ------------------------------------------------------------------------------------------------------------------
# 2.2 Upload top and bottom perforations
# ------------------------------------------------------------------------------------------------------------------

df_perf = pd.read_excel(excel_file, sheet_name='Wellbore Data').drop(0) 

processed_wells = {}

for index, row in df_perf.iterrows():
    well_name = row['Well Name']
    top_md = row['Top Perforation MD']
    bottom_md = row['Bottom Perforation MD']

    if well_name in processed_wells:
        continue

    payload = {
        "top_perforation_md": top_md,
        "bottom_perforation_md": bottom_md
    }

    well_id = whitson_connection.get_well_id_by_wellname(whitson_wells, well_name)

    whitson_connection.edit_perf_interval(well_id, payload=payload)

    processed_wells[well_name] = True

# ------------------------------------------------------------------------------------------------------------------
# 2.3 Upload wellbore configs
# ------------------------------------------------------------------------------------------------------------------

df_perf = pd.read_excel(excel_file, sheet_name='Wellbore Data').drop(0) 

well_payloads = {}

for index, row in df_perf.iterrows():
    well_name = row['Well Name']
    use_from_date = row['Use from Date'].strftime("%Y-%m-%d") if pd.notna(row['Use from Date']) else None
    flow_path = row['Flowpath'].lower().replace(" ", "_")
    lift_method = row['Lift Method'].lower().replace(" ", "_") if pd.notna(row['Lift Method']) else 'none'
    gas_lift_config = row.get('Gas Lift Configuration', 'poorboy').lower() if pd.notna(row.get('Gas Lift Configuration')) else 'poorboy'
    gauge_depth = row['Gauge Depth'] if pd.notna(row['Gauge Depth']) else None
    gas_lift_valve_md = row.get('Gas Lift Valve MD', 0) if pd.notna(row.get('Gas Lift Valve MD')) else 0

    # Example values, replace with actual logic to determine these
    last_valve_open = True if lift_method == 'Gas Lift' else False
    calculate_from_gauge = False # If gauge in well, put to True
    compute_through = "flowing_side" # use static_side if relevant
    compute_static_down_to = "end_of_tubing"
    roughness = 0.0006 # default in whitson+

    well_data_casing = [
        {
            "d_casing_inner": row['Casing 1 ID'],
            "pipe_number": 1,
            "k_casing": roughness,
            "bottom_md": row['Casing 1 Bottom MD'],
            "top_md": row['Casing 1 Top MD']
        },
    ]

    if pd.notna(row['Casing 2 ID']): # check and append data only if it exists
        well_data_casing.append({
            "d_casing_inner": row['Casing 2 ID'],
            "pipe_number": 2,
            "k_casing": roughness,
            "bottom_md": row['Casing 2 Bottom MD'],
            "top_md": row['Casing 2 Top MD']
        })

    # Example for additional casing IDs:
    # if pd.notna(row['Casing 3 ID'])::
    #     well_data_casing.append({
    #         "d_casing_inner": row['Casing 3 ID'],
    #         "pipe_number": 3,
    #         "k_casing": roughness,
    #         "bottom_md": row['Casing 3 Bottom MD'],
    #         "top_md": row['Casing 3 Top MD']
    #     })

    well_data_tubing = []
    if pd.notna(row['Tubing ID']):
        well_data_tubing.append({
            "pipe_number": 1,
            "bottom_md": row['Tubing Bottom MD'],
            "d_tubing_inner": row['Tubing ID'],
            "d_tubing_outer": row['Tubing OD'],
            "k_tubing": roughness 
        })

    # Example for additional tubing IDs:
    # if pd.notna(row['Tubing ID 2 ID'])::
    #     well_data_tubing.append({
    #         "pipe_number": 2,
    #         "bottom_md": row['Tubing 2 Bottom MD'],
    #         "d_tubing_inner": row['Tubing 2 ID'],
    #         "d_tubing_outer": row['Tubing 2 OD'],
    #         "k_tubing": roughness 
    #     })

    gas_lift_data = []
    if lift_method == 'gas_lift':
        gas_lift_data.append({
            "ps_close": 0,
            "valve_md": gas_lift_valve_md,
            "ps_open": 0,
            "joint": 0
        })

    if well_name not in well_payloads:
        well_payloads[well_name] = []

    well_payloads[well_name].append({
        "use_from_date": use_from_date,
        "flow_path": flow_path,
        "lift_method": lift_method,
        "gas_lift_config": gas_lift_config,
        "last_valve_open": last_valve_open,
        "gauge_depth": gauge_depth,
        "calculate_from_gauge": calculate_from_gauge,
        "compute_through": compute_through,
        "compute_static_down_to": compute_static_down_to,
        "well_data_casing": well_data_casing,
        "well_data_tubing": well_data_tubing,
        "gas_lift_data": gas_lift_data
    })

for well_name, payload_list in well_payloads.items():
    well_id = whitson_connection.get_well_id_by_wellname(whitson_wells, well_name)
    wellbore_data = whitson_connection.get_well_data(well_id)

    # Delete the default wellbore. Not needed if you're simply appending wellbore configurations.
    if wellbore_data: 
        well_data_id = wellbore_data[0]['id']
        whitson_connection.delete_wellbore_config_by_well_data_id(well_data_id=well_data_id)        

    whitson_connection.upload_well_data_to_well(well_id, payload_list)

# ----------------------------------------------------------------------------------------------------------------
# 3. Upload production data
# ----------------------------------------------------------------------------------------------------------------

df_prod = pd.read_excel(excel_file, sheet_name='Production-Data', skiprows=2)
production_payload = []
prior_wellname = None

for index, row in df_prod.iterrows():
    well_name= row.iloc[0]
    date = pd.to_datetime(row.iloc[1], format='%Y-%m-%d %H:%M:%S')
    qo = row.iloc[2]
    qg = row.iloc[3]
    qw = row.iloc[4]
    p_wf_measured = row.iloc[5]
    p_tubing = row.iloc[6]
    p_casing = row.iloc[7]
    qg_gas_lift = row.iloc[8]
    liquid_level = row.iloc[9]

    this_prod_time = {
        "date": date.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if date else None,
        "qo_sc": max(0, qo) if pd.notnull(qo) else None,
        "qg_sc": max(0, qg) if pd.notnull(qg) else None,
        "qw_sc": max(0, qw) if pd.notnull(qw) else None,
        "p_wf_measured": max(0, p_wf_measured) if pd.notnull(p_tubing) else None,
        "p_tubing": max(0, p_tubing) if pd.notnull(p_tubing) else None,
        "p_casing": max(0, p_casing) if pd.notnull(p_casing) else None,
        "qg_gas_lift": max(0, qg_gas_lift) if pd.notnull(qg_gas_lift) else None,
        "liquid_level": max(0, liquid_level) if pd.notnull(liquid_level) else None,
        "choke_size": None,
        "line_pressure": None,
    }

    if well_name == prior_wellname:
        production_payload.append(this_prod_time)
    else:
        if prior_wellname is not None:
            well_id = whitson_connection.get_well_id_by_wellname(whitson_wells, prior_wellname)
            whitson_connection.upload_production_to_well(
                well_id,
                {"production_data": production_payload},
                append_only=True,
            )
        production_payload = []
        production_payload.append(this_prod_time)
        prior_wellname = well_name

# Don't forget to upload the last batch
if production_payload:
    well_id = whitson_connection.get_well_id_by_wellname(whitson_wells, prior_wellname)
    whitson_connection.upload_production_to_well(
        well_id,
        {"production_data": production_payload},
        append_only=True,
    )

# ----------------------------------------------------------------------------------------------------------------
# 4. Run BHP calculations
# ----------------------------------------------------------------------------------------------------------------

# Run BHP calc

for well in whitson_wells: 
    well_id = well['id']
    whitson_connection.run_bhp_calc(well_id)

# Output BHP calc

# for well in whitson_wells: 
#     well_id = well['id']
#     calculated_bhp = whitson_connection.get_bhp_calc(well_id)
#     # To something with it, e.g. third-party dashboard

Reading Data from a SQL Table

The example above demonstrates how to read data from an excel template into whitson+. The code can be easily modified to read from a SQL database into whitson+ instead. Example of how to read SQL into a Pandas Dataframe can be seen below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd
import pyodbc

# Establish a connection to your SQL Server
conn = pyodbc.connect('DRIVER={SQL Server};SERVER=your_server;DATABASE=your_database;UID=your_username;PWD=your_password')

# Define your SQL query to select data from your table
sql_query = "SELECT * FROM your_table_name"  # Replace 'your_table_name' with your actual table name

# Read data from SQL table into a pandas DataFrame
df = pd.read_sql(sql_query, conn)

# Close the connection
conn.close()

# Now you have your data in the DataFrame 'df'
print(df.head())  # Display the first few rows of the DataFrame