Agenda.Arrangement (Agenda v0.3.0)

Copy Markdown View Source

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

Types

t()

A candidate placement of a session.

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

t()

@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(a, b)

@spec compare(t(), t()) :: :lt | :eq | :gt

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

  • a and b are each a t/0.

Returns

  • :lt, :eq or :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"]

explain(arrangement)

@spec explain(t()) :: String.t()

A one-line description of the arrangement.

Arguments

  • arrangement is a t/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"

resource(arrangement, name)

@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

  • arrangement is a t/0.

  • name is the requirement's name to read.

Returns

  • the resource filling name, or nil when 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

resources(arrangement)

@spec resources(t()) :: [Agenda.Resource.t()]

Every resource allocated, across all requirements.

Arguments

  • arrangement is a t/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"]

resources(arrangement, name)

@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

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)
[]