Skip to content

coverage_reservations

coverage_reservations #

Per-user location reservations — spread a crowd across a survey area.

The spatial sibling of ReservationBook: each user leases a place out of a batch, and a place is retired once a capture lands within threshold of it.

DEFAULT_LEASE = timedelta(minutes=30) module-attribute #

How long a handout is held before, absent a covering capture, it recycles.

CoverageBook #

Bases: ImpureState

The persistent book of location leases.

batch holds the un-handed-out survey locations; held maps each user to their [Lease][]. The struct IS the format: changing its shape starts a fresh book (no migration).

Source code in capturegraph-lib/capturegraph/scheduling/organize/coverage_reservations.py
class CoverageBook(cg.ImpureState):
    """The persistent book of location leases.

    ``batch`` holds the un-handed-out survey locations; ``held`` maps each user
    to their [Lease][]. The struct IS the format: changing its shape starts a
    fresh book (no migration).
    """

    batch: cg.Array[cg.Location]
    held: cg.Map[cg.UserID, Lease]

    def reserve(
        self,
        user_id: str,
        *,
        distance: Callable[[cg.Location, cg.Location], float],
        at: cg.Location | None = None,
        fulfilled_by: Sequence[cg.Location] = (),
        threshold: float = 1.0,
        lease: timedelta = DEFAULT_LEASE,
        refill: Sequence[cg.Location] | None = None,
        now: datetime | None = None,
    ) -> cg.Location | None:
        """The location this user holds, leasing one from the batch if none.

        Args:
            user_id: The user asking for a location.
            distance: A distance between two ``cg.Location``s — the same metric used
                to select the batch (``cgsh.distance.location(sigma_m=...)``). It
                both picks the location closest to a requester and decides when a
                capture covers a reservation.
            at: The requester's current location, if known. The batch location
                closest to it is leased; without it, the batch is leased in
                insertion order (FIFO).
            fulfilled_by: Captured locations; a reservation or batch location within
                ``threshold`` of any of these is retired first, so a covered spot is
                never offered.
            threshold: A reservation is released once a capture lies within this
                distance of it, in the units ``distance`` returns. Defaults to
                ``1.0``.
            lease: How long a handout is held before, absent a covering capture, the
                location returns to the batch. Defaults to 30 minutes.
            refill: Locations to install as the batch **when it is exhausted**.
                Ignored while the batch still has locations to hand out.
            now: The reference time; defaults to ``datetime.now()``. Pass it
                explicitly to make lease expiry deterministic under test.

        Returns:
            The user's reserved location, or ``None`` if the batch is exhausted
            and the user holds nothing.
        """
        now = now if now is not None else datetime.now(UTC)
        held = _retire_covered(_held(self), fulfilled_by, distance=distance, threshold=threshold)
        held, batch = _reclaim_lapsed(held, _batch(self), now=now)
        batch = _uncovered(batch, fulfilled_by, distance=distance, threshold=threshold)

        if refill is not None and not batch:
            batch = _uncovered(list(refill), fulfilled_by, distance=distance, threshold=threshold)

        if user_id in held:
            self._record(batch, held)
            return held[user_id][0]

        if not batch:
            self._record(batch, held)
            return None

        index = _closest_index(batch, at, distance=distance) if at is not None else 0
        chosen = batch.pop(index)
        held[user_id] = (chosen, now + lease)
        self._record(batch, held)
        return chosen

    def release(self, user_id: str) -> None:
        """Drop this user's hold, without returning its location to the batch.

        Args:
            user_id: The user giving up their location.
        """
        held = _held(self)
        held.pop(user_id, None)
        self._record(_batch(self), held)

    def _record(self, batch: list[cg.Location], held: _Held) -> None:
        """Write ``batch`` and ``held`` back as this book's fields."""
        self.batch = cg.Array(batch)
        self.held = held_map(
            {
                user_id: Lease(location=location, expires=cg.Time(expires))
                for user_id, (location, expires) in held.items()
            },
        )

release(user_id) #

Drop this user's hold, without returning its location to the batch.

Parameters:

Name Type Description Default
user_id str

The user giving up their location.

required
Source code in capturegraph-lib/capturegraph/scheduling/organize/coverage_reservations.py
def release(self, user_id: str) -> None:
    """Drop this user's hold, without returning its location to the batch.

    Args:
        user_id: The user giving up their location.
    """
    held = _held(self)
    held.pop(user_id, None)
    self._record(_batch(self), held)

