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.
Files changed (63) hide show
  1. corva_worker_python-2.0.0.dist-info/METADATA +30 -0
  2. corva_worker_python-2.0.0.dist-info/RECORD +63 -0
  3. corva_worker_python-2.0.0.dist-info/WHEEL +5 -0
  4. corva_worker_python-2.0.0.dist-info/top_level.txt +1 -0
  5. worker/__init__.py +5 -0
  6. worker/app/__init__.py +291 -0
  7. worker/app/modules/__init__.py +265 -0
  8. worker/app/modules/activity_module.py +141 -0
  9. worker/app/modules/connection_module.py +21 -0
  10. worker/app/modules/depth_activity_module.py +21 -0
  11. worker/app/modules/scheduler.py +44 -0
  12. worker/app/modules/time_activity_module.py +21 -0
  13. worker/app/modules/trigger.py +43 -0
  14. worker/constants.py +51 -0
  15. worker/data/__init__.py +0 -0
  16. worker/data/activity/__init__.py +132 -0
  17. worker/data/activity/activity_grouping.py +242 -0
  18. worker/data/alert.py +89 -0
  19. worker/data/api.py +155 -0
  20. worker/data/enums.py +141 -0
  21. worker/data/json_encoder.py +18 -0
  22. worker/data/math.py +104 -0
  23. worker/data/operations.py +477 -0
  24. worker/data/serialization.py +110 -0
  25. worker/data/task_handler.py +82 -0
  26. worker/data/two_way_dict.py +17 -0
  27. worker/data/unit_conversions.py +5 -0
  28. worker/data/wits.py +323 -0
  29. worker/event/__init__.py +53 -0
  30. worker/event/event_handler.py +90 -0
  31. worker/event/scheduled.py +64 -0
  32. worker/event/stream.py +48 -0
  33. worker/exceptions.py +26 -0
  34. worker/mixins/__init__.py +0 -0
  35. worker/mixins/logging.py +119 -0
  36. worker/mixins/rollbar.py +87 -0
  37. worker/partial_rerun_merge/__init__.py +0 -0
  38. worker/partial_rerun_merge/merge.py +500 -0
  39. worker/partial_rerun_merge/models.py +91 -0
  40. worker/partial_rerun_merge/progress.py +241 -0
  41. worker/state/__init__.py +96 -0
  42. worker/state/mixins.py +111 -0
  43. worker/state/state.py +46 -0
  44. worker/test/__init__.py +3 -0
  45. worker/test/lambda_function_test_run.py +196 -0
  46. worker/test/local_testing/__init__.py +0 -0
  47. worker/test/local_testing/to_local_transfer.py +360 -0
  48. worker/test/utils.py +51 -0
  49. worker/wellbore/__init__.py +0 -0
  50. worker/wellbore/factory.py +496 -0
  51. worker/wellbore/measured_depth_finder.py +12 -0
  52. worker/wellbore/model/__init__.py +0 -0
  53. worker/wellbore/model/ann.py +103 -0
  54. worker/wellbore/model/annulus.py +113 -0
  55. worker/wellbore/model/drillstring.py +196 -0
  56. worker/wellbore/model/drillstring_components.py +439 -0
  57. worker/wellbore/model/element.py +102 -0
  58. worker/wellbore/model/enums.py +92 -0
  59. worker/wellbore/model/hole.py +297 -0
  60. worker/wellbore/model/hole_section.py +51 -0
  61. worker/wellbore/model/riser.py +22 -0
  62. worker/wellbore/sections_mixin.py +64 -0
  63. 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"]