Agenda.Arrangement (Agenda v0.1.0)

Copy Markdown View Source

One way a session could be held — a time, and a resource for every role.

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.

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 role, or nil.

Every resource allocated, across all roles.

The resources filling one role.

Types

t()

@type t() :: %Agenda.Arrangement{
  allocations: %{optional(atom()) => [Agenda.Resource.t()]},
  attending: %{optional(atom()) => [Agenda.Resource.t()]},
  interval: Tempo.Interval.t(),
  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, role)

@spec resource(t(), atom()) :: Agenda.Resource.t() | nil

The single resource filling one role, or nil.

The common case, because most roles take exactly one: a session has one room even when it has four speakers. Where a role holds several, this is the first and resources/2 is the question to ask.

Arguments

  • arrangement is a t/0.

  • role is the role to read.

Returns

  • the resource filling role, 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 roles.

Arguments

  • arrangement is a t/0.

Returns

  • the allocated resources, role 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, role)

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

The resources filling one role.

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 role already states.

Arguments

Returns

  • the resources filling role, or [] when the arrangement has none. An absent role 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)
[]