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

A requirement — what a session demands of the resources allocated to
it.

A requirement is named, so that an arrangement can say *which* requirement a
resource was chosen for, and comes in two shapes:

* **by attribute** — "a room seating at least eight with video
  conferencing"; any resource satisfying the predicates will do.

* **by roster** — "Alice, Bob and Carol"; these named resources
  specifically.

Both shapes are the same struct, so a session can hold a list of
requirements without the caller sorting them into kinds.

`new/2` builds a requirement from attribute predicates and `roster/2`
one naming specific resources; `merge/2` and `induce/2` combine them.
`eligible/2`, `eligible?/2` and `unmet/2` hold resources to a
requirement: which satisfy it, whether one does, and every way one
does not.

# `t`

```elixir
@type t() :: %Agenda.Requirement{
  attributes: %{optional(atom()) =&gt; Agenda.Predicate.t()},
  name: atom(),
  roster: [Agenda.Resource.t()]
}
```

What a session demands, under a name.

# `eligible`

```elixir
@spec eligible(t(), [Agenda.Resource.t()]) :: [Agenda.Resource.t()]
```

The resources from `candidates` that satisfy `requirement`.

### Arguments

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

* `candidates` is a list of `t:Agenda.Resource.t/0`.

### Returns

* the eligible resources, in the order given.

### Examples

    iex> import Agenda.Predicate
    iex> big = Agenda.Resource.new("Boardroom", seats: 8)
    iex> small = Agenda.Resource.new("Meeting room 2", seats: 4)
    iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
    iex> Agenda.Requirement.eligible(requirement, [big, small])
    ...> |> Enum.map(& &1.name)
    ["Boardroom"]

# `eligible?`

```elixir
@spec eligible?(t(), Agenda.Resource.t()) :: boolean()
```

`true` when `resource` satisfies every attribute of `requirement`.

### Arguments

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

* `resource` is a `t:Agenda.Resource.t/0`.

### Returns

* `true` or `false`.

### Examples

    iex> import Agenda.Predicate
    iex> boardroom = Agenda.Resource.new("Boardroom", seats: 8)
    iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
    iex> Agenda.Requirement.eligible?(requirement, boardroom)
    true

# `induce`

```elixir
@spec induce(t(), [Agenda.Resource.t()]) :: t()
```

Fold the requirements induced by `resources` into `requirement`.

This is what makes `requires:` on a person bind the room: allocating
Alice, who requires step-free access, tightens the room requirement
so that an inaccessible room stops being eligible.

### Arguments

* `requirement` is the `t:t/0` to tighten.

* `resources` is a list of `t:Agenda.Resource.t/0` whose
  `requires` should be folded in.

### Returns

* a `t:t/0` carrying the additional predicates.

### Examples

    iex> alice = Agenda.Resource.new("Alice", requires: [step_free_access: true])
    iex> room = Agenda.Requirement.new(:room, seats: 8)
    iex> tightened = Agenda.Requirement.induce(room, [alice])
    iex> Map.keys(tightened.attributes)
    ...> |> Enum.sort()
    [:seats, :step_free_access]

# `merge`

```elixir
@spec merge(t(), t()) :: t()
```

Combine two requirements of the same name into one.

Naming a requirement twice is one demand stated in two calls, not two
demands: `roster(:speaker, [alice])` followed by
`roster(:speaker, [bob])` wants both people on stage. The
alternative — keeping both requirements — cannot be honoured,
because an arrangement fills each requirement once, so the second would
quietly displace the first.

Attributes combine the way `induce/2` combines them, with the later
predicate winning where both name the same attribute. Rosters are
concatenated, and a resource named twice is named once.

### Arguments

* `requirement` is the `t:t/0` stated first.

* `addition` is the `t:t/0` stated after it, under the same name.

### Returns

* one `t:t/0` demanding everything both demanded.

### Examples

    iex> alice = Agenda.Resource.new("Alice")
    iex> bob = Agenda.Resource.new("Bob")
    iex> first = Agenda.Requirement.roster(:speaker, [alice])
    iex> second = Agenda.Requirement.roster(:speaker, [bob])
    iex> Agenda.Requirement.merge(first, second).roster
    ...> |> Enum.map(& &1.name)
    ["Alice", "Bob"]

    iex> import Agenda.Predicate
    iex> seating = Agenda.Requirement.new(:room, seats: at_least(250))
    iex> wired = Agenda.Requirement.new(:room, projector: true)
    iex> Agenda.Requirement.merge(seating, wired).attributes
    ...> |> Map.keys()
    ...> |> Enum.sort()
    [:projector, :seats]

# `new`

```elixir
@spec new(atom(), keyword()) :: t()
```

Build a requirement from attribute predicates.

Bare values are coerced to `Agenda.Predicate.exactly/1`, so
`video_conferencing: true` and `seats: at_least(8)` may be mixed
freely.

### Arguments

* `name` is the requirement's name, such as `:room`.

* `predicates` is a keyword list of attribute predicates.

### Returns

* a `t:t/0`.

### Examples

    iex> import Agenda.Predicate
    iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
    iex> requirement.attributes[:seats].op
    :at_least

# `roster`

```elixir
@spec roster(atom(), [Agenda.Resource.t()]) :: t()
```

Build a requirement naming specific resources.

### Arguments

* `name` is the requirement's name, such as `:attendees`.

* `resources` is the list of required `t:Agenda.Resource.t/0`.

### Returns

* a `t:t/0`.

### Examples

    iex> alice = Agenda.Resource.new("Alice")
    iex> requirement = Agenda.Requirement.roster(:attendees, [alice])
    iex> Enum.map(requirement.roster, & &1.name)
    ["Alice"]

# `unmet`

```elixir
@spec unmet(t(), Agenda.Resource.t()) :: [String.t()]
```

Every way `resource` fails `requirement`, as readable phrases.

An empty list means the resource is eligible — so `eligible?/2` is
`unmet/2` returning nothing, and the explanation is never computed
separately from the decision.

### Arguments

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

* `resource` is a `t:Agenda.Resource.t/0`.

### Returns

* a list of phrases, one per unsatisfied attribute, empty when the
  resource qualifies.

### Examples

    iex> import Agenda.Predicate
    iex> small = Agenda.Resource.new("Meeting room 2", seats: 4)
    iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
    iex> Agenda.Requirement.unmet(requirement, small)
    ["seats is 4 — needs at least 8"]

---

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