Agenda.Layout (Agenda v0.1.0)

Copy Markdown View Source

A programme laid out as far as it would go — what was placed, and what was left out.

Agenda.Arranger.arrange/3 fails the whole programme by default: if one session cannot be held, there is no arrangement. That is the right answer when the programme is a unit, and the wrong one when it is a wish list. A conference of forty sessions where thirty-eight fit is not a failure — it is thirty-eight sessions and two conversations to have.

A layout is what arrange/3 returns under unplaced: :allow when it could not place everything. It is never returned as {:ok, …}: the tag is {:partial, layout}, so a caller who has not thought about incompleteness cannot mistake one for a finished programme.

case Agenda.arrange(programme, pool, unplaced: :allow) do
  {:ok, arrangements} -> publish(arrangements)
  {:partial, layout}  -> review(layout.placed, layout.unplaced)
  {:error, reason}    -> abandon(reason)
end

Each entry in unplaced is a Agenda.Infeasible.t/0, so a session that could not be held still says why.

Whether this is the best partial layout

minimal? separates two answers that would otherwise look identical:

  • true — no layout leaves out fewer sessions. The search either finished, or matched the most any arrangement of these candidates could place.

  • false — the best found before the search hit its :nodes cap. A better layout may exist; raise :nodes and ask again.

A caller that treats the second as though it were the first will turn away work it could have taken, so the distinction is a field rather than a footnote.

The score

score is what the layout cost against the programme's Agenda.Preferences, where 0 is ideal. score_proven? is its counterpart to minimal? and answers a different question:

  • minimal? — is this the fewest sessions left out? Proven by the first pass, and never affected by the second.

  • score_proven? — did the scoring pass finish, or stop at its :score_nodes budget with the best it had found?

The two are independent. A layout can be provably minimal and merely well-scored, which is the common case, because the count is cheap to prove and the score is not.

Agenda.explain_score/3 breaks the number down per preference, since a bare total says a layout is worse without saying how.

Summary

Types

t()

A programme placed as far as it would go.

Functions

The layout as a sentence — how much was placed, and why the rest was not.

The names of the sessions that could not be placed.

Types

t()

@type t() :: %Agenda.Layout{
  minimal?: boolean(),
  placed: [Agenda.Arrangement.t()],
  programme: String.t(),
  score: number(),
  score_proven?: boolean(),
  unplaced: [Agenda.Infeasible.t()]
}

A programme placed as far as it would go.

Functions

explain(layout)

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

The layout as a sentence — how much was placed, and why the rest was not.

Arguments

  • layout is a t/0.

Returns

  • a sentence naming the programme, the count placed, and each session that was left out with its reasons. A layout that is not known to be the best says so, since the reader's next move differs.

Examples

iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.explain(Agenda.Layout.new("Conf", [], [reason]))
"Conf: 0 of 1 sessions placed. Workshop cannot be held: no room seats 8"

iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.explain(Agenda.Layout.new("Conf", [], [reason], false))
"Conf: 0 of 1 sessions placed, and the search stopped at its node limit before proving that is the fewest left out — raise :nodes to be sure. Workshop cannot be held: no room seats 8"

new(programme, placed, unplaced, minimal? \\ true)

@spec new(
  String.t(),
  [Agenda.Arrangement.t()],
  [Agenda.Infeasible.t(), ...],
  boolean()
) :: t()

Build a layout.

Arguments

  • programme is the programme's name.

  • placed is the list of Agenda.Arrangement.t/0 that were successfully placed.

  • unplaced is a non-empty list of Agenda.Infeasible.t/0, one per session that could not be held.

  • minimal? is whether no layout leaves out fewer. The default is true.

Returns

Examples

iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> layout = Agenda.Layout.new("Conf", [], [reason])
iex> {Enum.map(layout.unplaced, & &1.session), layout.minimal?}
{["Workshop"], true}

iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.new("Conf", [], [reason], false).minimal?
false

unplaced_sessions(layout)

@spec unplaced_sessions(t()) :: [String.t()]

The names of the sessions that could not be placed.

Arguments

  • layout is a t/0.

Returns

  • the session names, in the order they were given up on.

Examples

iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.unplaced_sessions(Agenda.Layout.new("Conf", [], [reason]))
["Workshop"]