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,297 @@
1
+ from copy import deepcopy
2
+ from typing import List, Union
3
+
4
+ from worker.data.operations import get_data_by_path
5
+ from worker.data.serialization import serialization
6
+ from worker.wellbore.model.enums import HoleType
7
+ from worker.wellbore.model.hole_section import HoleSection
8
+ from worker.wellbore.sections_mixin import SectionsMixin
9
+
10
+
11
+ @serialization
12
+ class Hole(SectionsMixin):
13
+ DEPTH_THRESHOLD = 50 # ft
14
+ LENGTH_THRESHOLD = 1 # ft
15
+
16
+ SERIALIZED_VARIABLES = {"sections": list}
17
+
18
+ def __init__(self, *args, **kwargs):
19
+ super().__init__(*args, **kwargs)
20
+ self.sections = kwargs.get("sections") or []
21
+
22
+ def add_section(self, section: HoleSection):
23
+ """
24
+ Add a new hole section to the end of the hole.
25
+ :param section:
26
+ :return:
27
+ """
28
+ if not self:
29
+ top_depth = section.top_depth
30
+
31
+ if top_depth < Hole.DEPTH_THRESHOLD:
32
+ section.top_depth = 0
33
+
34
+ if self:
35
+ section.top_depth = self[-1].bottom_depth
36
+
37
+ # bottom depth is the source of truth, so we are updating the length
38
+ section.set_length()
39
+
40
+ self.sections.append(section)
41
+
42
+ def insert_section(self, section: HoleSection, index: int):
43
+ """
44
+ Insert a new hole section in the given index.
45
+ :param section:
46
+ :param index:
47
+ :return:
48
+ """
49
+ if index is not None:
50
+ if self:
51
+ section.top_depth = self[index - 1].bottom_depth
52
+ section.set_bottom_depth()
53
+
54
+ self.sections.insert(index, section)
55
+ self.update_depths(start_index=index + 1)
56
+
57
+ def insert_section_after(self, section: HoleSection, section_before: HoleSection):
58
+ index_section_before = self.sections.index(section_before)
59
+ self.insert_section(section, index_section_before + 1)
60
+
61
+ def get_bottom_depth(self) -> float:
62
+ if not self:
63
+ return 0
64
+ return self[-1].bottom_depth
65
+
66
+ def update_depths(self, start_index: int = 0):
67
+ """
68
+ To update the top and bottom depth of the components
69
+ :param start_index:
70
+ :return:
71
+ """
72
+ if start_index < 1:
73
+ self[0].top_depth = 0
74
+ self[0].set_bottom_depth()
75
+ start_index = 1
76
+
77
+ for i in range(start_index, len(self)):
78
+ self[i].top_depth = self[i - 1].bottom_depth
79
+ self[i].set_bottom_depth()
80
+
81
+ def get_cased_hole(self):
82
+ """
83
+ Get the cased sections of the hole.
84
+ :return:
85
+ """
86
+ cased_hole = Hole()
87
+
88
+ for sec in self:
89
+ if sec.hole_type == HoleType.CASED_HOLE:
90
+ cased_hole.add_section(sec)
91
+
92
+ return cased_hole
93
+
94
+ def get_open_hole(self):
95
+ """
96
+ Get the open sections of the hole.
97
+ :return:
98
+ """
99
+ open_hole = Hole()
100
+
101
+ for sec in self:
102
+ if sec.hole_type == HoleType.OPEN_HOLE:
103
+ open_hole.add_section(sec)
104
+
105
+ return open_hole
106
+
107
+ def get_casing_bottom_depth(self):
108
+ return self.get_cased_hole().get_bottom_depth()
109
+
110
+ def is_casing_exist(self) -> bool:
111
+ """
112
+ Get the cased sections of the hole.
113
+ :return:
114
+ """
115
+ return any(sec.hole_type == HoleType.CASED_HOLE for sec in self)
116
+
117
+ def update(self, hole_depth: Union[dict, float]) -> bool:
118
+ """
119
+ Update the last open hole segment of the hole.
120
+ :param hole_depth: a wits dict or actual hole depth
121
+ :return: if any update occurred or not
122
+ """
123
+
124
+ # If there are no sections in the hole or the last hole is Open Hole, do not update hole and return
125
+ if not self or self[-1].hole_type != HoleType.OPEN_HOLE:
126
+ return False
127
+
128
+ if isinstance(hole_depth, dict):
129
+ hole_depth = get_data_by_path(hole_depth, "data.hole_depth", float)
130
+
131
+ # if the hole depth < casing bottom depth, then ignore
132
+ cased_hole_bottom_depth = self.get_cased_hole().get_bottom_depth()
133
+ if hole_depth < cased_hole_bottom_depth:
134
+ return False
135
+
136
+ self[-1].set_bottom_depth(hole_depth)
137
+ self[-1].set_length()
138
+
139
+ return True
140
+
141
+ def set_casings(self, casings: List[dict]):
142
+ """
143
+ Set the casing by providing the list of the jsons
144
+ :param casings: list of casings from API in json dict format
145
+ :return: None
146
+ """
147
+ # TODO we need to consider using 'data.components' if available to set the casings
148
+ self.sections = []
149
+
150
+ if not casings:
151
+ return
152
+
153
+ # sorting the casings in the ascending order (the smaller the inner_diameter the earlier it is added)
154
+ casings = sorted(casings, key=lambda csg: csg.get("data", {}).get("inner_diameter"))
155
+
156
+ # removing the outer casings at the surface
157
+ surface_index = next(
158
+ (
159
+ idx
160
+ for idx, csg in enumerate(casings)
161
+ if csg.get("data", {}).get("top_depth", 1000) < Hole.DEPTH_THRESHOLD
162
+ ),
163
+ 0,
164
+ )
165
+ casings = casings[0 : surface_index + 1]
166
+
167
+ # finding if a liner is overlapping outer liners
168
+ # this is common in offshore drilling where they set the casings at the sea floor
169
+ index = 1
170
+ while index < len(casings):
171
+ top_depth_inner = casings[index - 1].get("data", {}).get("top_depth", 1000)
172
+ top_depth_outer = casings[index].get("data", {}).get("top_depth", 1000)
173
+ if top_depth_outer + Hole.DEPTH_THRESHOLD > top_depth_inner:
174
+ casings.pop(index)
175
+ else:
176
+ index += 1
177
+
178
+ # reversing the order (the larger the inner_diameter the earlier it is)
179
+ casings = reversed(casings)
180
+
181
+ for csg in casings:
182
+ hole_section = HoleSection(**csg.get("data", {}), hole_type=HoleType.CASED_HOLE)
183
+ self.add_section(hole_section)
184
+
185
+ def update_casings(self, casings: "Hole"):
186
+ """
187
+ To remove the casings of the current hole and
188
+ replace them with the given one
189
+ :param casings:
190
+ :return:
191
+ """
192
+ casings = casings.get_cased_hole()
193
+ # TODO: check if deepcopy is required
194
+ open_holes = deepcopy([sec for sec in self if sec.hole_type == HoleType.OPEN_HOLE])
195
+
196
+ self.sections = []
197
+ self.sections.extend(casings)
198
+ self.sections.extend(open_holes)
199
+
200
+ self.update_depths()
201
+
202
+ def get_min_inner_diameter(self) -> Union[float, None]:
203
+ if not self:
204
+ return None
205
+ return self[-1].inner_diameter
206
+
207
+ def get_last_hole_section(self):
208
+ if self:
209
+ return self[-1]
210
+ return None
211
+
212
+ def compute_get_total_volume(self, top_depth: float = None, bottom_depth: float = None) -> float:
213
+ """
214
+ Compute the volume of the hole section
215
+ :return: volume of the hole section in FOOT^3
216
+ """
217
+ if top_depth is None and bottom_depth is None:
218
+ return sum(sec.compute_get_volume() for sec in self)
219
+
220
+ top_depth = top_depth or 0
221
+ bottom_depth = bottom_depth or self.get_bottom_depth()
222
+
223
+ total_volume = 0
224
+
225
+ for index, section in enumerate(self):
226
+ if section.bottom_depth <= top_depth:
227
+ continue
228
+ if section.top_depth >= bottom_depth:
229
+ break
230
+
231
+ area = section.compute_get_area() # in INCH^2
232
+ length = min(bottom_depth, section.bottom_depth) - max(top_depth, section.top_depth) # in FOOT
233
+ volume = area / 144 * length
234
+ total_volume += volume
235
+
236
+ return total_volume
237
+
238
+ def trim_to_bottom_depth(self, depth):
239
+ if not self.sections:
240
+ return
241
+
242
+ self.sections = [section for section in self.sections if section.top_depth <= depth]
243
+ if self.sections:
244
+ self.sections[-1].set_bottom_depth(depth)
245
+ self.sections[-1].set_length()
246
+
247
+ if self.sections[-1].length < Hole.LENGTH_THRESHOLD:
248
+ self.sections.pop(-1)
249
+
250
+ def trim_to_top_depth(self, depth):
251
+ if not self.sections:
252
+ return
253
+
254
+ self.sections = [section for section in self.sections if section.bottom_depth > depth]
255
+ if self.sections:
256
+ self.sections[0].set_top_depth(depth)
257
+ self.sections[0].set_length()
258
+
259
+ if self.sections[0].length < Hole.LENGTH_THRESHOLD:
260
+ self.sections.pop(0)
261
+
262
+ @staticmethod
263
+ def merge_holes(casing_holes: List):
264
+ """
265
+ This method can be used to merge Holes. The overlapping sections are trimmed and updated.
266
+ The mergedHole is the ultimate hole being generated starting from the bottom and keep adding
267
+ elements (sections) to the top of it.
268
+
269
+ :param casing_holes: casingHoles List of holes to be merged from surface to bottom;
270
+ the wider casing is closer to surface (index 0), except for the riser that is always the first section.
271
+ :return: a merged hole
272
+ """
273
+ if not casing_holes:
274
+ return None
275
+
276
+ merged_hole = deepcopy(casing_holes[-1])
277
+
278
+ if len(casing_holes) == 1:
279
+ return merged_hole
280
+
281
+ for i in range(len(casing_holes) - 2, -1, -1):
282
+ casing_hole = casing_holes[i]
283
+ current_section = casing_hole.sections[-1]
284
+ bottom_section = merged_hole.sections[0]
285
+
286
+ # comparing the inner diameter of the current section vs the one below
287
+ if current_section.inner_diameter > bottom_section.inner_diameter > 0:
288
+ # normal situation where the casings are telescopic from surface
289
+ casing_hole.trim_to_bottom_depth(bottom_section.top_depth)
290
+ else:
291
+ # in case there is a riser smaller than the casing below.
292
+ merged_hole.trim_to_top_depth(current_section.bottom_depth)
293
+
294
+ for j in range(len(casing_hole.sections) - 1, -1, -1):
295
+ merged_hole.sections.insert(0, casing_hole.sections[j])
296
+
297
+ return merged_hole
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+
3
+ from worker.data.operations import equal, nanround
4
+ from worker.data.serialization import serialization
5
+ from worker.wellbore.model.element import Element
6
+ from worker.wellbore.model.enums import HoleType
7
+
8
+
9
+ @serialization
10
+ class HoleSection(Element):
11
+ SERIALIZED_VARIABLES = {
12
+ "top_depth": float,
13
+ "bottom_depth": float,
14
+ "length": float,
15
+ "inner_diameter": float,
16
+ "hole_type": HoleType,
17
+ }
18
+
19
+ def __init__(self, **kwargs):
20
+ super(HoleSection, self).__init__(**kwargs)
21
+ self.inner_diameter = kwargs["inner_diameter"]
22
+ self.hole_type = kwargs.get("hole_type")
23
+
24
+ def __eq__(self, other):
25
+ params = ["inner_diameter", "length", "hole_type"]
26
+ return equal(self, other, params)
27
+
28
+ def eq_without_length(self, other):
29
+ params = ["inner_diameter", "hole_type"]
30
+ return equal(self, other, params)
31
+
32
+ # override
33
+ def compute_get_area(self) -> float:
34
+ """
35
+ Compute the total area of the hole section and return the result
36
+ :return: cross sectional area of the hole section in INCH^2
37
+ """
38
+ return np.pi / 4 * self.inner_diameter**2
39
+
40
+ # override
41
+ def compute_get_volume(self) -> float:
42
+ """
43
+ Compute the volume of the hole section
44
+ :return: volume of the hole section in FOOT^3
45
+ """
46
+ volume = self.compute_get_area() * self.length # in INCH^2 * FOOT
47
+ volume /= 144 # in FOOT^3
48
+ return volume
49
+
50
+ def __repr__(self):
51
+ return f"{nanround(self.inner_diameter, 2):>6} in, " f"{super().__repr__()}, " f"{self.hole_type.value}"
@@ -0,0 +1,22 @@
1
+ from copy import deepcopy
2
+
3
+ from worker.data.serialization import serialization
4
+ from worker.wellbore.model.hole_section import HoleSection
5
+
6
+
7
+ @serialization
8
+ class Riser(HoleSection):
9
+ SERIALIZED_VARIABLES = deepcopy(HoleSection.SERIALIZED_VARIABLES)
10
+ SERIALIZED_VARIABLES.update({"external_flow_source_depth": float})
11
+
12
+ def __init__(self, **kwargs):
13
+ super().__init__(**kwargs)
14
+ self.external_flow_source_depth = None
15
+
16
+ def get_external_flow_source_depth(self):
17
+ # If an external_flow_source_depth is not set, it is defaulted to the Riser bottom depth
18
+ # This is only used for hydraulics, which will be implemented later.
19
+ if self.external_flow_source_depth is None:
20
+ self.external_flow_source_depth = self.bottom_depth
21
+
22
+ return self.external_flow_source_depth
@@ -0,0 +1,64 @@
1
+ from typing import Union
2
+
3
+ from worker.wellbore.model.ann import Ann
4
+ from worker.wellbore.model.drillstring_components import Pipe
5
+ from worker.wellbore.model.hole_section import HoleSection
6
+
7
+
8
+ class SectionsMixin(object):
9
+ def __init__(self, *args, **kwargs):
10
+ self.sections = []
11
+
12
+ def __len__(self):
13
+ return len(self.sections)
14
+
15
+ def __getitem__(self, index):
16
+ return self.sections[index]
17
+
18
+ def __delitem__(self, index):
19
+ del self.sections[index]
20
+
21
+ def __bool__(self):
22
+ return len(self) != 0
23
+
24
+ def __repr__(self):
25
+ return "\n".join(str(sec) for sec in self.sections)
26
+
27
+ def compute_volume(self, top_depth: float, bottom_depth: float) -> float:
28
+ """
29
+ Compute the volume between two depths
30
+ :param top_depth: in ft
31
+ :param bottom_depth: in ft
32
+ :return: volume in cuft
33
+ """
34
+ volume = 0
35
+ for element in self:
36
+ if element.top_depth <= bottom_depth <= element.bottom_depth:
37
+ volume += (bottom_depth - top_depth) * element.area / 144
38
+ break
39
+
40
+ elif element.top_depth <= top_depth <= element.bottom_depth:
41
+ volume += (element.bottom_depth - top_depth) * element.area / 144
42
+ top_depth = element.bottom_depth
43
+
44
+ return volume
45
+
46
+ def find_section_at_measured_depth(
47
+ self, measured_depth: float, at_exact_measured_depth: bool = False
48
+ ) -> Union[HoleSection, Ann, Pipe, None]:
49
+ """
50
+ :param measured_depth:
51
+ :param at_exact_measured_depth:
52
+ :return:
53
+ """
54
+ if measured_depth > self[-1].bottom_depth:
55
+ return None
56
+
57
+ for sec in self:
58
+ if sec.top_depth < measured_depth <= sec.bottom_depth:
59
+ return sec
60
+
61
+ if at_exact_measured_depth:
62
+ return None
63
+
64
+ return self[0]