ExBooking.Assignment (ExBooking v0.1.0)

View Source

Deterministic resource assignment.

Assignment runs after availability has already established which resources can take the slot. Strategies use explicit fairness inputs from the caller, then fall back to resource id so ties are stable and replayable.

A scorer may rank resources before the strategy key. The scorer receives the request routing context as opaque data; the kernel never inspects CRM, GTM, territory, or enrichment semantics.

Example

iex> slot = ExBooking.Interval.new!(~U[2026-07-13 09:00:00Z], ~U[2026-07-13 09:30:00Z])
...>
...> resources = [
...>   %ExBooking.Resource{id: "b", timezone: "Etc/UTC"},
...>   %ExBooking.Resource{id: "a", timezone: "Etc/UTC"}
...> ]
...>
...> {:ok, [winner]} = ExBooking.Assignment.assign(resources, slot, [])
...> winner.id
"a"

Summary

Types

Opaque scoring hook over routing context.

Assignment strategy selector.

Functions

Picks the resource(s) that take a booking for slot.

Validates a strategy and any fairness inputs it consumes.

Types

scorer()

@type scorer() :: (ExBooking.Resource.t(), map() -> number())

Opaque scoring hook over routing context.

strategy()

@type strategy() :: atom() | {atom(), keyword()}

Assignment strategy selector.

Functions

assign(resources, slot, opts)

@spec assign([ExBooking.Resource.t()], ExBooking.Interval.t(), keyword()) ::
  {:ok, [ExBooking.Resource.t()]}
  | {:error, :no_eligible_resource | {:invalid, atom(), term()}}

Picks the resource(s) that take a booking for slot.

Options: :strategy (default :first_available), :scorer, :routing_context (passed to the scorer), :participants (default :one), and :capacity_required (for :pool).

Examples

iex> resources = [
...>   %ExBooking.Resource{id: "b", timezone: "Etc/UTC"},
...>   %ExBooking.Resource{id: "a", timezone: "Etc/UTC"}
...> ]
...>
...> slot = ExBooking.Interval.new!(~U[2026-07-13 09:00:00Z], ~U[2026-07-13 09:30:00Z])
...>
...> {:ok, [winner]} =
...>   ExBooking.Assignment.assign(resources, slot, strategy: :first_available)
...>
...> winner.id
"a"

validate(resources, opts)

@spec validate(
  [ExBooking.Resource.t()],
  keyword()
) :: :ok | {:error, {:invalid, :opts | :strategy | :resource_weight, term()}}

Validates a strategy and any fairness inputs it consumes.

Validation is separate from selection so callers can reject malformed input before performing availability work.

Example

iex> resource = %ExBooking.Resource{id: "resource_1", timezone: "Etc/UTC"}
...> ExBooking.Assignment.validate([resource], strategy: :round_robin)
:ok