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

What makes one workable layout better than another.

Every constraint elsewhere in this library is **hard**: a room is
eligible or it is not, two sessions clash or they do not. A
preference is soft. It never makes a layout invalid, it only makes
it worse — and a programme with none is exactly as correct as one
with several, just less opinionated about which of the workable
answers you get.

### Penalties, not rewards

A preference counts *violations*, and lower is better. Counting what
is wrong rather than what is right means the ideal layout scores
zero, which is a number that means something on its own — where a
reward total only means something next to another reward total.

### What is and is not promised

`Agenda.Arranger.arrange/3` optimises **lexicographically**: it
first places as many sessions as it can, provably, and only then
prefers a better score among the layouts it reaches. The count is
proven; the score is not, and `Agenda.Layout`'s `score_proven?`
says which you have. This is a deliberate limit — proving soft
optimality means bounding a weighted cost, and no bound that cheap
exists here. A programme that genuinely needs it wants a solver.

### Writing your own

A preference is a name, a weight, and a function from the placements
to a violation count:

    Agenda.Programme.prefer(programme, {:no_friday_afternoons, &count_friday_afternoons/2},
      weight: 3)

The function receives the arrangements and a context carrying
`:programme` and `:pool`, so it can consult the tracks and the
resources as well as the placements.

`new/2` builds one. `score/3` gives what a layout costs against a list
of preferences, and `explain/3` what each contributed, as sentences.

# `context`

```elixir
@type context() :: %{programme: Agenda.Programme.t(), pool: list()}
```

The information a preference may consult beyond the placements.

# `t`

```elixir
@type t() :: %Agenda.Preference{
  count: (list(), context() -&gt; non_neg_integer()),
  name: atom(),
  weight: number()
}
```

A named, weighted soft constraint.

# `explain`

```elixir
@spec explain([t()], [Agenda.Arrangement.t()], context()) :: [String.t()]
```

What each preference contributed, as sentences.

A single number says a layout is worse without saying how, which is
the same failure `explain/2` exists to avoid for eligibility.

### Arguments

* `preferences` is a list of `t:t/0`.

* `arrangements` is the placements to score.

* `context` is a `t:context/0`.

### Returns

* one sentence per preference, in the order they were declared.

### Examples

    iex> {:ok, preference} = Agenda.Preference.new(:room_changes, weight: 10)
    iex> context = %{programme: Agenda.programme("Conf"), pool: []}
    iex> Agenda.Preference.explain([preference], [], context)
    ["room_changes: 0 × 10 = 0"]

# `new`

```elixir
@spec new(atom() | {atom(), function()}, keyword()) :: {:ok, t()} | {:error, term()}
```

Build a preference.

### Arguments

* `preference` is a built-in name — `:room_changes`, `:room_spread`
  or `:resource_wishes` — or a `{name, function}` pair for one of
  your own.

* `options` is a keyword list of options.

### Options

* `:weight` is how much each violation costs. The default is `1`.

### Returns

* `{:ok, t:t/0}`, or

* `{:error, reason}` when the name is neither a built-in nor paired
  with a function.

### Examples

    iex> {:ok, preference} = Agenda.Preference.new(:room_changes, weight: 10)
    iex> {preference.name, preference.weight}
    {:room_changes, 10}

    iex> Agenda.Preference.new(:teleportation)
    {:error, {:unknown_preference, :teleportation}}

# `score`

```elixir
@spec score([t()], [Agenda.Arrangement.t()], context()) :: number()
```

What a layout costs against `preferences`.

### Arguments

* `preferences` is a list of `t:t/0`.

* `arrangements` is the placements to score.

* `context` is a `t:context/0`.

### Returns

* the total penalty, where `0` is ideal.

### Examples

    iex> Agenda.Preference.score([], [], %{programme: Agenda.programme("C"), pool: []})
    0

---

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