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,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
+ )