def assign_ci_start_end(segarr, cnarr):
    """Assign ci_start and ci_end fields to segments.

    Values for each segment indicate the CI boundary points within that segment,
    i.e. the right CI boundary for the left-side breakpoint (segment start), and
    left CI boundary for the right-side breakpoint (segment end).

    This is a little unintuitive because the CI refers to the breakpoint, not
    the segment, but we're storing the info in an array of segments.

    Calculation: Just use the boundaries of the bins left- and right-adjacent to
    each segment breakpoint.
    """
    lefts_rights = ((bins.end.iat[0], bins.start.iat[-1])
                    for _seg, bins in cnarr.by_ranges(segarr, mode="outer"))
    ci_lefts, ci_rights = zip(*lefts_rights)
    return segarr.as_dataframe(
        segarr.data.assign(ci_left=ci_lefts, ci_right=ci_rights))