corva-worker-python 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- corva_worker_python-2.0.0.dist-info/METADATA +30 -0
- corva_worker_python-2.0.0.dist-info/RECORD +63 -0
- corva_worker_python-2.0.0.dist-info/WHEEL +5 -0
- corva_worker_python-2.0.0.dist-info/top_level.txt +1 -0
- worker/__init__.py +5 -0
- worker/app/__init__.py +291 -0
- worker/app/modules/__init__.py +265 -0
- worker/app/modules/activity_module.py +141 -0
- worker/app/modules/connection_module.py +21 -0
- worker/app/modules/depth_activity_module.py +21 -0
- worker/app/modules/scheduler.py +44 -0
- worker/app/modules/time_activity_module.py +21 -0
- worker/app/modules/trigger.py +43 -0
- worker/constants.py +51 -0
- worker/data/__init__.py +0 -0
- worker/data/activity/__init__.py +132 -0
- worker/data/activity/activity_grouping.py +242 -0
- worker/data/alert.py +89 -0
- worker/data/api.py +155 -0
- worker/data/enums.py +141 -0
- worker/data/json_encoder.py +18 -0
- worker/data/math.py +104 -0
- worker/data/operations.py +477 -0
- worker/data/serialization.py +110 -0
- worker/data/task_handler.py +82 -0
- worker/data/two_way_dict.py +17 -0
- worker/data/unit_conversions.py +5 -0
- worker/data/wits.py +323 -0
- worker/event/__init__.py +53 -0
- worker/event/event_handler.py +90 -0
- worker/event/scheduled.py +64 -0
- worker/event/stream.py +48 -0
- worker/exceptions.py +26 -0
- worker/mixins/__init__.py +0 -0
- worker/mixins/logging.py +119 -0
- worker/mixins/rollbar.py +87 -0
- worker/partial_rerun_merge/__init__.py +0 -0
- worker/partial_rerun_merge/merge.py +500 -0
- worker/partial_rerun_merge/models.py +91 -0
- worker/partial_rerun_merge/progress.py +241 -0
- worker/state/__init__.py +96 -0
- worker/state/mixins.py +111 -0
- worker/state/state.py +46 -0
- worker/test/__init__.py +3 -0
- worker/test/lambda_function_test_run.py +196 -0
- worker/test/local_testing/__init__.py +0 -0
- worker/test/local_testing/to_local_transfer.py +360 -0
- worker/test/utils.py +51 -0
- worker/wellbore/__init__.py +0 -0
- worker/wellbore/factory.py +496 -0
- worker/wellbore/measured_depth_finder.py +12 -0
- worker/wellbore/model/__init__.py +0 -0
- worker/wellbore/model/ann.py +103 -0
- worker/wellbore/model/annulus.py +113 -0
- worker/wellbore/model/drillstring.py +196 -0
- worker/wellbore/model/drillstring_components.py +439 -0
- worker/wellbore/model/element.py +102 -0
- worker/wellbore/model/enums.py +92 -0
- worker/wellbore/model/hole.py +297 -0
- worker/wellbore/model/hole_section.py +51 -0
- worker/wellbore/model/riser.py +22 -0
- worker/wellbore/sections_mixin.py +64 -0
- worker/wellbore/wellbore.py +289 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
from worker import API
|
|
4
|
+
from worker.data.operations import compare_float, get_config_by_id, get_data_by_path
|
|
5
|
+
from worker.exceptions import MissingConfigError
|
|
6
|
+
from worker.wellbore.model.drillstring import Drillstring
|
|
7
|
+
from worker.wellbore.model.enums import HoleType
|
|
8
|
+
from worker.wellbore.model.hole import Hole
|
|
9
|
+
from worker.wellbore.model.hole_section import HoleSection
|
|
10
|
+
from worker.wellbore.model.riser import Riser
|
|
11
|
+
from worker.wellbore.wellbore import Wellbore
|
|
12
|
+
|
|
13
|
+
MIN_LENGTH = 1 # ft
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_drillstring_and_create_wellbore(wits: dict, mud_flow_in: float = None) -> Union[Wellbore, None]:
|
|
17
|
+
"""
|
|
18
|
+
Get the wits record and create a wellbore
|
|
19
|
+
:param wits: wits records in json format
|
|
20
|
+
:param mud_flow_in: used to determine if under-reamer needs to be activated for hole enlargement
|
|
21
|
+
:return: a wellbore
|
|
22
|
+
"""
|
|
23
|
+
if wits is None:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
api_worker = API()
|
|
27
|
+
|
|
28
|
+
drillstring_id = get_data_by_path(wits, "metadata.drillstring", str, default=None)
|
|
29
|
+
|
|
30
|
+
if not drillstring_id:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
drillstring = get_config_by_id(drillstring_id, collection="data.drillstring")
|
|
34
|
+
|
|
35
|
+
if not drillstring:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
active_drillstring_number = drillstring.get("data", {}).get("id")
|
|
39
|
+
|
|
40
|
+
# setting active drillstring
|
|
41
|
+
drillstring = Drillstring(drillstring)
|
|
42
|
+
|
|
43
|
+
bit = drillstring.get_bit()
|
|
44
|
+
if not bit:
|
|
45
|
+
raise MissingConfigError(f"Bit was not found in the drillstring with _id='{drillstring_id}'")
|
|
46
|
+
|
|
47
|
+
asset_id = wits["asset_id"]
|
|
48
|
+
bit_size = bit.size
|
|
49
|
+
|
|
50
|
+
bit_depth = get_data_by_path(wits, "data.bit_depth", float)
|
|
51
|
+
hole_depth = get_data_by_path(wits, "data.hole_depth", float)
|
|
52
|
+
|
|
53
|
+
# setting cased-hole sections
|
|
54
|
+
query = "{data.inner_diameter#gte#%s}" % bit_size
|
|
55
|
+
casings = api_worker.get(
|
|
56
|
+
path="/v1/data/corva", collection="data.casing", asset_id=asset_id, query=query, limit=100
|
|
57
|
+
).data
|
|
58
|
+
hole = set_casings(casings)
|
|
59
|
+
smallest_cased_hole_diameter = hole.get_min_inner_diameter() or 100
|
|
60
|
+
|
|
61
|
+
# ====== setting open-hole sections
|
|
62
|
+
query = "{data.id#lte#%s}" % active_drillstring_number
|
|
63
|
+
sort = "{data.id:1}"
|
|
64
|
+
drillstrings = api_worker.get(
|
|
65
|
+
path="/v1/data/corva", collection="data.drillstring", asset_id=asset_id, query=query, sort=sort, limit=100
|
|
66
|
+
).data
|
|
67
|
+
hole = add_open_holes(hole, drillstrings, smallest_cased_hole_diameter, hole_depth)
|
|
68
|
+
|
|
69
|
+
mud_flow_in = get_mud_flow_in(mud_flow_in, wits)
|
|
70
|
+
|
|
71
|
+
return Wellbore(
|
|
72
|
+
drillstring=drillstring,
|
|
73
|
+
hole=hole,
|
|
74
|
+
bit_depth=bit_depth,
|
|
75
|
+
hole_depth=hole_depth,
|
|
76
|
+
mud_flow_in=mud_flow_in,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def run_casingstring_and_create_wellbore(
|
|
81
|
+
wits: dict, mud_flow_in: float = None, is_design: bool = False
|
|
82
|
+
) -> Union[Wellbore, None]:
|
|
83
|
+
"""
|
|
84
|
+
Get the wits record and create a wellbore
|
|
85
|
+
:param wits: wits records in json format
|
|
86
|
+
:param mud_flow_in: used to determine if under-reamer needs to be activated for hole enlargement
|
|
87
|
+
:param is_design: the mode of function
|
|
88
|
+
:return: a wellbore
|
|
89
|
+
"""
|
|
90
|
+
if wits is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
api_worker = API()
|
|
94
|
+
|
|
95
|
+
casingstring_id = get_data_by_path(wits, "metadata.casing", str, default=None)
|
|
96
|
+
|
|
97
|
+
if not casingstring_id:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
casing_data_source = "design.data.casing" if is_design else "data.casing"
|
|
101
|
+
casingstring = get_config_by_id(casingstring_id, collection=casing_data_source)
|
|
102
|
+
|
|
103
|
+
if not casingstring:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
casing_outer_diameter = casingstring.get("data", {}).get("outer_diameter")
|
|
107
|
+
casing_inner_diameter = casingstring.get("data", {}).get("inner_diameter")
|
|
108
|
+
casing_top_depth = casingstring.get("data", {}).get("top_depth")
|
|
109
|
+
casing_bottom_depth = casingstring.get("data", {}).get("bottom_depth")
|
|
110
|
+
casing_linear_weight = casingstring.get("data", {}).get("linear_weight")
|
|
111
|
+
casing_components = casingstring.get("data", {}).get("components")
|
|
112
|
+
|
|
113
|
+
# For best results in running a casing/liner in the well, the components should be presented.
|
|
114
|
+
# If not we create an alternative.
|
|
115
|
+
if not casing_components:
|
|
116
|
+
# If it is a casing that reaches to the surface, we can use the provided
|
|
117
|
+
# information on the top to construct a casing string.
|
|
118
|
+
casing_components = [
|
|
119
|
+
{
|
|
120
|
+
"family": "casing_joints",
|
|
121
|
+
"linear_weight": casing_linear_weight,
|
|
122
|
+
"length": casing_bottom_depth - casing_top_depth,
|
|
123
|
+
"outer_diameter": casing_outer_diameter,
|
|
124
|
+
"inner_diameter": casing_inner_diameter,
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
# For liners, we use the top components for the casing joint part of the liner.
|
|
129
|
+
# From the surface to the liner hanger, we create the same component with
|
|
130
|
+
# the family of DP (it is not the actual casing part and will be removed later).
|
|
131
|
+
if casing_top_depth > Wellbore.DEPTH_THRESHOLD:
|
|
132
|
+
add_component = {
|
|
133
|
+
"family": "dp",
|
|
134
|
+
"linear_weight": casing_linear_weight,
|
|
135
|
+
"length": casing_top_depth,
|
|
136
|
+
"outer_diameter": casing_outer_diameter,
|
|
137
|
+
"inner_diameter": casing_inner_diameter,
|
|
138
|
+
}
|
|
139
|
+
casing_components.insert(0, add_component)
|
|
140
|
+
|
|
141
|
+
casingstring["data"]["components"] = casing_components
|
|
142
|
+
|
|
143
|
+
# setting active drillstring
|
|
144
|
+
casingstring = Drillstring(casingstring)
|
|
145
|
+
|
|
146
|
+
asset_id = wits["asset_id"]
|
|
147
|
+
|
|
148
|
+
bit_depth = get_data_by_path(wits, "data.bit_depth", float)
|
|
149
|
+
hole_depth = get_data_by_path(wits, "data.hole_depth", float)
|
|
150
|
+
|
|
151
|
+
query = "{data.inner_diameter#gt#%s}" % casing_inner_diameter
|
|
152
|
+
sort = "{data.inner_diameter:-1}"
|
|
153
|
+
previous_casings = api_worker.get(
|
|
154
|
+
path="/v1/data/corva", collection=casing_data_source, asset_id=asset_id, query=query, sort=sort, limit=100
|
|
155
|
+
).data
|
|
156
|
+
|
|
157
|
+
drilling_data_source = "data.well-sections" if is_design else "data.drillstring"
|
|
158
|
+
sort = "{data.diameter:-1}" if is_design else "{timestamp: -1}"
|
|
159
|
+
|
|
160
|
+
drill_sections = api_worker.get(
|
|
161
|
+
path="/v1/data/corva", collection=drilling_data_source, asset_id=asset_id, sort=sort, limit=100
|
|
162
|
+
).data
|
|
163
|
+
|
|
164
|
+
# use data.well-sections to create hole
|
|
165
|
+
if is_design:
|
|
166
|
+
hole = create_hole_using_well_sections_data(previous_casings, drill_sections, casing_bottom_depth)
|
|
167
|
+
|
|
168
|
+
# use data.drillstring to create hole
|
|
169
|
+
else:
|
|
170
|
+
hole = create_hole_use_drillstring_data(previous_casings, drill_sections, casing_outer_diameter, hole_depth)
|
|
171
|
+
|
|
172
|
+
mud_flow_in = get_mud_flow_in(mud_flow_in, wits)
|
|
173
|
+
|
|
174
|
+
return Wellbore(
|
|
175
|
+
drillstring=casingstring,
|
|
176
|
+
hole=hole,
|
|
177
|
+
bit_depth=bit_depth,
|
|
178
|
+
hole_depth=hole_depth,
|
|
179
|
+
mud_flow_in=mud_flow_in,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_detailed_hole_from_elements(
|
|
184
|
+
elements: List[dict],
|
|
185
|
+
previous_element_top_depth: float,
|
|
186
|
+
possible_top_depth: float,
|
|
187
|
+
extend_casing_to_top: bool,
|
|
188
|
+
is_riser: bool,
|
|
189
|
+
) -> Hole:
|
|
190
|
+
"""
|
|
191
|
+
This function converts elements of casing/riser components into a Hole.
|
|
192
|
+
:param elements: list casing/riser components
|
|
193
|
+
:param previous_element_top_depth: will be used as the bottom depth of current component
|
|
194
|
+
:param possible_top_depth: possible top depth to which a casing/riser can extend.
|
|
195
|
+
In case of a casing, it will extend to the btm of riser (if available)
|
|
196
|
+
:param extend_casing_to_top: if the casing/riser should extend to the top
|
|
197
|
+
:param is_riser: If its a riser or a cased hole
|
|
198
|
+
:return: Hole with all the components as HoleSections
|
|
199
|
+
"""
|
|
200
|
+
casing_hole: Hole = Hole()
|
|
201
|
+
|
|
202
|
+
elements = [
|
|
203
|
+
element
|
|
204
|
+
for index, element in enumerate(elements)
|
|
205
|
+
if check_casing_element_validity(element, index, extend_casing_to_top)
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
for element in reversed(elements):
|
|
209
|
+
element_id = element.get("inner_diameter", 0.0)
|
|
210
|
+
element_length = element.get("length", 0.0)
|
|
211
|
+
element_bottom = previous_element_top_depth
|
|
212
|
+
element_top = element_bottom - element_length
|
|
213
|
+
|
|
214
|
+
if element_top < possible_top_depth:
|
|
215
|
+
create_section_and_append(
|
|
216
|
+
casing_hole, element_id, possible_top_depth, element_bottom, HoleType.CASED_HOLE, is_riser
|
|
217
|
+
)
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
create_section_and_append(casing_hole, element_id, element_top, element_bottom, HoleType.CASED_HOLE, is_riser)
|
|
221
|
+
previous_element_top_depth = element_top
|
|
222
|
+
|
|
223
|
+
casing_hole.sections.reverse()
|
|
224
|
+
|
|
225
|
+
# checking and making sure the top element extends to the possible top depth.
|
|
226
|
+
# This check is needed because we are ignoring components of dp family which maybe at the top of the liner.
|
|
227
|
+
if compare_float(casing_hole.sections[0].top_depth, possible_top_depth, tolerance=0.01) != 0:
|
|
228
|
+
casing_hole.sections[0].set_top_depth(possible_top_depth)
|
|
229
|
+
casing_hole.sections[0].set_length()
|
|
230
|
+
|
|
231
|
+
return casing_hole
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def create_section_and_append(casing_hole, inner_diameter, top_depth, bottom_depth, hole_type, is_riser):
|
|
235
|
+
sec = HoleSection(
|
|
236
|
+
inner_diameter=inner_diameter, top_depth=top_depth, bottom_depth=bottom_depth, hole_type=hole_type
|
|
237
|
+
)
|
|
238
|
+
if is_riser:
|
|
239
|
+
sec = Riser(inner_diameter=inner_diameter, top_depth=top_depth, bottom_depth=bottom_depth, hole_type=hole_type)
|
|
240
|
+
|
|
241
|
+
casing_hole.sections.append(sec)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def check_casing_element_validity(casing_element: dict, idx: int, extend_casing_to_top: bool) -> bool:
|
|
245
|
+
"""
|
|
246
|
+
Check if the component is a casing and of a significant length
|
|
247
|
+
:param casing_element:
|
|
248
|
+
:param idx:
|
|
249
|
+
:param extend_casing_to_top:
|
|
250
|
+
:return:
|
|
251
|
+
"""
|
|
252
|
+
element_length: float = casing_element.get("length")
|
|
253
|
+
element_family: str = casing_element.get("family")
|
|
254
|
+
|
|
255
|
+
if not element_family or not isinstance(element_family, str):
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
# Ignoring casing components with length less than 20 feet and only components
|
|
259
|
+
# that are not top most or those that don't extend to top depth.
|
|
260
|
+
if element_length < 20.0 and (idx != 0 or not extend_casing_to_top):
|
|
261
|
+
return False
|
|
262
|
+
# Only accept the casing joints and riser components
|
|
263
|
+
return True if element_family in ["casing_joints", "riser"] else False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def set_casings(casings: List[dict]) -> Hole:
|
|
267
|
+
"""
|
|
268
|
+
Set the casing by providing the list of the jsons
|
|
269
|
+
:param casings: list of casings from API in json dict format
|
|
270
|
+
:return: None
|
|
271
|
+
"""
|
|
272
|
+
if not casings:
|
|
273
|
+
return Hole()
|
|
274
|
+
|
|
275
|
+
possible_casing_top_depth: float = 0.0
|
|
276
|
+
riser = next((csg for csg in casings if csg.get("data", {}).get("is_riser", False)), {})
|
|
277
|
+
|
|
278
|
+
if riser:
|
|
279
|
+
possible_casing_top_depth = riser.get("data").get("bottom_depth")
|
|
280
|
+
casings.remove(riser)
|
|
281
|
+
|
|
282
|
+
# sorting the casings in the ascending order (the smaller the inner_diameter the earlier it is added)
|
|
283
|
+
casings = sorted(casings, key=lambda csg: csg.get("data", {}).get("inner_diameter"))
|
|
284
|
+
|
|
285
|
+
# finding if a liner is overlapping outer liners
|
|
286
|
+
# this is common in offshore drilling where they set the casings at the sea floor
|
|
287
|
+
index = 1
|
|
288
|
+
while index < len(casings):
|
|
289
|
+
top_depth_inner = casings[index - 1].get("data", {}).get("top_depth", 1000)
|
|
290
|
+
top_depth_outer = casings[index].get("data", {}).get("top_depth", 1000)
|
|
291
|
+
if top_depth_outer + Wellbore.DEPTH_THRESHOLD > top_depth_inner:
|
|
292
|
+
casings.pop(index)
|
|
293
|
+
else:
|
|
294
|
+
index += 1
|
|
295
|
+
|
|
296
|
+
# reversing the order (the larger the inner_diameter the earlier it is)
|
|
297
|
+
casings = reversed(casings)
|
|
298
|
+
|
|
299
|
+
casing_holes = []
|
|
300
|
+
for index, casing in enumerate(casings):
|
|
301
|
+
casing_hole = Hole()
|
|
302
|
+
casing_object: dict = casing.get("data", {})
|
|
303
|
+
casing_bottom = casing_object.get("bottom_depth")
|
|
304
|
+
casing_top = possible_casing_top_depth
|
|
305
|
+
current_casing_top_depth = casing_object.get("top_depth")
|
|
306
|
+
|
|
307
|
+
# The top depth of the first component should reach to surface regardless
|
|
308
|
+
# Case 1 - Land (Normal)
|
|
309
|
+
# Case 2 - Riser
|
|
310
|
+
# Case 3 - Riserless
|
|
311
|
+
if not (index == 0 and current_casing_top_depth < Wellbore.DEPTH_THRESHOLD):
|
|
312
|
+
casing_top = current_casing_top_depth
|
|
313
|
+
|
|
314
|
+
casing_elements: List[dict] = casing_object.get("components", [])
|
|
315
|
+
if not casing_elements:
|
|
316
|
+
hole_section = HoleSection(**casing_object, hole_type=HoleType.CASED_HOLE)
|
|
317
|
+
casing_hole.sections.append(hole_section)
|
|
318
|
+
else:
|
|
319
|
+
previous_casing_element_top_depth = casing_bottom
|
|
320
|
+
# Individual casing elements have to be looped in reverse order to correctly account
|
|
321
|
+
# for the topmost element going to surface is not liner
|
|
322
|
+
extend_casing_to_top: bool = False
|
|
323
|
+
if index == 0:
|
|
324
|
+
extend_casing_to_top = True
|
|
325
|
+
casing_hole = get_detailed_hole_from_elements(
|
|
326
|
+
casing_elements, previous_casing_element_top_depth, casing_top, extend_casing_to_top, False
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
casing_holes.append(casing_hole)
|
|
330
|
+
|
|
331
|
+
# Now adding the components of the riser to casingHoles
|
|
332
|
+
if riser:
|
|
333
|
+
riser_elements: List[dict] = riser.get("data", {}).get("components", [])
|
|
334
|
+
previous_riser_element_top_depth = possible_casing_top_depth
|
|
335
|
+
riser_hole: Hole = get_detailed_hole_from_elements(
|
|
336
|
+
riser_elements, previous_riser_element_top_depth, 0.0, True, True
|
|
337
|
+
)
|
|
338
|
+
casing_holes.insert(0, riser_hole)
|
|
339
|
+
|
|
340
|
+
return Hole.merge_holes(casing_holes)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def add_open_holes(
|
|
344
|
+
hole: Hole, drillstrings: List[dict], smallest_cased_hole_diameter: float, hole_depth: float
|
|
345
|
+
) -> Hole:
|
|
346
|
+
for ds in drillstrings:
|
|
347
|
+
bit = Drillstring(ds).get_bit()
|
|
348
|
+
if not bit:
|
|
349
|
+
drillstring_id = ds.get("_id")
|
|
350
|
+
raise MissingConfigError(f"Bit was not found in the drillstring with _id='{drillstring_id}'")
|
|
351
|
+
|
|
352
|
+
bit_size = bit.size
|
|
353
|
+
# only keeping the DSs that ran below the smallest casing
|
|
354
|
+
if bit_size > smallest_cased_hole_diameter:
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
start_depth = ds.get("data", {}).get("start_depth")
|
|
358
|
+
|
|
359
|
+
# if the open hole size increases then the prior open hole section IDs should be updated
|
|
360
|
+
for section in reversed(hole):
|
|
361
|
+
if section.hole_type == HoleType.OPEN_HOLE:
|
|
362
|
+
if section.inner_diameter < bit_size:
|
|
363
|
+
section.inner_diameter = bit_size
|
|
364
|
+
else:
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
previous_open_hole_size = hole.get_open_hole()[-1].inner_diameter if len(hole.get_open_hole()) > 0 else None
|
|
368
|
+
|
|
369
|
+
# if the previous drillstring section inner diameter is equal to this bit size
|
|
370
|
+
# there is no need to add a new drillstring section
|
|
371
|
+
if previous_open_hole_size and previous_open_hole_size == bit_size:
|
|
372
|
+
continue
|
|
373
|
+
|
|
374
|
+
# if due to the side track the hole depth reduced, then it should update the setting depths in the open hole
|
|
375
|
+
hole.sections = [
|
|
376
|
+
sec for sec in hole.sections if sec.hole_type != HoleType.OPEN_HOLE or sec.top_depth <= hole_depth
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
# if a new open-hole section is created in addition to the previous one,
|
|
380
|
+
# the previous bottom depth may need to be modified.
|
|
381
|
+
previous_hole_section = hole.get_last_hole_section()
|
|
382
|
+
|
|
383
|
+
top_depth = hole.get_bottom_depth()
|
|
384
|
+
bottom_depth = top_depth + 0.0001
|
|
385
|
+
current_hole_section = HoleSection(
|
|
386
|
+
top_depth=top_depth, bottom_depth=bottom_depth, inner_diameter=bit_size, hole_type=HoleType.OPEN_HOLE
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if previous_hole_section:
|
|
390
|
+
# The casing bottom depth can't be modified if the difference is more than 300 ft
|
|
391
|
+
cased_hole_condition = (
|
|
392
|
+
previous_hole_section.hole_type == HoleType.CASED_HOLE
|
|
393
|
+
and start_depth - previous_hole_section.bottom_depth < 300
|
|
394
|
+
)
|
|
395
|
+
if cased_hole_condition or previous_hole_section.hole_type == HoleType.OPEN_HOLE:
|
|
396
|
+
previous_hole_section.set_bottom_depth(start_depth)
|
|
397
|
+
previous_hole_section.set_length()
|
|
398
|
+
|
|
399
|
+
if current_hole_section.eq_without_length(previous_hole_section):
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
current_hole_section.set_length()
|
|
403
|
+
hole.add_section(current_hole_section)
|
|
404
|
+
|
|
405
|
+
# In some cases the DS setting depth doesn't match the hole depth so the wits data overrides it;
|
|
406
|
+
# this is only applied to the top depth of the last section and bottom depth of the previous section.
|
|
407
|
+
# Note: only in cases that the wits hole_depth < setting depth
|
|
408
|
+
# Condition: the number of open hole sections should be at least two needs to be satisfied first
|
|
409
|
+
if len(hole) - len(hole.get_cased_hole()) > 1 and 1 < hole_depth < hole[-1].top_depth:
|
|
410
|
+
hole[-2].set_bottom_depth(hole_depth)
|
|
411
|
+
hole[-2].set_length()
|
|
412
|
+
|
|
413
|
+
hole[-1].set_top_depth(hole_depth)
|
|
414
|
+
hole[-1].set_length()
|
|
415
|
+
|
|
416
|
+
return hole
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def get_mud_flow_in(mud_flow_in: Union[float, None], wits: dict) -> Union[float, None]:
|
|
420
|
+
"""
|
|
421
|
+
Get the mud flow rate from wits if `mud_flow_in` is None
|
|
422
|
+
:param mud_flow_in:
|
|
423
|
+
:param wits:
|
|
424
|
+
:return:
|
|
425
|
+
"""
|
|
426
|
+
if mud_flow_in is not None:
|
|
427
|
+
return mud_flow_in
|
|
428
|
+
|
|
429
|
+
if not wits:
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
return get_data_by_path(wits, "data.mud_flow_in", default=None)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def create_hole_use_drillstring_data(
|
|
436
|
+
previous_casings: List[dict], drillstrings: List[dict], casing_outer_diameter: float, hole_depth: float
|
|
437
|
+
) -> Union[Hole, None]:
|
|
438
|
+
"""
|
|
439
|
+
Create a hole using data.drillstring collection
|
|
440
|
+
:param previous_casings: previous casings data
|
|
441
|
+
:param drillstrings: drillstring data
|
|
442
|
+
:param casing_outer_diameter: outer diameter of casing in inch
|
|
443
|
+
:param hole_depth: bottom depth of the hole in ft
|
|
444
|
+
:return: a hole
|
|
445
|
+
"""
|
|
446
|
+
drillstrings = [
|
|
447
|
+
string
|
|
448
|
+
for string in drillstrings
|
|
449
|
+
if (string.get("data", {}).get("components", [{}])[-1].get("size") or -1) > casing_outer_diameter
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
hole = set_casings(previous_casings)
|
|
453
|
+
smallest_cased_hole_diameter = hole.get_min_inner_diameter() or 100
|
|
454
|
+
|
|
455
|
+
return add_open_holes(hole, drillstrings, smallest_cased_hole_diameter, hole_depth)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def create_hole_using_well_sections_data(
|
|
459
|
+
previous_casings: List[dict], drill_sections: List[dict], casing_bottom_depth: float
|
|
460
|
+
) -> Union[Hole, None]:
|
|
461
|
+
"""
|
|
462
|
+
Create a hole using data.well-sections collection
|
|
463
|
+
:param previous_casings: previous casings data
|
|
464
|
+
:param drill_sections: well sections data
|
|
465
|
+
:param casing_bottom_depth: bottom depth of casing in ft
|
|
466
|
+
:return: a hole
|
|
467
|
+
"""
|
|
468
|
+
# setting cased-hole sections
|
|
469
|
+
hole = Hole()
|
|
470
|
+
previous_casing_bottom_depth = 0
|
|
471
|
+
if previous_casings:
|
|
472
|
+
previous_casing_bottom_depth = previous_casings[-1].get("data", {}).get("bottom_depth")
|
|
473
|
+
hole.set_casings(previous_casings)
|
|
474
|
+
|
|
475
|
+
# setting open-hole sections
|
|
476
|
+
for section in drill_sections:
|
|
477
|
+
section_data = section.get("data", {})
|
|
478
|
+
open_hole_top_depth = section_data.get("top_depth")
|
|
479
|
+
open_hole_bottom_depth = section_data.get("bottom_depth")
|
|
480
|
+
open_hole_diameter = section_data.get("diameter")
|
|
481
|
+
|
|
482
|
+
if (
|
|
483
|
+
previous_casing_bottom_depth
|
|
484
|
+
<= open_hole_top_depth
|
|
485
|
+
<= open_hole_bottom_depth
|
|
486
|
+
<= casing_bottom_depth + Wellbore.DEPTH_THRESHOLD
|
|
487
|
+
):
|
|
488
|
+
open_hole = HoleSection(
|
|
489
|
+
inner_diameter=open_hole_diameter,
|
|
490
|
+
top_depth=open_hole_top_depth,
|
|
491
|
+
bottom_depth=open_hole_bottom_depth,
|
|
492
|
+
hole_type=HoleType.OPEN_HOLE,
|
|
493
|
+
)
|
|
494
|
+
hole.add_section(open_hole)
|
|
495
|
+
|
|
496
|
+
return hole
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_unique_measured_depths(*items) -> List:
|
|
5
|
+
"""
|
|
6
|
+
Find the edge measure depths of the given items
|
|
7
|
+
:param items: an object of class Drilling or Hole
|
|
8
|
+
:return:
|
|
9
|
+
"""
|
|
10
|
+
mds = {depth for item in items for sec in item for depth in (sec.top_depth, sec.bottom_depth)}
|
|
11
|
+
mds = sorted(list(mds)) # sorting them
|
|
12
|
+
return mds
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from worker.data.operations import nanround
|
|
4
|
+
from worker.data.serialization import serialization
|
|
5
|
+
from worker.wellbore.model.drillstring_components import Pipe
|
|
6
|
+
from worker.wellbore.model.element import Element
|
|
7
|
+
from worker.wellbore.model.enums import HoleType
|
|
8
|
+
from worker.wellbore.model.hole_section import HoleSection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@serialization
|
|
12
|
+
class Ann(Element):
|
|
13
|
+
"""
|
|
14
|
+
This class is extended to create annulus segment
|
|
15
|
+
It can be done by passing arguments or pipe and hole section
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
SERIALIZED_VARIABLES = {
|
|
19
|
+
"top_depth": float,
|
|
20
|
+
"bottom_depth": float,
|
|
21
|
+
"length": float,
|
|
22
|
+
"inner_diameter_hole": float,
|
|
23
|
+
"outer_diameter_pipe": float,
|
|
24
|
+
"outer_diameter_tooljoint": float,
|
|
25
|
+
"tool_joint_length_ratio": float,
|
|
26
|
+
"hole_type": HoleType,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def __init__(self, **kwargs):
|
|
30
|
+
super(Ann, self).__init__(**kwargs)
|
|
31
|
+
|
|
32
|
+
for key in [
|
|
33
|
+
"inner_diameter_hole",
|
|
34
|
+
"outer_diameter_pipe",
|
|
35
|
+
"outer_diameter_tooljoint",
|
|
36
|
+
"tool_joint_length_ratio",
|
|
37
|
+
"hole_type",
|
|
38
|
+
]:
|
|
39
|
+
setattr(self, key, kwargs.get(key))
|
|
40
|
+
|
|
41
|
+
def set_properties_from_pipe_section(self, pipe: Pipe):
|
|
42
|
+
self.outer_diameter_pipe = pipe.outer_diameter
|
|
43
|
+
self.outer_diameter_tooljoint = pipe.outer_diameter_tooljoint
|
|
44
|
+
self.tool_joint_length_ratio = pipe.tool_joint_length_ratio
|
|
45
|
+
|
|
46
|
+
def set_properties_from_hole(self, hole_section: HoleSection):
|
|
47
|
+
self.inner_diameter_hole = hole_section.inner_diameter
|
|
48
|
+
self.hole_type = hole_section.hole_type
|
|
49
|
+
|
|
50
|
+
def compute_get_area_pipe_body(self) -> float:
|
|
51
|
+
"""
|
|
52
|
+
Compute the annulus flow area consisting of the hole section and pipe body
|
|
53
|
+
:return: area in INCH^2
|
|
54
|
+
"""
|
|
55
|
+
return np.pi / 4 * (self.inner_diameter_hole**2 - self.outer_diameter_pipe**2)
|
|
56
|
+
|
|
57
|
+
def compute_get_area_pipe_tooljoint(self) -> float:
|
|
58
|
+
"""
|
|
59
|
+
Compute the annulus flow area consisting of the hole section and pipe tooljoint
|
|
60
|
+
:return: area in INCH^2
|
|
61
|
+
"""
|
|
62
|
+
return np.pi / 4 * (self.inner_diameter_hole**2 - self.outer_diameter_tooljoint**2)
|
|
63
|
+
|
|
64
|
+
# override
|
|
65
|
+
def compute_get_area(self) -> float:
|
|
66
|
+
"""
|
|
67
|
+
Compute the total area of the hole section and return the result
|
|
68
|
+
:return: cross sectional area of the hole section in INCH^2
|
|
69
|
+
"""
|
|
70
|
+
area = self.compute_get_area_pipe_body()
|
|
71
|
+
r = self.tool_joint_length_ratio
|
|
72
|
+
if r > 0:
|
|
73
|
+
area = r * self.compute_get_area_pipe_tooljoint() + (1 - r) * area
|
|
74
|
+
return area
|
|
75
|
+
|
|
76
|
+
# override
|
|
77
|
+
def compute_get_volume(self):
|
|
78
|
+
"""
|
|
79
|
+
This method computes and returns the total volume of the annulus section
|
|
80
|
+
:return: volume in FOOT^3
|
|
81
|
+
"""
|
|
82
|
+
area = self.compute_get_area() # in INCH^2
|
|
83
|
+
volume = area / 144 * self.length # in FOOT^3
|
|
84
|
+
return volume
|
|
85
|
+
|
|
86
|
+
def eq_without_length(self, other):
|
|
87
|
+
if not isinstance(other, Ann):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
if self.inner_diameter_hole != other.inner_diameter_hole:
|
|
91
|
+
return False
|
|
92
|
+
if self.outer_diameter_pipe != other.outer_diameter_pipe:
|
|
93
|
+
return False
|
|
94
|
+
if self.hole_type != other.hole_type:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
def __repr__(self):
|
|
100
|
+
return (
|
|
101
|
+
f"{nanround(self.inner_diameter_hole, 2):>6} in / {nanround(self.outer_diameter_pipe, 2):>6} in, "
|
|
102
|
+
f"{super().__repr__()}"
|
|
103
|
+
)
|