///|
pub struct MissingSpan {
first_row : Int
last_row : Int
start_depth : Double
stop_depth : Double
count : Int
bounded : Bool
}
///|
/// Contiguous missing samples in acquisition order, including leading/trailing spans.
pub fn missing_spans(
log : Log,
name : String,
) -> Array[MissingSpan] raise WellError {
validate(log)
let c = curve_index(log, name)
let spans : Array[MissingSpan] = []
let mut i = 0
while i < log.rows.length() {
if log.rows[i][c] is Some(_) {
i += 1
continue
}
let first = i
while i < log.rows.length() && log.rows[i][c] is None {
i += 1
}
let last = i - 1
spans.push({
first_row: first,
last_row: last,
start_depth: log.rows[first][0].unwrap(),
stop_depth: log.rows[last][0].unwrap(),
count: i - first,
bounded: first > 0 && i < log.rows.length(),
})
}
spans
}
///|
/// Fill only bounded null runs whose supporting endpoint distance is within max_gap.
/// Does not interpolate already-present values, extend edges, or bridge large gaps.
pub fn fill_linear(
log : Log,
name : String,
max_gap : Double,
) -> Log raise WellError {
validate(log)
if !finite(max_gap) || max_gap <= 0.0 {
fail("fill max_gap must be finite and positive")
}
let column = curve_index(log, name)
if column == 0 {
fail("depth cannot be filled")
}
let rows = log.rows.map(row => row.copy())
for span in missing_spans(log, name) {
if !span.bounded {
continue
}
let left = span.first_row - 1
let right = span.last_row + 1
let a = rows[left][0].unwrap()
let b = rows[right][0].unwrap()
if (b - a).abs() > max_gap {
continue
}
let x = rows[left][column].unwrap()
let y = rows[right][column].unwrap()
for i in span.first_row.. Log raise WellError {
validate(log)
if !finite(value) {
fail("fill value must be finite")
}
let column = curve_index(log, name)
if column == 0 {
fail("depth cannot be filled")
}
let rows = log.rows.map(row => row.copy())
for row in rows {
if row[column] is None {
row[column] = Some(value)
}
}
rebuild(log, log.curves, rows)
}