Skip to content
PYD ENTERPRISES
DMN & rules8 min read

DMN 1.5 for mainframe rating engines: from EVALUATE to decision tables

A property and casualty rating program is a decision model that was never drawn. DMN gives it a shape actuaries can read, provided the extraction respects evaluation order.

Venkatesh IyerPrincipal Modernization Architect, PYD ENTERPRISES
On this page · 7 sections
  1. 01A rating engine is already a decision model
  2. 02From EVALUATE TRUE to a decision table
  3. 03Choosing the hit policy
  4. 04The integer age assumption
  5. 05Composing the decision requirements graph
  6. 06When the table lives in a VSAM file
  7. 07Review with the people who own the rates

A rating engine is already a decision model

Open any personal auto or homeowners rating program written in the 1980s and you find the same structure. A base rate is looked up by territory and coverage. A sequence of factors is applied: driver age band, vehicle symbol, prior claims, multi-policy discount, deductible credit. Each factor is chosen by a nest of IF statements or an EVALUATE TRUE block, and the final premium is a chain of COMPUTE ... ROUNDED statements.

That is a decision requirements graph with decision tables at the leaves and a formula at the root. DMN, the OMG Decision Model and Notation standard, describes exactly that structure, and version 1.5 is supported by mainstream engines including Camunda and Drools. The work is not inventing a model. It is recovering the one the code already implements, without changing its semantics.

From EVALUATE TRUE to a decision table

Here is a driver age factor routine, trimmed from a rating program we reviewed. It looks like a simple lookup.

The important detail is that EVALUATE stops at the first WHEN that is true. The conditions here happen not to overlap, but many real routines rely on order: a broad condition placed after a narrow one acts as a default for the remaining cases. A faithful extraction has to preserve that ordering, or prove that it does not matter.

RTAGEF01.cbl (procedure excerpt)COBOL
1 2100-DRIVER-AGE-FACTOR.
2 EVALUATE TRUE
3 WHEN DRV-AGE < 21
4 MOVE 1.850 TO WS-AGE-FACTOR
5 WHEN DRV-AGE < 25 AND DRV-GOOD-STUDENT
6 MOVE 1.320 TO WS-AGE-FACTOR
7 WHEN DRV-AGE < 25
8 MOVE 1.450 TO WS-AGE-FACTOR
9 WHEN DRV-AGE > 69
10 MOVE 1.120 TO WS-AGE-FACTOR
11 WHEN OTHER
12 MOVE 1.000 TO WS-AGE-FACTOR
13 END-EVALUATE.

Choosing the hit policy

DMN decision tables declare a hit policy that defines what happens when more than one rule matches. The two that matter most for procedural code are UNIQUE and FIRST.

  • UNIQUE (U): at most one rule may match any input. This is the easiest table to review, because each row stands alone.
  • FIRST (F): rules are evaluated in order and the first match wins. This mirrors EVALUATE and IF ... ELSE IF chains exactly, but readers must read rows in order.

In the routine above, rows two and three overlap: a 22 year old good student matches both DRV-AGE < 25 AND DRV-GOOD-STUDENT and DRV-AGE < 25. Under FIRST the good student factor wins, as in the COBOL. We prefer to emit UNIQUE tables, so the extractor rewrites the third condition as DRV-AGE < 25 AND NOT DRV-GOOD-STUDENT when it can prove the rewrite is equivalent, and falls back to FIRST when it cannot. The WHEN OTHER branch becomes a final row with all inputs set to -, which only works as a default under FIRST, or under UNIQUE if the other rows' complement is spelled out.

Range inputs use FEEL interval syntax. DRV-AGE < 21 becomes < 21, and [21..25) would express the band explicitly if the extractor rewrites to disjoint ranges.

