One way a session could be held — a time, and a resource for every requirement.
Planning returns arrangements ranked best-first rather than a single answer, because "best" depends on preferences the caller may want to overrule. An arrangement is inert: nothing is committed until it is allocated.
resources/1 and resources/2 give the resources it allocates,
across every requirement or in one, and resource/2 the single
resource filling a requirement. explain/1 describes it in a line, and compare/2
orders arrangements by time, so
Enum.sort(arrangements, Agenda.Arrangement) puts them in time order.
Summary
Functions
Compare two arrangements chronologically.
A one-line description of the arrangement.
The single resource filling one requirement, or nil.
Every resource allocated, across all requirements.
The resources filling one requirement.
Types
@type t() :: %Agenda.Arrangement{ allocations: %{optional(atom()) => [Agenda.Resource.t()]}, attending: %{optional(atom()) => [Agenda.Resource.t()]}, interval: Tempo.t(), recurrence_id: Tempo.t() | nil, score: number(), series: String.t() | nil, session: String.t() }
A candidate placement of a session.
Functions
Compare two arrangements chronologically.
Present so that Enum.sort/2 and Enum.sort_by/3 accept this module
the way they accept Date, Time and Tempo:
Enum.sort(arrangements, Agenda.Arrangement)Sorting a programme by time is the commonest thing anyone does with a layout, and without this the call site has to reach inside — first for the interval, then often for its start — which is three levels of structure to say "in the order they happen".
Ordering is Tempo.compare/2 on the intervals, so it is by start and
then by end: two sessions beginning together put the shorter first.
That is a total order, deliberately, where Tempo.relation/2 gives
the thirteen interval relations. Sorting needs the former; reasoning
about overlap needs the latter.
Arguments
aandbare each at/0.
Returns
:lt,:eqor:gt.
Examples
iex> import Tempo.Sigils
iex> morning = %Agenda.Arrangement{interval: ~o"2026-06-16T09:00:00/2026-06-16T10:00:00"}
iex> midday = %Agenda.Arrangement{interval: ~o"2026-06-16T12:00:00/2026-06-16T13:00:00"}
iex> Agenda.Arrangement.compare(morning, midday)
:lt
iex> import Tempo.Sigils
iex> morning = %Agenda.Arrangement{interval: ~o"2026-06-16T09:00:00/2026-06-16T10:00:00"}
iex> midday = %Agenda.Arrangement{interval: ~o"2026-06-16T12:00:00/2026-06-16T13:00:00"}
iex> [midday, morning]
...> |> Enum.sort(Agenda.Arrangement)
...> |> Enum.map(& &1.interval)
[~o"2026Y6M16DT9H0M0S/T10H0M0S",
~o"2026Y6M16DT12H0M0S/T13H0M0S"]
A one-line description of the arrangement.
Arguments
arrangementis at/0.
Returns
- a sentence naming the time and the resources.
Examples
iex> import Tempo.Sigils
iex> boardroom = Agenda.Resource.new("Boardroom")
iex> arrangement = %Agenda.Arrangement{
...> interval: ~o"2026-06-16T10:00:00/2026-06-16T11:00:00",
...> allocations: %{room: [boardroom]}
...> }
iex> Agenda.Arrangement.explain(arrangement)
"2026Y6M16DT10H0M0S/T11H0M0S — room: Boardroom"
@spec resource(t(), atom()) :: Agenda.Resource.t() | nil
The single resource filling one requirement, or nil.
The common case, because most requirements take exactly one: a session has
one room even when it has four speakers. Where a requirement holds several,
this is the first and resources/2 is the question to ask.
Arguments
arrangementis at/0.nameis the requirement's name to read.
Returns
- the resource filling
name, ornilwhen nothing does.
Examples
iex> boardroom = Agenda.Resource.new("Boardroom")
iex> arrangement = %Agenda.Arrangement{allocations: %{room: [boardroom]}}
iex> Agenda.Arrangement.resource(arrangement, :room).name
"Boardroom"
iex> arrangement = %Agenda.Arrangement{allocations: %{room: []}}
iex> Agenda.Arrangement.resource(arrangement, :room)
nil
@spec resources(t()) :: [Agenda.Resource.t()]
Every resource allocated, across all requirements.
Arguments
arrangementis at/0.
Returns
- the allocated resources, requirement order preserved.
Examples
iex> boardroom = Agenda.Resource.new("Boardroom")
iex> arrangement = %Agenda.Arrangement{allocations: %{room: [boardroom]}}
iex> Enum.map(Agenda.Arrangement.resources(arrangement), & &1.name)
["Boardroom"]
@spec resources(t(), atom()) :: [Agenda.Resource.t()]
The resources filling one requirement.
resources/1 answers "who and what is booked"; this answers "what
is in the room slot". Without it a caller reaches into
arrangement.allocations — or worse, hunts through every resource
for one carrying a :seats attribute, which guesses at something a
requirement already states.
Arguments
arrangementis at/0.nameis the requirement's name to read, as given toAgenda.needs/2orAgenda.Session.roster/3.
Returns
- the resources filling
name, or[]when the arrangement has none. An absent requirement is not an error: a session that never asked for a projector simply has no projector.
Examples
iex> boardroom = Agenda.Resource.new("Boardroom")
iex> alice = Agenda.Resource.new("Alice")
iex> arrangement = %Agenda.Arrangement{allocations: %{room: [boardroom], speaker: [alice]}}
iex> Enum.map(Agenda.Arrangement.resources(arrangement, :room), & &1.name)
["Boardroom"]
iex> arrangement = %Agenda.Arrangement{allocations: %{room: []}}
iex> Agenda.Arrangement.resources(arrangement, :projector)
[]