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

One session must finish before another starts.

Every other constraint in this library asks *whether* two things can
coexist. This one asks about **order**, and it is what separates a
bag of tasks from a task graph: preparation before delivery, the
survey before the quote, the first interview before the second.

### The gap is part of the constraint

A precedence with no gap means the successor may begin the instant
its predecessor ends. `:gap` widens that — thirty minutes to write
up the survey — and `:within` caps it, which is what makes an
interview loop a loop rather than two appointments a fortnight
apart:

    Programme.precede(programme, "Screening", "Panel", gap: "PT15M", within: "PT2H")

> *"The panel follows screening, no sooner than fifteen minutes
> after and no later than two hours."*

`:within` is measured from the end of the predecessor, not its
start, so it does not shorten as the predecessor runs long.

### Why it is a pairwise constraint

Precedence relates exactly two sessions, which is why it lives
alongside the track and resource checks rather than needing new
machinery. A chain of three is two precedences, and the search
enforces them independently — it never has to reason about the
chain as a whole.

That is also why `Agenda.Arranger.conflict?/4` covers it, and so
the fixpoint bridge gets precedence for free: both solvers ask the
same question about the same pair.

`new/3` builds one, and `between/3` finds the precedence relating two
sessions, whichever way round they are given.

### In iCalendar

A precedence is RFC 9253's `RELATED-TO;RELTYPE=FINISHTOSTART`, under
which a task "cannot start until" its predecessor finishes, and
`:gap` is that relation's `GAP` parameter under the same name: the
lag between the two. The names match because the idea does, so keep
them.

Two things differ. RFC 9253 allows a negative `GAP`, a lead, where
`:gap` is the least time that must pass; and it has nothing that
caps the lag, so an export drops `:within`.

# `t`

```elixir
@type t() :: %Agenda.Precedence{
  first: String.t(),
  gap: Agenda.Availability.pattern() | nil,
  then: String.t(),
  within: Agenda.Availability.pattern() | nil
}
```

An ordering between two sessions.

# `between`

```elixir
@spec between([t()], String.t(), String.t()) :: {t(), :in_order | :reversed} | nil
```

The precedence relating two sessions, whichever way round they are
given, or `nil` when they are unordered.

### Arguments

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

* `a` and `b` are session names.

### Returns

* `{precedence, :in_order}` when `a` precedes `b`,
  `{precedence, :reversed}` when `b` precedes `a`, or `nil`.

### Examples

    iex> precedences = [Agenda.Precedence.new("Survey", "Quote")]
    iex> {_precedence, order} = Agenda.Precedence.between(precedences, "Quote", "Survey")
    iex> order
    :reversed

    iex> Agenda.Precedence.between([], "A", "B")
    nil

# `new`

```elixir
@spec new(String.t(), String.t(), keyword()) :: t()
```

Build a precedence.

### Arguments

* `first` is the name of the session that must finish first.

* `then` is the name of the session that follows it.

* `options` is a keyword list of options.

### Options

* `:gap` is the least time that must pass between them, a duration.
  The default is none, meaning the successor may start immediately.

* `:within` is the most time that may pass, measured from the end of
  `first`. The default is none, meaning no upper bound.

### Returns

* a `t:t/0`.

### Examples

    iex> precedence = Agenda.Precedence.new("Survey", "Quote", gap: "PT30M")
    iex> {precedence.first, precedence.then}
    {"Survey", "Quote"}

---

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