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/3is whether every input of the columns matches a rule;overlaps/3is the pairs of rules that match a common input (the hit policy unique needs none);dead/3is, 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"]])
Summary
Types
An input of the table: the name of a column and the type of its values.
An input: a value for each column.
Named types used to parse and resolve source entries, including aliases and qualified names.
What was wrong with a rule: its position, the column, and the error of the parse.
A rule: a unary-test source or :unknown for each column, in the order of the columns.
Functions
Whether every input of the columns matches a rule.
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.
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.
Types
@type column() :: {String.t(), Feel.Type.Union.t()}
An input of the table: the name of a column and the type of its values.
An input: a value for each column.
@type option() :: {:types, %{optional(String.t()) => Feel.Type.t()}}
Named types used to parse and resolve source entries, including aliases and qualified names.
@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.
@type rule() :: [String.t() | :unknown]
A rule: a unary-test source or :unknown for each column, in the order of the columns.
Functions
@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.
@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.
@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.