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,439 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from worker.data.alert import Alerter
|
|
8
|
+
from worker.data.operations import equal, is_stream_app, nanround
|
|
9
|
+
from worker.data.serialization import serialization
|
|
10
|
+
from worker.data.unit_conversions import US_LIQUID_GAL_to_INCH3
|
|
11
|
+
from worker.mixins.logging import Logger
|
|
12
|
+
from worker.wellbore.model.element import Element
|
|
13
|
+
from worker.wellbore.model.enums import HoleType, PipeMaterial, PipeType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@serialization
|
|
17
|
+
class Pipe(Element):
|
|
18
|
+
SERIALIZED_VARIABLES = {
|
|
19
|
+
"top_depth": float,
|
|
20
|
+
"bottom_depth": float,
|
|
21
|
+
"length": float,
|
|
22
|
+
"inner_diameter": float,
|
|
23
|
+
"outer_diameter": float,
|
|
24
|
+
"joint_length": float,
|
|
25
|
+
"max_outer_diameter": float,
|
|
26
|
+
"inner_diameter_tooljoint": float,
|
|
27
|
+
"outer_diameter_tooljoint": float,
|
|
28
|
+
"tool_joint_length_per_joint": float,
|
|
29
|
+
"linear_mass_in_air": float,
|
|
30
|
+
"order": float,
|
|
31
|
+
"pipe_material": PipeMaterial,
|
|
32
|
+
"grade": float,
|
|
33
|
+
"tool_joint_length_ratio": float,
|
|
34
|
+
"pipe_type": PipeType,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
The pipe object can be used for the following components directly:
|
|
39
|
+
- drill pipe (DP)
|
|
40
|
+
- drill collar (DC)
|
|
41
|
+
- heavy weight drill pipe (HWDP)
|
|
42
|
+
- subs
|
|
43
|
+
- stabilizers
|
|
44
|
+
|
|
45
|
+
This class is extended by the other drillpipe components.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, **kwargs):
|
|
49
|
+
super(Pipe, self).__init__(**kwargs)
|
|
50
|
+
|
|
51
|
+
self.inner_diameter = kwargs["inner_diameter"]
|
|
52
|
+
self.outer_diameter = kwargs["outer_diameter"]
|
|
53
|
+
|
|
54
|
+
self.joint_length = kwargs.get("component_length", kwargs.get("joint_length"))
|
|
55
|
+
self.max_outer_diameter = kwargs.get("max_outer_diameter")
|
|
56
|
+
self.inner_diameter_tooljoint = kwargs.get("inner_diameter_tooljoint")
|
|
57
|
+
self.outer_diameter_tooljoint = kwargs.get("outer_diameter_tooljoint")
|
|
58
|
+
self.tool_joint_length_per_joint = kwargs.get("length_tooljoint") or kwargs.get("tool_joint_length_per_joint")
|
|
59
|
+
self.linear_mass_in_air = kwargs.get("linear_weight", kwargs.get("linear_mass_in_air"))
|
|
60
|
+
self.order = kwargs.get("order")
|
|
61
|
+
|
|
62
|
+
self.pipe_material = kwargs.get("material")
|
|
63
|
+
self.grade = kwargs.get("grade")
|
|
64
|
+
|
|
65
|
+
self.set_tool_joint_length_ratio()
|
|
66
|
+
|
|
67
|
+
family = kwargs.get("family") or ""
|
|
68
|
+
self.pipe_type = PipeType.determine_type(family)
|
|
69
|
+
|
|
70
|
+
def get_max_outer_diameter(self):
|
|
71
|
+
return self.max_outer_diameter or self.outer_diameter_tooljoint or self.outer_diameter
|
|
72
|
+
|
|
73
|
+
def set_tool_joint_length_ratio(self):
|
|
74
|
+
if self.tool_joint_length_per_joint is None or not self.joint_length:
|
|
75
|
+
self.tool_joint_length_ratio = 0
|
|
76
|
+
else:
|
|
77
|
+
self.tool_joint_length_ratio = self.tool_joint_length_per_joint / self.joint_length
|
|
78
|
+
|
|
79
|
+
def is_valid_tool_joint(self):
|
|
80
|
+
"""
|
|
81
|
+
If there is a valid tool joint or not
|
|
82
|
+
:return:
|
|
83
|
+
"""
|
|
84
|
+
if self.outer_diameter_tooljoint is None:
|
|
85
|
+
return False
|
|
86
|
+
if self.inner_diameter_tooljoint is None:
|
|
87
|
+
return False
|
|
88
|
+
if not self.tool_joint_length_ratio:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def get_pipe_id_area(self):
|
|
94
|
+
"""
|
|
95
|
+
Compute the total inner area of the pipe body
|
|
96
|
+
:return: inner area of the pipe body in INCH^2
|
|
97
|
+
"""
|
|
98
|
+
return np.pi / 4 * (self.inner_diameter**2)
|
|
99
|
+
|
|
100
|
+
def get_pipe_od_area(self):
|
|
101
|
+
"""
|
|
102
|
+
Compute the total outer area of the pipe body
|
|
103
|
+
:return: outer area of the pipe body in INCH^2
|
|
104
|
+
"""
|
|
105
|
+
return np.pi / 4 * (self.outer_diameter**2)
|
|
106
|
+
|
|
107
|
+
def get_pipe_tool_joint(self):
|
|
108
|
+
"""
|
|
109
|
+
Create a pipe with its id and od equal to the tool joint values (if available)
|
|
110
|
+
:return:
|
|
111
|
+
"""
|
|
112
|
+
p = deepcopy(self)
|
|
113
|
+
p.inner_diameter = p.inner_diameter_tooljoint or p.inner_diameter
|
|
114
|
+
p.outer_diameter = p.outer_diameter_tooljoint or p.outer_diameter
|
|
115
|
+
return p
|
|
116
|
+
|
|
117
|
+
def compute_inner_area_tool_joint_adjusted(self):
|
|
118
|
+
"""
|
|
119
|
+
Compute the inner area of the pipe adjusted for the tool joint
|
|
120
|
+
:return: the area in INCH^2
|
|
121
|
+
"""
|
|
122
|
+
area_body = self.get_pipe_id_area() # in INCH^2
|
|
123
|
+
if not self.is_valid_tool_joint():
|
|
124
|
+
return area_body
|
|
125
|
+
|
|
126
|
+
area_tj = np.pi / 4 * (self.inner_diameter_tooljoint**2) # in INCH^2
|
|
127
|
+
return area_body * (1 - self.tool_joint_length_ratio) + area_tj * self.tool_joint_length_ratio
|
|
128
|
+
|
|
129
|
+
def compute_outer_area_tool_joint_adjusted(self):
|
|
130
|
+
"""
|
|
131
|
+
Compute the outer area of the pipe adjusted for the tool joint
|
|
132
|
+
:return: the area in INCH^2
|
|
133
|
+
"""
|
|
134
|
+
area_body = self.get_pipe_od_area() # in INCH^2
|
|
135
|
+
if not self.is_valid_tool_joint():
|
|
136
|
+
return area_body
|
|
137
|
+
|
|
138
|
+
area_tj = np.pi / 4 * (self.outer_diameter_tooljoint**2) # in INCH^2
|
|
139
|
+
r = self.tool_joint_length_ratio
|
|
140
|
+
return area_body * (1 - r) + area_tj * r
|
|
141
|
+
|
|
142
|
+
def compute_inner_diameter_tool_joint_adjusted(self):
|
|
143
|
+
"""
|
|
144
|
+
Compute the inner diameter of the pipe adjusting for the tool joint
|
|
145
|
+
:return: the adjusted inner diameter of the pipe in INCH
|
|
146
|
+
"""
|
|
147
|
+
di_body = self.inner_diameter
|
|
148
|
+
if not self.is_valid_tool_joint():
|
|
149
|
+
return di_body
|
|
150
|
+
|
|
151
|
+
di_tj = self.inner_diameter_tooljoint
|
|
152
|
+
r = self.tool_joint_length_ratio
|
|
153
|
+
return di_body * (1 - r) + di_tj * r
|
|
154
|
+
|
|
155
|
+
def compute_outer_diameter_tool_joint_adjusted(self):
|
|
156
|
+
"""
|
|
157
|
+
Compute the outer diameter of the pipe adjusting for the tool joint
|
|
158
|
+
:return: the adjusted outer diameter of the pipe in INCH
|
|
159
|
+
"""
|
|
160
|
+
do_body = self.outer_diameter
|
|
161
|
+
if not self.is_valid_tool_joint():
|
|
162
|
+
return do_body
|
|
163
|
+
|
|
164
|
+
do_tj = self.outer_diameter_tooljoint
|
|
165
|
+
r = self.tool_joint_length_ratio
|
|
166
|
+
return do_body * (1 - r) + do_tj * r
|
|
167
|
+
|
|
168
|
+
def compute_body_cross_sectional_area_tj_adjusted(self):
|
|
169
|
+
"""
|
|
170
|
+
Compute body cross sectional area adjusted for the tool joint
|
|
171
|
+
:return: area in INCH^2
|
|
172
|
+
"""
|
|
173
|
+
return self.compute_outer_area_tool_joint_adjusted() - self.compute_inner_area_tool_joint_adjusted()
|
|
174
|
+
|
|
175
|
+
# override
|
|
176
|
+
def compute_get_area(self):
|
|
177
|
+
return self.compute_inner_area_tool_joint_adjusted()
|
|
178
|
+
|
|
179
|
+
def compute_body_volume_tj_adjusted(self):
|
|
180
|
+
"""
|
|
181
|
+
Compute body volume adjusted for the tool joint
|
|
182
|
+
:return: volume in FOOT^3
|
|
183
|
+
"""
|
|
184
|
+
return self.compute_body_cross_sectional_area_tj_adjusted() / 144 * self.length
|
|
185
|
+
|
|
186
|
+
def compute_outer_volume_tj_adjusted(self):
|
|
187
|
+
"""
|
|
188
|
+
Compute outer volume adjusted for the tool joint
|
|
189
|
+
:return: volume in FOOT^3
|
|
190
|
+
"""
|
|
191
|
+
return self.compute_outer_area_tool_joint_adjusted() / 144 * self.length
|
|
192
|
+
|
|
193
|
+
def compute_inner_volume_tj_adjusted(self):
|
|
194
|
+
"""
|
|
195
|
+
Compute inner volume adjusted for the tool joint
|
|
196
|
+
:return: volume in FOOT^3
|
|
197
|
+
"""
|
|
198
|
+
return self.compute_inner_area_tool_joint_adjusted() / 144 * self.length
|
|
199
|
+
|
|
200
|
+
# override
|
|
201
|
+
def compute_get_volume(self):
|
|
202
|
+
return self.compute_inner_volume_tj_adjusted()
|
|
203
|
+
|
|
204
|
+
def calculate_linear_mass(self):
|
|
205
|
+
"""
|
|
206
|
+
Compute the pipe linear mass in air from pipe materials
|
|
207
|
+
:return: pipe linear mass in LB/FOOT
|
|
208
|
+
"""
|
|
209
|
+
density = self.pipe_material.get_density() # PPG
|
|
210
|
+
pipe_body_area = self.get_pipe_body_area() # INCH^2
|
|
211
|
+
linear_mass = density * pipe_body_area * (US_LIQUID_GAL_to_INCH3 * 12) # lb/ft
|
|
212
|
+
return linear_mass
|
|
213
|
+
|
|
214
|
+
def __eq__(self, other):
|
|
215
|
+
if not isinstance(other, (Pipe, Bit)):
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
if self.eq_without_length(other) is False:
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
return self.length == other.length
|
|
222
|
+
|
|
223
|
+
def eq_without_length(self, other):
|
|
224
|
+
params = [
|
|
225
|
+
"pipe_type",
|
|
226
|
+
"inner_diameter",
|
|
227
|
+
"outer_diameter",
|
|
228
|
+
"inner_diameter_tooljoint",
|
|
229
|
+
"outer_diameter_tooljoint",
|
|
230
|
+
"linear_mass_in_air",
|
|
231
|
+
]
|
|
232
|
+
return equal(self, other, params)
|
|
233
|
+
|
|
234
|
+
def eq_id_and_od(self, other):
|
|
235
|
+
params = ["inner_diameter", "outer_diameter"]
|
|
236
|
+
return equal(self, other, params)
|
|
237
|
+
|
|
238
|
+
def __repr__(self):
|
|
239
|
+
return (
|
|
240
|
+
f"{self.pipe_type.name:13}"
|
|
241
|
+
f"{nanround(self.inner_diameter, 2):>6} in / "
|
|
242
|
+
f"{nanround(self.outer_diameter, 2):>6} in, "
|
|
243
|
+
f"{nanround(self.linear_mass_in_air, 2):>6} lb/ft, "
|
|
244
|
+
f"{super().__repr__()}"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@serialization
|
|
249
|
+
class MWD(Pipe):
|
|
250
|
+
# TODO extra items to be implemented later
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@serialization
|
|
255
|
+
class PDM(Pipe):
|
|
256
|
+
# TODO extra items to be implemented later
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@serialization
|
|
261
|
+
class RSS(Pipe):
|
|
262
|
+
# TODO extra items to be implemented later
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@serialization
|
|
267
|
+
class Agitator(Pipe):
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@serialization
|
|
272
|
+
class Bit(Element):
|
|
273
|
+
SERIALIZED_VARIABLES = {
|
|
274
|
+
"top_depth": float,
|
|
275
|
+
"bottom_depth": float,
|
|
276
|
+
"length": float,
|
|
277
|
+
"size": float,
|
|
278
|
+
"tfa": float,
|
|
279
|
+
"pipe_type": PipeType,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# TODO extra items to be implemented later
|
|
283
|
+
def __init__(self, **kwargs):
|
|
284
|
+
super(Bit, self).__init__(**kwargs)
|
|
285
|
+
|
|
286
|
+
self.size = float(kwargs["size"])
|
|
287
|
+
self.tfa = float(kwargs["tfa"])
|
|
288
|
+
self.pipe_type = PipeType.BIT
|
|
289
|
+
|
|
290
|
+
# override
|
|
291
|
+
def compute_get_area(self):
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
# override
|
|
295
|
+
def compute_get_volume(self):
|
|
296
|
+
return 0
|
|
297
|
+
|
|
298
|
+
def __repr__(self):
|
|
299
|
+
return (
|
|
300
|
+
f"{self.pipe_type.name:13}"
|
|
301
|
+
f"{nanround(self.size, 2)} in - "
|
|
302
|
+
f"tfa={nanround(self.tfa, 2)} in^2, "
|
|
303
|
+
f"{super().__repr__()}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@serialization
|
|
308
|
+
class UnderReamer(Pipe):
|
|
309
|
+
SERIALIZED_VARIABLES = deepcopy(Pipe.SERIALIZED_VARIABLES)
|
|
310
|
+
SERIALIZED_VARIABLES.update(
|
|
311
|
+
{
|
|
312
|
+
"activation_logic": str,
|
|
313
|
+
"ur_opened_od": float,
|
|
314
|
+
"ur_opened_depth": float,
|
|
315
|
+
"flow_rate": float,
|
|
316
|
+
"_is_opened": bool,
|
|
317
|
+
"tfa": float,
|
|
318
|
+
"alerted_at_timestamp": float,
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
class UnderReamerActivationType(Enum):
|
|
323
|
+
ALREADY_ACTIVATED = "already_activated"
|
|
324
|
+
MUD_FLOW_RATE = "flowrate_activated"
|
|
325
|
+
OPEN_HOLE = "open_hole_activated"
|
|
326
|
+
UNACTIVATED = "did_not_activate"
|
|
327
|
+
|
|
328
|
+
@serialization
|
|
329
|
+
class ActivationLogic(Enum):
|
|
330
|
+
OPEN_HOLE_ACTIVATED = "open_hole_activated"
|
|
331
|
+
FLOW_ACTIVATED = "flow_activated"
|
|
332
|
+
DEPTH_ACTIVATION = "depth_activation"
|
|
333
|
+
|
|
334
|
+
def __init__(self, **kwargs):
|
|
335
|
+
super(UnderReamer, self).__init__(**kwargs)
|
|
336
|
+
|
|
337
|
+
self._is_opened: bool = False
|
|
338
|
+
self.alerted_at_timestamp: float = 0.0
|
|
339
|
+
self.ALERT_BACKOFF_TIME = 3600
|
|
340
|
+
|
|
341
|
+
self.tfa = float(kwargs["tfa"])
|
|
342
|
+
self.ur_opened_depth = kwargs.get("ur_opened_depth")
|
|
343
|
+
self.ur_opened_od = float(kwargs["ur_opened_od"])
|
|
344
|
+
self.flow_rate = float(kwargs.get("flow_rate"))
|
|
345
|
+
self.activation_logic = self.ActivationLogic(kwargs["activation_logic"])
|
|
346
|
+
self.pipe_type = PipeType.UNDERREAMER
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def discharge_coefficient(self) -> float:
|
|
350
|
+
return 0.95
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def is_opened(self) -> bool:
|
|
354
|
+
"""
|
|
355
|
+
This property is needed for enabling split flow
|
|
356
|
+
:return:
|
|
357
|
+
"""
|
|
358
|
+
return self._is_opened and self.tfa > 0.0
|
|
359
|
+
|
|
360
|
+
def set_opened(self, opened: bool):
|
|
361
|
+
self._is_opened = opened
|
|
362
|
+
|
|
363
|
+
def get_activation_type(self, mud_flow_rate: float):
|
|
364
|
+
opened_depth = self.ur_opened_depth or 10_000_000.0
|
|
365
|
+
|
|
366
|
+
# if it was activated before no need to check how it was activated just use `ur_opened_depth` key
|
|
367
|
+
if self.top_depth >= opened_depth:
|
|
368
|
+
return self.UnderReamerActivationType.ALREADY_ACTIVATED
|
|
369
|
+
|
|
370
|
+
# if it was never activated and logic is open hole
|
|
371
|
+
if self.activation_logic == self.ActivationLogic.OPEN_HOLE_ACTIVATED and not self.ur_opened_depth:
|
|
372
|
+
return self.UnderReamerActivationType.OPEN_HOLE
|
|
373
|
+
|
|
374
|
+
# if it was never activated and logic is flow activated
|
|
375
|
+
if mud_flow_rate > self.flow_rate and not self.ur_opened_depth:
|
|
376
|
+
return self.UnderReamerActivationType.MUD_FLOW_RATE
|
|
377
|
+
|
|
378
|
+
# Otherwise unactivated
|
|
379
|
+
return self.UnderReamerActivationType.UNACTIVATED
|
|
380
|
+
|
|
381
|
+
def activate_under_reamer(self, mud_flow_rate, start_reaming_depth, hole_type: HoleType):
|
|
382
|
+
activation_mode = self.get_activation_type(mud_flow_rate)
|
|
383
|
+
|
|
384
|
+
if activation_mode == self.UnderReamerActivationType.UNACTIVATED:
|
|
385
|
+
self.ur_opened_depth = None
|
|
386
|
+
return activation_mode
|
|
387
|
+
|
|
388
|
+
# If OpenHole activated then set the opened depth to lastCasingDepth
|
|
389
|
+
if activation_mode == self.UnderReamerActivationType.OPEN_HOLE:
|
|
390
|
+
if start_reaming_depth is None:
|
|
391
|
+
Logger.warn("Unable to find the last casing depth")
|
|
392
|
+
self.ur_opened_depth = None
|
|
393
|
+
return self.UnderReamerActivationType.UNACTIVATED
|
|
394
|
+
|
|
395
|
+
# No need to raise Alert here
|
|
396
|
+
if start_reaming_depth >= self.top_depth:
|
|
397
|
+
self.ur_opened_depth = None
|
|
398
|
+
return self.UnderReamerActivationType.UNACTIVATED
|
|
399
|
+
|
|
400
|
+
self.ur_opened_depth = start_reaming_depth
|
|
401
|
+
return activation_mode
|
|
402
|
+
|
|
403
|
+
# If mud activated then set the opened depth as top depth of UR
|
|
404
|
+
if self.ur_opened_depth is None:
|
|
405
|
+
self.ur_opened_depth = self.top_depth
|
|
406
|
+
|
|
407
|
+
# Check if under-reamer was opened in cased hole
|
|
408
|
+
if self.ur_opened_depth <= start_reaming_depth and hole_type == HoleType.CASED_HOLE:
|
|
409
|
+
self.trigger_alert(self.ur_opened_depth, start_reaming_depth)
|
|
410
|
+
self.ur_opened_depth = None
|
|
411
|
+
return self.UnderReamerActivationType.UNACTIVATED
|
|
412
|
+
|
|
413
|
+
return activation_mode
|
|
414
|
+
|
|
415
|
+
def trigger_alert(self, ur_opened_depth, start_reaming_depth):
|
|
416
|
+
identifier = "ur_opened_in_cased_hole"
|
|
417
|
+
message = (
|
|
418
|
+
f"Attempted to open under-reamer inside cased hole at {ur_opened_depth}. "
|
|
419
|
+
f"Last casing shoe depth of {start_reaming_depth}. "
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
current_timestamp = time.time()
|
|
423
|
+
|
|
424
|
+
if (current_timestamp > self.alerted_at_timestamp + self.ALERT_BACKOFF_TIME) and is_stream_app():
|
|
425
|
+
Alerter.trigger_alert(identifier, message)
|
|
426
|
+
self.alerted_at_timestamp = current_timestamp
|
|
427
|
+
Logger.warn(f"Alert created. identifier: {identifier}, message: {message}")
|
|
428
|
+
else:
|
|
429
|
+
Logger.info(
|
|
430
|
+
f"Alert created. identifier: {identifier}, message: {message}"
|
|
431
|
+
f"However, not triggering alert because it was not triggered from Stream app"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def __repr__(self):
|
|
435
|
+
return (
|
|
436
|
+
f"{super().__repr__()}"
|
|
437
|
+
f"opened_od={nanround(self.ur_opened_od, 2)} in - "
|
|
438
|
+
f"tfa={nanround(self.tfa, 2)} in^2, "
|
|
439
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from worker.data.operations import get_data_by_path, nanround
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Element:
|
|
5
|
+
"""
|
|
6
|
+
This is a base object for any element in the wellbore:
|
|
7
|
+
- Pipe, and Bit
|
|
8
|
+
- Hole section
|
|
9
|
+
- Annulus section
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
Set the element properties through kwargs. The keys are:
|
|
15
|
+
- 'top_depth'
|
|
16
|
+
- 'length'
|
|
17
|
+
- 'bottom_depth'
|
|
18
|
+
"""
|
|
19
|
+
self.top_depth = None
|
|
20
|
+
self.bottom_depth = None
|
|
21
|
+
self.length = None
|
|
22
|
+
|
|
23
|
+
if {"top_depth", "length"} <= kwargs.keys():
|
|
24
|
+
self.top_depth = get_data_by_path(kwargs, "top_depth", func=float)
|
|
25
|
+
self.length = get_data_by_path(kwargs, "length", func=float)
|
|
26
|
+
self.set_bottom_depth()
|
|
27
|
+
elif {"top_depth", "bottom_depth"} <= kwargs.keys():
|
|
28
|
+
self.top_depth = get_data_by_path(kwargs, "top_depth", func=float)
|
|
29
|
+
self.bottom_depth = get_data_by_path(kwargs, "bottom_depth", func=float)
|
|
30
|
+
self.set_length()
|
|
31
|
+
elif "length" in kwargs:
|
|
32
|
+
self.length = get_data_by_path(kwargs, "length", func=float)
|
|
33
|
+
else:
|
|
34
|
+
raise KeyError("Missing keys: 'top_depth', 'bottom_depth', and 'length'")
|
|
35
|
+
|
|
36
|
+
def set_length(self, length: float = None):
|
|
37
|
+
if length is None:
|
|
38
|
+
length = self.bottom_depth - self.top_depth
|
|
39
|
+
self.length = length
|
|
40
|
+
|
|
41
|
+
def set_bottom_depth(self, bottom_depth: float = None):
|
|
42
|
+
if bottom_depth is None:
|
|
43
|
+
bottom_depth = self.top_depth + self.length
|
|
44
|
+
self.bottom_depth = bottom_depth
|
|
45
|
+
|
|
46
|
+
def set_top_depth(self, top_depth: float = None):
|
|
47
|
+
if top_depth is None:
|
|
48
|
+
top_depth = self.bottom_depth - self.length
|
|
49
|
+
self.top_depth = top_depth
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def area(self):
|
|
53
|
+
return self.compute_get_area()
|
|
54
|
+
|
|
55
|
+
def compute_get_area(self):
|
|
56
|
+
"""
|
|
57
|
+
Compute the area of the element
|
|
58
|
+
:return: area of the element in INCH^2
|
|
59
|
+
:return:
|
|
60
|
+
"""
|
|
61
|
+
raise NotImplementedError()
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def volume(self):
|
|
65
|
+
return self.compute_get_volume()
|
|
66
|
+
|
|
67
|
+
def compute_get_volume(self):
|
|
68
|
+
"""
|
|
69
|
+
Compute the volume of the element
|
|
70
|
+
:return: volume in FOOT^3
|
|
71
|
+
"""
|
|
72
|
+
raise NotImplementedError()
|
|
73
|
+
|
|
74
|
+
def cmp(self, measured_depth: float) -> int:
|
|
75
|
+
"""
|
|
76
|
+
To check the position of the given measured depth vs the current section
|
|
77
|
+
if measured depth is closer to surface compared to the annulus it returns -1,
|
|
78
|
+
and if deeper it returns +1 and otherwise it is 0
|
|
79
|
+
:param measured_depth:
|
|
80
|
+
:return: -1: above, 0: at, +1: below
|
|
81
|
+
"""
|
|
82
|
+
if measured_depth <= self.top_depth:
|
|
83
|
+
return -1
|
|
84
|
+
|
|
85
|
+
if self.bottom_depth < measured_depth:
|
|
86
|
+
return +1
|
|
87
|
+
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
def __contains__(self, measured_depth: float):
|
|
91
|
+
"""
|
|
92
|
+
To check if this element covers the given measured depth.
|
|
93
|
+
:param measured_depth:
|
|
94
|
+
:return:
|
|
95
|
+
"""
|
|
96
|
+
return self.top_depth < measured_depth <= self.bottom_depth
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
return (
|
|
100
|
+
f"{nanround(self.length, 2):>8} ft --> {nanround(self.top_depth, 2):>8} ft "
|
|
101
|
+
f"- {nanround(self.bottom_depth, 2):>8} ft"
|
|
102
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from worker.data.serialization import serialization
|
|
4
|
+
from worker.data.unit_conversions import KG_M3_to_PPG
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@serialization
|
|
8
|
+
class HoleType(Enum):
|
|
9
|
+
CASED_HOLE = "Cased Hole"
|
|
10
|
+
OPEN_HOLE = "Open Hole"
|
|
11
|
+
RISER_LESS = "Riserless"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@serialization
|
|
15
|
+
class PipeType(Enum):
|
|
16
|
+
Unknown = "Unknown"
|
|
17
|
+
DP = "Drillpipe"
|
|
18
|
+
HWDP = "Heavy Weight Drillpipe"
|
|
19
|
+
DC = "Drill Collar"
|
|
20
|
+
STABILIZER = "Stabilizer"
|
|
21
|
+
AGITATOR = "Agitator"
|
|
22
|
+
PDM = "Positive Displacement Motor"
|
|
23
|
+
MWD = "Measurement While Drilling"
|
|
24
|
+
RSS = "Rotary Steerable System"
|
|
25
|
+
BIT = "Bit"
|
|
26
|
+
UNDERREAMER = "Under-reamer"
|
|
27
|
+
FLOAT_COLLAR = "Float Collar"
|
|
28
|
+
FLOAT_SHOE = "Float Shoe"
|
|
29
|
+
CROSS_OVER = "Cross Over"
|
|
30
|
+
TOE_SLEEVE = "Toe Sleeve"
|
|
31
|
+
CASING_JOINTS = "Casing Joints"
|
|
32
|
+
DV_TOOL = "Differential Valve Tool"
|
|
33
|
+
ACP = "Annular Casing Packer"
|
|
34
|
+
RISER = "Riser"
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def determine_type(family: str):
|
|
38
|
+
family = family.lower()
|
|
39
|
+
mapping = {
|
|
40
|
+
"dp": PipeType.DP,
|
|
41
|
+
"hwdp": PipeType.HWDP,
|
|
42
|
+
"dc": PipeType.DC,
|
|
43
|
+
"stabilizer": PipeType.STABILIZER,
|
|
44
|
+
"agitator": PipeType.AGITATOR,
|
|
45
|
+
"pdm": PipeType.PDM,
|
|
46
|
+
"mwd": PipeType.MWD,
|
|
47
|
+
"rss": PipeType.RSS,
|
|
48
|
+
"bit": PipeType.BIT,
|
|
49
|
+
"ur": PipeType.UNDERREAMER,
|
|
50
|
+
"float_collar": PipeType.FLOAT_COLLAR,
|
|
51
|
+
"float_shoe": PipeType.FLOAT_SHOE,
|
|
52
|
+
"cross_over": PipeType.CROSS_OVER,
|
|
53
|
+
"toe_sleeve": PipeType.TOE_SLEEVE,
|
|
54
|
+
"casing_joints": PipeType.CASING_JOINTS,
|
|
55
|
+
"dv_tool": PipeType.DV_TOOL,
|
|
56
|
+
"acp": PipeType.ACP,
|
|
57
|
+
"riser": PipeType.RISER,
|
|
58
|
+
}
|
|
59
|
+
_type = mapping.get(family)
|
|
60
|
+
if not _type:
|
|
61
|
+
_type = PipeType.Unknown
|
|
62
|
+
|
|
63
|
+
return _type
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@serialization
|
|
67
|
+
class PipeMaterial(Enum):
|
|
68
|
+
Steel = {"density": 7850 * KG_M3_to_PPG, "modulus_of_elasticity": 29.9e6, "poisson_ratio": 0.30}
|
|
69
|
+
Aluminium = {"density": 2700 * KG_M3_to_PPG, "modulus_of_elasticity": 10.5e6, "poisson_ratio": 0.32}
|
|
70
|
+
Titanium = {"density": 4506 * KG_M3_to_PPG, "modulus_of_elasticity": 16.5e6, "poisson_ratio": 0.31}
|
|
71
|
+
NonMagnetic = {"density": 7850 * KG_M3_to_PPG, "modulus_of_elasticity": 29.9e6, "poisson_ratio": 0.30}
|
|
72
|
+
|
|
73
|
+
def get_density(self) -> float:
|
|
74
|
+
"""
|
|
75
|
+
Get the density of the pipe material
|
|
76
|
+
:return: pipe density PPG
|
|
77
|
+
"""
|
|
78
|
+
return self["density"]
|
|
79
|
+
|
|
80
|
+
def get_modulus_of_elasticity(self) -> float:
|
|
81
|
+
"""
|
|
82
|
+
Get the pipe modulus of elasticity
|
|
83
|
+
:return: modulus of elasticity in PSI
|
|
84
|
+
"""
|
|
85
|
+
return self["modulus_of_elasticity"]
|
|
86
|
+
|
|
87
|
+
def get_poisson_ratio(self) -> float:
|
|
88
|
+
"""
|
|
89
|
+
Get the Poisson ratio
|
|
90
|
+
:return: Poisson ratio
|
|
91
|
+
"""
|
|
92
|
+
return self["poisson_ratio"]
|