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,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]
|