Decision Tables

In EL, a decision table is a construct that expresses the equivalent logic of a multi-branch construct that returns a single expression as a result. There are two flavours, both familiar to programmers in mainstream languages: the condition chain (i.e. an if/then/else construct) and the case table (i.e. a case statement). The evaluation of both constructs determines which of a number of possible expressions to return as the result, based on the prior evaluation of branch conditions, whose particular form depends on which flavour of construct is used. Both constructs are thus purely functional, i.e. their branches cannot contain statements (i.e. assignments, procedure calls etc), only expressions.

Condition Chain (if/then)

The syntax for a condition chain (the if/then equivalent) takes a standard form and a compact form. The standard form is as follows.

    choice in
        <condition_1>:  <expression_1>,
        <condition_2>:  <expression_2>,
        ...
        <condition_N>:  <expression_N>,
        *:              <else expression>
    ;

In the above, the '*' character is understood as a wildcard, meaning 'all other cases'. A final row containing '*' is thus equivalent to a catch-all 'else' branch in the if/then/else chain of a procedural language.

A realistic example is illustrated below, making use of line comments to visually aid the author.

    molecular_subtype: Terminology_term
        Result := choice in
            =========================================================
            er_positive and
            her2_negative and
            not ki67.in_range ([high]):    #luminal_A,
            ---------------------------------------------------------
            er_positive and
            her2_negative and
            ki67.in_range ([high]):        #luminal_B_HER2_negative,
            ---------------------------------------------------------
            er_positive and
            her2_positive:                 #luminal_B_HER2_positive,
            ---------------------------------------------------------
            er_negative and
            pr_negative and
            her2_positive and
            ki67.in_range ([high]):        #HER2,
            ---------------------------------------------------------
            er_negative and
            pr_negative and
            her2_negative and
            ki67.in_range ([high]):        #triple_negative,
            ---------------------------------------------------------
            *:                             #none
            =========================================================
        ;

For the common degenerate case where there is a single condition, the standard form looks as follows:

    calculate_score: Integer
        Result := choice in
            ============
            expr1:    2,
            ------------
            *:        0
            ============
        ;

While perfectly understandable (and legal syntax), the following compact form may be used instead:

    calculate_score: Integer
        Result := expr1 ? 2 : 0

The above syntax is adopted from the C language family. It may be used to construct intelligible conditional arithmetic operations such as summing, e.g.:

    ipi_raw_score: Integer
        Result := Result.add (
            =============================================
            age > 60                             ? 1 : 0,
            staging ∈ {#stage_III, #stage_IV}    ? 1 : 0,
            ldh.in_range (#normal)               ? 1 : 0,
            ecog > 1                             ? 1 : 0,
            extranodal_sites > 1                 ? 1 : 0
            =============================================
        )
        ;

Case Table

The Case Table syntax form (case statement equivalent) is logically no different from the more general condition chain, except that every branch condition expression takes the form Expr ∈ Constri, where Expr is the same expression left-hand side for all branches, each having a variable right-hand side in the form of a value range constraint. Here the operator is read as 'is in', i.e. set-membership. The case table construct is designed to enable the value of a single determining expression to be tested against any number of value ranges. This is illustrated in the following example:

    gfr_range: Real

    risk_assessment: Real
        Result := case gfr_range in
            =================
            |>20|:      1,
            |10 - 20|:  0.75,
            |<10|:      0.5
            =================
        ;

This expression returns one of the values 1, 0.75 or 0.5, depending on the evaluated value of gfr_range, but it could equally return the value of a more complex expression, including further instances of Case tables, Condition chains, operator expressions etc.

Nested Case Table

The following shows the use of nested case tables to achieve the effect of a credit application test, from an example in the DMN specification.

    post_bureau_risk_category: Terminology_term
        Result := case existing_customer in
            ========================================
            True:   case
                    appl_risk_score
                    in
                    --------------------------------
                    |≤120|:     case
                                credit_score
                                in
                                --------------------
                                |<590|:      #HIGH,
                                |590..610|:  #MEDIUM,
                                |>610|:      #LOW
                                --------------------
                                ;,
                    |>120|:     case
                                credit_score
                                in
                                --------------------
                                |<600|:      #HIGH,
                                |600..625|:  #MEDIUM,
                                |>625|:      #LOW
                                --------------------
                                ;
                    --------------------------------
                    ;,
            False:  case
                    appl_risk_score
                    in
                    --------------------------------
                    |≤100|:     case
                                credit_score
                                in
                                --------------------
                                |<580|:      #HIGH,
                                |580..600|:  #MEDIUM,
                                |>600|:      #LOW
                                --------------------
                                ;,
                    |>100|:     case
                                credit_score
                                in
                                --------------------
                                |<590|:      #HIGH,
                                |590..615|:  #MEDIUM,
                                |>615|:      #LOW
                                --------------------
                                ;
                    --------------------------------
                    ;
            ========================================
            ;
        ;

Multi-dimensional Case Table (experimental)

The credit assessment example above can be recoded as a sparse table.

post_bureau_risk_category := multicase
    =======================================================================================
   {existing_customer,  appl_risk_score,        credit_score} in
    ---------------------------------------------------------------------------------------
    True:               |≤120|:                 |<590|:         #HIGH,
                                                |590..610|:     #MEDIUM,
                                                |>610|:         #LOW;
                        -------------------------------------------------------------------
                        |>120|:                 |<600|:         #HIGH,
                                                |600..625|:     #MEDIUM,
                                                |>625|:         #LOW;
                        ,
   ----------------------------------------------------------------------------------------
   False:               |≤100|:                 |<580|:         #HIGH,
                                                |580..600|:     #MEDIUM,
                                                |>600|:         #LOW;
                        -------------------------------------------------------------------
                        |>100|:                 |<590|:         #HIGH,
                                                |590..615|:     #MEDIUM,
                                                |>615|:         #LOW;
                        ;
    =======================================================================================
    ;

Two-dimensional Tables (experimental)

Two-dimensional decision tables are common in all sectors. Although they can be reduced to a condition chain, EL provides a more direct syntax that enables them to be expressed in a form visually very close to their logical form.

item in
    ==========================================================================
                   {    isEconomy(p),   isBusiness(p),      isFirstClass(p) },
    --------------------------------------------------------------------------
    isChild(p):    {    50,             250,                1000            },
    --------------------------------------------------------------------------
    isAdult(p):    {    250 + trip.d,   450 + trip.d,       750 + trip.d    },
    --------------------------------------------------------------------------
    isMilitary(p): {    90,             250,                750 - 2 * p.age }
    ==========================================================================
;