Coverage

Coverage :: # (opaque)

Canonical finite coverage on the POSIX microsecond axis. Members are nonempty, sorted, disjoint, and do not touch.

Example

Combine occupied spans, then subtract them from a finite availability window. Overlapping or touching spans coalesce. Keep bookings in EventCollection when their individual identities matter; coverage deliberately forgets them.

import time.Coverage
import time.PosixSpan
import time.PosixDelta

expect {
    window = PosixSpan.from_seconds(0, 10, RejectSubmicrosecond)?
    busy = Coverage.from_spans([
        PosixSpan.from_seconds(2, 4, RejectSubmicrosecond)?,
        PosixSpan.from_seconds(4, 6, RejectSubmicrosecond)?,
    ])
    free = Coverage.difference(Coverage.from_spans([window]), busy)
    expected_width = PosixDelta.from_microseconds(6000000)
    Coverage.member_count(busy) == 1 and
        Coverage.member_count(free) == 2 and
        Coverage.coordinate_width(free) == Ok(expected_width)
}

Examples assume a package dependency named time.

from_sorted_spans : List(PosixSpan) -> Try(Coverage, [UnsortedInput, ..])

Validate start ordering in O(n); merge overlap and touch.

to_spans : Coverage -> List(PosixSpan)

Returns the canonical spans. Roc may share their backing storage.

iter : Coverage -> Iter(PosixSpan)

Iterate whole canonical spans, never individual microseconds.

fact_count : Coverage -> U64

Indexed semantic facts visit canonical spans, never their coordinates. An allocated span list uses at least 16 bytes per member, leaving room for the fixed fact headers when its length is counted in U64.

fact_at : Coverage, U64 -> [End, Item(SemanticFact)]
fold_overlaps : Coverage, PosixSpan, state, (state, PosixSpan -> state) -> state

Fold whole overlapping members, not clipped intersections. O(log n + k).

union : Coverage, Coverage -> Coverage
complement_within : Coverage, PosixSpan -> Coverage

Complement relative to one finite window; seek before emitting gaps.

SortedBuilder

Coverage.SortedBuilder :: # (opaque)

Incremental construction from start-ordered spans, including across input chunks. Retains canonical output plus one pending span, not all raw input. This materializes coverage; it is not a lazy output iterator. Retaining a builder/snapshot can cause subsequent List appends to copy shared storage.

append : SortedBuilder, PosixSpan -> Try(SortedBuilder, [UnsortedInput, ..])

Constant comparison work; owned append is amortized, shared append may copy retained output. Equal starts are valid; decreasing starts fail.

append_bounded : SortedBuilder, PosixSpan, U64 -> Try([Added(SortedBuilder), Full], [UnsortedInput, ..])

Full leaves the input builder unchanged and performs no output append. The limit counts canonical members, so touching/overlapping input can still merge at capacity. An already larger builder also returns Full.

append_retaining : SortedBuilder, PosixSpan, U64 -> Try([Added(SortedBuilder), Full(SortedBuilder)], [UnsortedInput, ..])

Internal cursor integration: return ownership of an unchanged full builder so callers need not retain an alias across a successful append. The public append_bounded adapter uses this same validation and merge.

to_coverage : SortedBuilder -> Coverage

No revalidation or sorting. Appending the pending member can copy a shared list; this does not detach earlier snapshots' backing storage.