# `Agenda.Layout`
[🔗](https://github.com/elixir-tempo/agenda/blob/v0.3.0/lib/agenda/layout.ex#L1)

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 `t: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.Preference`s, where `0` is ideal. `score_proven?` is its
counterpart to `minimal?` and answers a *different* question:

* `minimal?` — whether this is the fewest sessions left out, proven
  by the first pass and never affected by the second.

* `score_proven?` — whether the scoring pass finished, rather than
  stopping 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.

`new/4` builds one. `explain/1` gives it as a sentence, how much was
placed and why the rest was not, and `unplaced_sessions/1` names the
sessions left out.

# `t`

```elixir
@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.

# `explain`

```elixir
@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: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`

```elixir
@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 `t:Agenda.Arrangement.t/0` that were
  successfully placed.

* `unplaced` is a non-empty list of `t: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

* a `t:t/0`.

### 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`

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

The names of the sessions that could not be placed.

### Arguments

* `layout` is a `t: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"]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