reserve(user_id, *, distance, at=None, fulfilled_by=(), threshold=1.0, lease=DEFAULT_LEASE, refill=None, now=None) #

The location this user holds, leasing one from the batch if none.

Parameters:

Name Type Description Default
user_id str

The user asking for a location.

required
distance Callable[[Location, Location], float]

A distance between two cg.Locations — the same metric used to select the batch (cgsh.distance.location(sigma_m=...)). It both picks the location closest to a requester and decides when a capture covers a reservation.

required
at Location | None

The requester's current location, if known. The batch location closest to it is leased; without it, the batch is leased in insertion order (FIFO).

None
fulfilled_by Sequence[Location]

Captured locations; a reservation or batch location within threshold of any of these is retired first, so a covered spot is never offered.

()
threshold float

A reservation is released once a capture lies within this distance of it, in the units distance returns. Defaults to 1.0.

1.0
lease timedelta

How long a handout is held before, absent a covering capture, the location returns to the batch. Defaults to 30 minutes.

DEFAULT_LEASE
refill Sequence[Location] | None

Locations to install as the batch when it is exhausted. Ignored while the batch still has locations to hand out.

None
now datetime | None

The reference time; defaults to datetime.now(). Pass it explicitly to make lease expiry deterministic under test.

None

Returns:

Type Description
Location | None

The user's reserved location, or None if the batch is exhausted

Location | None

and the user holds nothing.

Source code in capturegraph-lib/capturegraph/scheduling/organize/coverage_reservations.py
def reserve(
    self,
    user_id: str,
    *,
    distance: Callable[[cg.Location, cg.Location], float],
    at: cg.Location | None = None,
    fulfilled_by: Sequence[cg.Location] = (),
    threshold: float = 1.0,
    lease: timedelta = DEFAULT_LEASE,
    refill: Sequence[cg.Location] | None = None,
    now: datetime | None = None,
) -> cg.Location | None:
    """The location this user holds, leasing one from the batch if none.

    Args:
        user_id: The user asking for a location.
        distance: A distance between two ``cg.Location``s — the same metric used
            to select the batch (``cgsh.distance.location(sigma_m=...)``). It
            both picks the location closest to a requester and decides when a
            capture covers a reservation.
        at: The requester's current location, if known. The batch location
            closest to it is leased; without it, the batch is leased in
            insertion order (FIFO).
        fulfilled_by: Captured locations; a reservation or batch location within
            ``threshold`` of any of these is retired first, so a covered spot is
            never offered.
        threshold: A reservation is released once a capture lies within this
            distance of it, in the units ``distance`` returns. Defaults to
            ``1.0``.
        lease: How long a handout is held before, absent a covering capture, the
            location returns to the batch. Defaults to 30 minutes.
        refill: Locations to install as the batch **when it is exhausted**.
            Ignored while the batch still has locations to hand out.
        now: The reference time; defaults to ``datetime.now()``. Pass it
            explicitly to make lease expiry deterministic under test.

    Returns:
        The user's reserved location, or ``None`` if the batch is exhausted
        and the user holds nothing.
    """
    now = now if now is not None else datetime.now(UTC)
    held = _retire_covered(_held(self), fulfilled_by, distance=distance, threshold=threshold)
    held, batch = _reclaim_lapsed(held, _batch(self), now=now)
    batch = _uncovered(batch, fulfilled_by, distance=distance, threshold=threshold)

    if refill is not None and not batch:
        batch = _uncovered(list(refill), fulfilled_by, distance=distance, threshold=threshold)

    if user_id in held:
        self._record(batch, held)
        return held[user_id][0]

    if not batch:
        self._record(batch, held)
        return None

    index = _closest_index(batch, at, distance=distance) if at is not None else 0
    chosen = batch.pop(index)
    held[user_id] = (chosen, now + lease)
    self._record(batch, held)
    return chosen

Lease #

Bases: Struct

One user's held location and when the hold expires.

Source code in capturegraph-lib/capturegraph/scheduling/organize/coverage_reservations.py
class Lease(cg.Struct):
    """One user's held location and when the hold expires."""

    location: cg.Location
    expires: cg.Time