driver-age-factor.dmn (DMN 1.5)XML
1<definitions xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
2 id="rating" name="AutoRating" namespace="https://pydenterprises.com/dmn/rating">
3 <decision id="DriverAgeFactor" name="Driver age factor">
4 <decisionTable id="dt_age" hitPolicy="UNIQUE">
5 <input id="in_age" label="Driver age">
6 <inputExpression typeRef="number"><text>driverAge</text></inputExpression>
7 </input>
8 <input id="in_gs" label="Good student">
9 <inputExpression typeRef="boolean"><text>goodStudent</text></inputExpression>
10 </input>
11 <output id="out_f" name="ageFactor" typeRef="number"/>
12 <rule id="R1"><inputEntry><text>&lt; 21</text></inputEntry><inputEntry><text>-</text></inputEntry><outputEntry><text>1.850</text></outputEntry></rule>
13 <rule id="R2"><inputEntry><text>[21..25)</text></inputEntry><inputEntry><text>true</text></inputEntry><outputEntry><text>1.320</text></outputEntry></rule>
14 <rule id="R3"><inputEntry><text>[21..25)</text></inputEntry><inputEntry><text>false</text></inputEntry><outputEntry><text>1.450</text></outputEntry></rule>
15 <rule id="R4"><inputEntry><text>[25..69]</text></inputEntry><inputEntry><text>-</text></inputEntry><outputEntry><text>1.000</text></outputEntry></rule>
16 <rule id="R5"><inputEntry><text>&gt; 69</text></inputEntry><inputEntry><text>-</text></inputEntry><outputEntry><text>1.120</text></outputEntry></rule>
17 </decisionTable>
18 </decision>
19</definitions>

The integer age assumption

The UNIQUE table above is only equivalent if driver age is an integer. DRV-AGE is declared PIC 9(03), so it is, and the extractor records that justification next to the rewrite. If the field had been PIC 9(03)V9, the band [25..69] would leave a gap between 69 and 69.9 that the COBOL covered through WHEN OTHER. These are the details that make generic text-to-table conversion dangerous: the semantics live in the data division as much as in the procedure division.

Composing the decision requirements graph

Individual factor tables are not the model. The premium formula ties them together, and in rating code it is usually a long chain of COMPUTE statements with ROUNDED at specific steps. The extractor builds a decision requirements diagram where each factor table is a required decision of the premium decision, and the premium itself is a literal FEEL expression.

Rounding placement is preserved exactly. If the COBOL rounds after applying the territory factor and again after the final discount, the FEEL expression rounds at those two points with round half up(x, 2), not decimal(x, 2), which rounds half to even, and the equivalence test suite includes inputs chosen to land on half-cent boundaries. Rounding once at the end gives a different premium on a small but real share of policies, and that difference is the kind a regulator rate filing will catch.

When the table lives in a VSAM file

Many carriers moved factor values out of code and into VSAM KSDS files or DB2 tables years ago, keyed by state, line and effective date. In that case the decision table structure is in code, while the outputs are data. The extractor emits the table with input columns from the key structure and marks the output column as externally sourced, with lineage to the file and record layout. Loading the current factor values is then a data migration task, versioned by effective date, rather than a rule extraction task.

Review with the people who own the rates

Actuaries and product managers can read a DMN table. They cannot read 2100-DRIVER-AGE-FACTOR. The sign-off workflow shows each row beside the COBOL lines it came from, and every rewrite the extractor made, such as the UNIQUE conversion above, is listed with its proof obligation. Reviewers approve, correct or reject; nothing moves into the target rules repository unsigned.

For the full treatment of hit policies, FEEL and engine validation, see the DMN specification guide. The insurance carriers page covers how rating engine discovery fits into a policy administration replacement.

Keep reading

All posts

Legacy anti-patterns catalog

25 legacy anti-patterns, mapped to modern designs.

From COBOL ALTER and GO TO DEPENDING ON to EJB entity beans and PL/SQL cursors: why each is dangerous during migration, how it is detected, and the before and after code.

Explore the catalog

See your own code as a rule inventory.

Run PYD ENTERPRISES on up to 100,000 lines of COBOL, Java or .NET in your private VPC and review the extracted rules beside their source lines.