# `Dmn`
[🔗](https://github.com/FeeJai/BDM/blob/dmn-v0.1.0/apps/dmn/lib/dmn.ex#L1)

A DMN engine built on the FEEL library.

FEEL stays a separate package (`:feel`). This application reads a model (`load/2`), compiles it
to prepared FEEL expressions (`Dmn.Compiler`) and evaluates the required units (`Dmn.Run`).

A model is untrusted input here, and so is every model it imports: see `Dmn.XML` and `Dmn.Limits`.
Public operations use `Feel.Worker` by default. `guard:` configures the policy;
`sandbox: false` skips the outer worker for trusted input, keeping model and FEEL
limits, XML parsing guards and optional table-check guards.

# `compile`

```elixir
@spec compile(binary() | Dmn.Model.t(), keyword()) ::
  {:ok, Dmn.Compiled.t()} | {:error, Dmn.Error.t()}
```

Compiles a model (`Dmn.Compiler`): every text of it is parsed, every name resolved and every expression typed and prepared, once.
Accepts XML bytes or a loaded `Dmn.Model`; bytes include loading in the same worker deadline.
Containment is on by default. `guard:` configures it and `sandbox: false` opts out for trusted input.
What is wrong with the model is in the diagnostics of the result, and the model is compiled all the same, unless `validate: true` is
given, which refuses a model that has an error (`validate/1`). `extensions:` switches on additions to DMN 1.6 (`Dmn.Extension`), such as
the names of DMN 1.1 for the date and time types; a name or module that is not an extension is `:invalid_options`.

`features:` switches on what DMN 1.6 does not have (`Dmn.Features`, DD-29): `:recursion`, which lets a business knowledge model or a
decision service invoke itself or one that invokes it, and enables guarded recursive item definitions
with complete finite-value checks (DD-22). Other features are the additions of `Dmn.Extension`, such as the names of DMN 1.1 for
the date and time types, by name or as modules. Only the caller can say so. A name that is no feature is `:invalid_options`. The
spellings `recursion: true` and `extensions: [...]` are the same, and add to it.

The options are those of `Dmn.Compiler.compile/2` (`:resolver`, `:limits`, `:feel_limits`, `:check_tables`, `:backend`, and the features above), and
`guard:` (`Feel.Worker`: deadline, memory and transfer limits). Options that are not a keyword list, and a `guard:` that is not
a valid worker policy, are `{:error, %Dmn.Error{kind: :compile, code: :invalid_options}}`, as for
`evaluate/4` with `kind: :run`: nothing raises for them (DD-36). So are `limits:` that are neither a `Dmn.Limits` nor a keyword list of
its fields, and `feel_limits:` that is not a `Feel.Limits` (DD-39), with the option in `details.option`.

    iex> xml = ~s(<definitions xmlns="https://www.omg.org/spec/DMN/20240513/MODEL/" name="m" namespace="urn:m"><decision id="d" name="Double"><variable name="Double" typeRef="number"/><informationRequirement><requiredInput href="#i"/></informationRequirement><literalExpression><text>Amount * 2</text></literalExpression></decision><inputData id="i" name="Amount"><variable name="Amount" typeRef="number"/></inputData></definitions>)
    iex> {:ok, model} = Dmn.load(xml)
    iex> {:ok, compiled} = Dmn.compile(model)
    iex> {:ok, value, []} = Dmn.evaluate(compiled, "Double", %{"Amount" => Decimal.new(21)})
    iex> Decimal.equal?(value, 42)
    true

# `evaluate`

```elixir
@spec evaluate(Dmn.Compiled.t(), String.t(), map(), keyword()) ::
  {:ok, Feel.Value.t(), [Dmn.Diagnostic.t()]} | {:error, Dmn.Error.t()}
```

Evaluates the decision called `name` with the inputs, in a compiled model: `{:ok, value, diagnostics}`.

`Dmn.Run` says what the inputs are and what an error is. Options: `:mode` (`:lenient` or `:strict`), the options of a run of
FEEL (`:clock`, `:entropy`, `:limits`), and `guard:` (`Feel.Worker`: deadline, memory and transfer limits). What a caller got from
outside can be given as it is, and nothing raises (DD-36): a `name` that is not a string is `:unknown_decision`, `inputs` that are not a
map of names are `:invalid_inputs`, and options that are not a keyword list, a `mode` that is none, and a `guard:` that is not a
valid worker policy are `:invalid_options`, all with `kind: :run`.
Options also accept `sandbox: false` for trusted input. Unknown keys, repeated keys and invalid
modes are rejected before selection or worker startup. Guard policy keys must be unique too.

# `evaluate_all`

```elixir
@spec evaluate_all(Dmn.Compiled.t(), [String.t()], map(), keyword()) ::
  {:ok, Dmn.Result.t()} | {:error, Dmn.Error.t()}
```

Evaluates several decisions at once, with the inputs, in a compiled model: the `Dmn.Result`, which has the value of each and
the diagnostics of everything that ran. A decision that more than one of them needs is run once.

Takes the options of `evaluate/4`, and nothing raises for them or for what a caller got from outside: `names` that are not a list, and
`inputs` that are not a map, are `:invalid_inputs`, and a name in the list that is not a string is `:unknown_decision` (DD-36).

# `invoke`

```elixir
@spec invoke(Dmn.Compiled.t(), String.t(), map() | list(), keyword()) ::
  {:ok, Feel.Value.t(), [Dmn.Diagnostic.t()]} | {:error, Dmn.Error.t()}
```

Calls a business knowledge model or a decision service of a compiled model as a function (`Dmn.Run.invoke/4`): the arguments are a
map of the names of the parameters to values, or a list in the order of the parameters. `{:ok, value, diagnostics}`.

A service returns its single output value, a context keyed by canonical decision names for several
outputs, or an empty context for no outputs. A missing or `Any` service result declaration keeps
the result type derived from its output decisions. A readable non-`Any` declaration controls the
final conversion; a function item on the service variable contributes its output type, while a
function-valued output decision keeps its function layer. Output and service constraints are enforced
at their respective boundaries, including recursive descendant constraints when recursion is enabled.

A service inherits faults from its input data, input decisions, encapsulated decisions and output
decisions. These are reported even when the caller supplies the inputs: strict mode stops on a fault,
and lenient mode reports it and continues. The same contracts apply when a decision calls the service.

Takes `guard:` as `evaluate/4` does. What is not a function that can be called (a decision, a knowledge model with no logic, an external
one), a `name` that is not a string (`:unknown_decision`), arguments of another shape and options that are not what they should be are an
error value (`:not_a_function`, `:invalid_arguments`, `:invalid_options`) and not an exception.

A call that ends on a limit of FEEL is that error (`:limit_exceeded`, with `details.kind: :limit`), as a run of a decision is; `:strict`
is the code of an error in the `:strict` mode, and of that only (DD-36).

# `load`

```elixir
@spec load(binary(), keyword()) :: {:ok, Dmn.Model.t()} | {:error, Dmn.Error.t()}
```

Reads a DMN model from the bytes of an XML document.

The model is data, as the document says it: nothing is resolved or compiled yet. Options: `:limits`, a `t:Dmn.Limits.t/0` or the
fields of one to replace; and `:base_uri`, the URI the document was read from (`Dmn.Model` has it as `base_uri`), which a resolver
that resolves a relative `locationURI` of its imports against the document that has them needs (`Dmn.Resolver`). It is not read
from the document and is not opened. Unknown and repeated option keys are rejected. Options that are not a keyword list, a `:base_uri` that is not a string, and `:limits` that are
neither a `Dmn.Limits` nor a keyword list of its fields (positive integers) are `{:error, %Dmn.Error{kind: :model, code:
:invalid_options}}`: nothing raises for them (DD-39).

    iex> xml = ~s(<definitions xmlns="https://www.omg.org/spec/DMN/20240513/MODEL/" name="m" namespace="urn:m"><inputData id="i" name="x"/></definitions>)
    iex> {:ok, model} = Dmn.load(xml)
    iex> Enum.map(model.input_data, & &1.name)
    ["x"]
    iex> {:error, %Dmn.Error{code: :doctype_not_allowed}} = Dmn.load(~s(<!DOCTYPE a [<!ENTITY x "y">]><a/>))

# `load!`

```elixir
@spec load!(binary(), keyword()) :: Dmn.Model.t()
```

Like `load/2`, and raises the `Dmn.Error`.

# `prepare_call`

```elixir
@spec prepare_call(Dmn.Compiled.t(), String.t(), keyword()) ::
  {:ok, Dmn.PreparedCall.t()} | {:error, Dmn.Error.t()}
```

Prepares repeated evaluation of one decision: resolves its name and dependency plan once, with fixed run options.

Takes the options of `evaluate/4`, including `:sandbox` and `:guard`, which every run then applies. Unknown or ambiguous
names and invalid mode, sandbox or guard options are errors at preparation. Inputs are supplied to `run/2`, which returns the same value and diagnostics as `evaluate/4`.
Input validation, declared types, limits, diagnostics and the clock of each run still apply. A supplied clock function
is called at execution, not preparation. This selects existing compiled expressions; it does not generate more code.
Prepare a new call to change the decision, model or options. This evaluates an element; use `invoke/4` to invoke a BKM
or decision service as a function.

    {:ok, call} = Dmn.prepare_call(compiled, "Double", mode: :strict)
    {:ok, value, diagnostics} = Dmn.run(call, %{"Amount" => Decimal.new(21)})

# `run`

```elixir
@spec run(Dmn.PreparedCall.t(), map()) ::
  {:ok, Feel.Value.t(), [Dmn.Diagnostic.t()]} | {:error, Dmn.Error.t()}
```

Evaluates a prepared decision with fresh inputs: `{:ok, value, diagnostics}` or `{:error, Dmn.Error.t()}`.

Input names are validated and normalized on every call, and intermediate values and diagnostics are never shared between
runs. Inside a worker (the default, `Feel.Worker`) the request is selected and admitted as `evaluate/4` does it, so the
worker is given the part of the model the decision needs and not the prepared plan; with `sandbox: false` (or inside a
worker that is running already) the plan from `prepare_call/3` is used and no decision lookup or planning is repeated.

# `validate`

```elixir
@spec validate(Dmn.Compiled.t()) :: :ok | {:error, Dmn.Error.t()}
```

Whether a compiled model is valid: `:ok` if the compile found no error in it, and `{:error, %Dmn.Error{kind: :model, code:
:invalid_model}}` with the errors (`details.diagnostics`) if it did.

`compile/2` normally retains model defects as diagnostics, as DMN has an element that cannot be evaluated be `null` (7.3.8), and a run
of such a model goes on. Invalid caller options, resource limits, and `validate: true` can instead refuse compilation. So a compile that succeeded, and a run that did not end in `:strict` mode, do not say that the model is
valid: an element that no decision asked for is not run, and a run in lenient mode goes on. A caller that wants a model that is
valid asks this once, or gives `validate: true` to `compile/2`. Confirmed unresolved names also refuse a model,
including names in unreachable branches. Other warnings and infos do not.

    iex> xml = ~s(<definitions xmlns="https://www.omg.org/spec/DMN/20240513/MODEL/" name="m" namespace="urn:m"><decision id="d" name="D"><variable name="D"/><informationRequirement><requiredInput href="#nope"/></informationRequirement><literalExpression><text>1</text></literalExpression></decision></definitions>)
    iex> {:ok, model} = Dmn.load(xml)
    iex> {:ok, compiled} = Dmn.compile(model)
    iex> {:error, %Dmn.Error{code: :invalid_model, details: %{count: 1}}} = Dmn.validate(compiled)
    iex> {:error, %Dmn.Error{code: :invalid_model}} = Dmn.compile(model, validate: true)

---

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