# `Feel.Rules`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/rules.ex#L1)

Questions about a table of rules whose entries are unary tests, the input entries of a decision table: does the table cover
the inputs, do two rules share an input, is a rule never reached.

These queries need no DMN model: each column has a type for its input and each rule has a unary
test for each column (`"[18..150]"`, `"\"gold\""`, `"-"`). A rule is then a set of inputs, and the questions are about sets,
which `Feel.Type.Verdict` answers (D-158):

  * `coverage/3` is whether every input of the columns matches a rule;
  * `overlaps/3` is the pairs of rules that match a common input (the hit policy *unique* needs none);
  * `dead/3` is, for each rule, whether the rules before it take every input it could take (the policy *first*).

Each answer is **proved** or **refuted with an input**, or it is `:unknown`. A rule is bounded from outside and from inside by
two types, as the analysis of unary tests gives them: the inputs it **may** match (`Feel.Analysis.Refinement.restrict/2`, a
superset) and the inputs it **certainly** matches (`Feel.Analysis.Refinement.certain/2`, a subset). A table covers the inputs
if they are within what the rules certainly match; an input that is in no rule's superset shows it does not; two rules are
apart if their supersets are, and share an input that is in both subsets.

The entries that are read exactly are the comparisons of the input with one number, string, boolean, date or duration, an
interval, a list of them, `-` and `not(...)` of those. An entry that is not (a name, a function, a time) is not a subset for
certain, so a table with one may be `:unknown` where a person can see the answer; and a name makes its rule **open**: it may
match any input, and certainly none. Whether a rule matches `null` is found by running its entry on `null`.

The columns are `{name, type}` with the type a union of the input (`Feel.Type.Union`, for example
`Feel.Type.Union.from_spec(:number)` or one narrowed by its allowed values); a rule is a list of sources, one for each
column in order. An entry may instead be `:unknown` when its meaning depends on bindings or extensions that the caller
cannot preserve in source preparation. It may match any input of its column, including `null`, and certainly none;
other entries and rules can still establish a conclusion. An input is a map from the name of a column to a value.

All three queries accept an optional `:types` map of named types, with the same descriptors as
`Feel.Scope.new/2`. Supply the original type definitions when a source uses `instance of`, including
aliases and qualified or multiword names: both parsing and type resolution depend on this map.
These types remain available even when analysis has folded away the original type reference.
Source entries are prepared without the caller's value bindings and without its extensions.
The caller must supply the effective runtime test and use `:unknown` when that meaning cannot be
preserved. This module does not resolve DMN `inputValues` or recognize model-local function aliases.
`Dmn.Compiler.Checks` performs that adaptation from prepared table IR (DMN DD-43).
An unknown answer produces no proof; absence of a reported pair alone is meaningful only when
`overlaps/2` actually returns `{:ok, []}`, rather than an error or pairs whose verdict is unknown.

    iex> number = Feel.Type.Union.from_spec(:number) |> List.delete(:null)
    iex> columns = [{"age", number}]
    iex> Feel.Rules.coverage(columns, [["< 18"], [">= 18"]])
    :covered
    iex> {:uncovered, %{"age" => age}} = Feel.Rules.coverage(columns, [["< 18"], ["> 18"]])
    iex> Decimal.equal?(age, 18)
    true
    iex> Feel.Rules.overlaps(columns, [["< 18"], [">= 18"]])
    {:ok, []}
    iex> {:ok, [%{rules: {0, 1}, verdict: {:overlap, _input}}]} = Feel.Rules.overlaps(columns, [["< 10"], ["[5..30]"], [">= 40"]])
    iex> {:ok, [{0, {:live, _input}}, {1, :dead}]} = Feel.Rules.dead(columns, [["> 5"], ["> 10"]])

# `column`

```elixir
@type column() :: {String.t(), Feel.Type.Union.t()}
```

An input of the table: the name of a column and the type of its values.

# `input`

```elixir
@type input() :: %{optional(String.t()) =&gt; term()}
```

An input: a value for each column.

# `option`

```elixir
@type option() :: {:types, %{optional(String.t()) =&gt; Feel.Type.t()}}
```

Named types used to parse and resolve source entries, including aliases and qualified names.

# `problem`

```elixir
@type problem() ::
  {:error, {:entry, non_neg_integer(), String.t(), Feel.Error.t()}}
  | {:error, :columns}
```

What was wrong with a rule: its position, the column, and the error of the parse.

# `rule`

```elixir
@type rule() :: [String.t() | :unknown]
```

A rule: a unary-test source or `:unknown` for each column, in the order of the columns.

# `coverage`

```elixir
@spec coverage([column()], [rule()], [option()]) ::
  :covered | {:uncovered, input()} | :unknown | problem()
```

Whether every input of the columns matches a rule.

`:covered` is a proof. `{:uncovered, input}` is an input that no rule can match. `:unknown` is neither (the table is too
large for the search, or an entry is not read exactly). Pass named type definitions with `types: map`.

# `dead`

```elixir
@spec dead([column()], [rule()], [option()]) ::
  {:ok, [{non_neg_integer(), :dead | {:live, input()} | :unknown}]} | problem()
```

For each rule, in order: `:dead` if the rules before it take every input it could take, `{:live, input}` with an input it
takes that no rule before it can, or `:unknown`. Pass named type definitions with `types: map`.

# `overlaps`

```elixir
@spec overlaps([column()], [rule()], [option()]) ::
  {:ok,
   [
     %{
       rules: {non_neg_integer(), non_neg_integer()},
       verdict: {:overlap, input()} | :unknown
     }
   ]}
  | problem()
```

The pairs of rules that are not shown to be apart, each with `{:overlap, input}` if an input matches both, or `:unknown` if
none was found. A table where every pair is apart is `{:ok, []}`. Pass named type definitions with `types: map`.

---

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