An Evaluation Table is a rate chain object that maps input values to output variables using a spreadsheet-like decision table — without writing free-form evaluation code.
Use Evaluation Tables for the classic "look up the right number based on a few inputs" pattern that lives in every ISO manual: deductible factors, territory factors, age/credit-band factors, ISO L/M symbol factors, package-credit grids, and the like.
Gated behind feature flag
toggle-feature-enable-evaluation-tables(section:lines, default: off).
When to use an Evaluation Table
Reach for an Evaluation Table any time you have a finite, table-shaped lookup that today lives inside an Evaluation rate-chain object as expression code. Typical signals:
- The logic looks like a series of
if/elif/elsebranches that compare a single input against a finite list of values. - The values come straight from an ISO manual, a state filing, or an actuarial worksheet that is already a table.
- The configuration team has copy-pasted near-identical Evaluation objects across LEDs, policy types, or states to handle small numeric variations.
- Underwriting or actuarial cannot audit the factors today because they live in code.
Keep using a regular Evaluation when the logic is genuinely procedural — multi-step calculations, complex conditionals on derived values, accumulator loops. Evaluation Tables replace lookups, not computation.
Concepts
An Evaluation Table has three parts: inputs, outputs, and rules.
Inputs — the columns on the left. Each input has:
| Property | Notes |
|---|---|
| Name | Human-readable column label shown in the grid header (blue). |
| Type | String or Number. Defaults to String for backward compatibility. |
| Expression | A rate-chain reference (same syntax as a regular Evaluation's inputs) — e.g. deductible, territory, c.pp.items[name='X'].limit. The expression resolves to the actual value at rating time. |
Outputs — the columns on the right (purple headers). Each output has:
| Property | Notes |
|---|---|
| Name | Column label. |
| Variable name | The name under which the matched value becomes available to subsequent rate chain objects in the same rate chain. |
Numeric outputs are stored as Decimal. Anything non-numeric is stored as a string.
Rules — the rows of the grid. Each row is a single rule. The system walks rules top-to-bottom and the first matching rule wins.
Matching behavior
String inputs
String cells display a static = pill. Matching is exact equality — case-sensitive, no whitespace tolerance. Use this for territories, package codes, classification codes, etc.
Number inputs
Number cells display a clickable operator pill. Click the pill to choose: =, <, >, <=, >=.
Numeric comparison uses Decimal so all of the following match a 500 cell with =:
500(int)"500"(string)Decimal("500.00")
This is the key behavior — you do not have to coerce types in your input expression.
Wildcards
A blank/null input cell is a wildcard and matches any value. Combined with first-match-wins, this is how you write "this rule applies if the value is X, otherwise this rule applies" — put the specific rules first, the wildcard rule last.
Default / catch-all
A row with all blank inputs is the default. It must be placed last. Use it as your safety net so policies never fail to rate.
No match → clear error
If no rule matches, the rating engine raises an error showing the input values that failed to match. Add a default row to prevent this.
How to add an Evaluation Table
- Open Lines → choose the policy type and LED → navigate to the line item whose rate chain you want to extend.
- In the rate chain, click Add Rate Chain Object → select Evaluation Table. (Not in the dropdown? The feature flag is off — see Availability above.)
- Define inputs. Add a row per input you want to match on. Set Name, Type, Expression.
- Define outputs. Add a row per variable you want the table to produce. Set Name and Variable name.
- Fill the grid. Add rules top-to-bottom. Most specific rules first; default catch-all last.
- Save. The table participates in rating from the next quote forward.
Tip — copy/paste from Excel. Select cells in Excel and paste directly into the rules grid. Values come through. Operator pills default to
=. You can adjust operators per cell afterward.
Tip — zero values. A
0(zero) numeric value displays correctly with its operator pill — it is not treated as empty/wildcard. Completely empty rows are stripped on save, so a deliberate zero is safe.
Examples
Example 1 — deductible factor (single input)
| deductible (Number) | factor (Number) |
|---|---|
>= 1000 | 1.10 |
>= 500 | 1.00 |
| (blank — default) | 0.90 |
- Input:
deductible(Number), expressiondeductible. - Output:
factor(variable name:deductible_factor). - Behavior: a $1,500 deductible matches row 1 → factor
1.10. A $250 deductible falls through to row 3 (the default) → factor0.90. - Use downstream: the next rate-chain step references
deductible_factorlike any other custom variable.
Example 2 — territory factor (single string input)
| territory (String) | factor (Number) |
|---|---|
= Coastal | 1.45 |
= Tier 1 | 1.20 |
= Tier 2 | 1.05 |
= Standard | 1.00 |
| (blank — default) | 1.00 |
Use this when the territory string comes from a classification step earlier in the rate chain.
Example 3 — ISO symbol grid (two inputs, two outputs)
Matching ISO L (liability) and ISO M (medical payments) symbol factors in one step using the new symbol fields added to the Vehicle model in BC-21967.
| iso_l_symbol (Number) | iso_m_symbol (Number) | l_factor (Number) | m_factor (Number) |
|---|---|---|---|
= 10 | = 10 | 0.85 | 0.90 |
= 10 | = 12 | 0.85 | 1.00 |
= 12 | = 10 | 1.00 | 0.90 |
= 12 | = 12 | 1.00 | 1.00 |
| (blank) | (blank) | 1.15 | 1.15 |
A single table replaces what would otherwise be a nested conditional in Evaluation code. Both l_factor and m_factor become custom variables for the rest of the rate chain to use.
Example 4 — age band with wildcards
| age (Number) | credit_band (String) | factor (Number) |
|---|---|---|
< 25 | (blank) | 1.40 |
>= 65 | (blank) | 1.15 |
| (blank) | = Excellent | 0.85 |
| (blank) | = Good | 0.95 |
| (blank) | (blank) | 1.00 |
This shows the wildcard pattern. Under-25 drivers get the youth surcharge regardless of credit. 65+ drivers get the senior surcharge regardless of credit. Standard-age drivers fall through to the credit-band rows. Anyone with no credit info hits the default.
Using outputs in subsequent rate chain objects
Output variable names defined on an Evaluation Table are available to every rate-chain object that follows it in the chain. They behave identically to custom variables produced by a regular Evaluation. Reference them by name in the next step's expression, e.g. premium = base_rate * deductible_factor * territory_factor.
If you reference the variable in an object that runs before the Evaluation Table in the rate chain, you will get an unresolved-variable error. Order matters.
API round-trip
The full table configuration (inputs, outputs, rules with operators and types) is serialized in the policy type rate-chain payload:
POST /api/v2/policy-types—create_policy_typesaves Evaluation Tables alongside other rate chain objects.GET /api/v2/policy-types/{id}—get_policy_typereturns the same shape.- Rate-chain references in input expressions are handled by
eval_translator(same code path as a regular Evaluation).
If you are programmatically authoring rate chains, an Evaluation Table object includes a type: "Evaluation Table" discriminator alongside its inputs, outputs, and rules arrays.
Engineering reference (for advanced configurators / BCAs)
| Concern | Where it lives |
|---|---|
| Rating engine entrypoint | get_rate_chain_premium → _process_evaluation_table |
| Service / evaluation logic | EvaluationTableService.evaluate(), EvaluationTableService._values_match() |
| Rate-chain reference resolution | eval_translator |
| Rating details persistence | One entry per Evaluation Table, type: "Evaluation Table", outputs preserved in the outputs dict so populate_custom_variables_for_rated_item can reconstruct them on already-rated items |
| Feature flag postfix script | 595_add_evaluation_table_feature_flag.py |
Troubleshooting
The "Evaluation Table" option does not appear in the rate chain type dropdown. The feature flag toggle-feature-enable-evaluation-tables (section: lines) is off. Existing Evaluation Tables in saved configs remain editable and continue to rate even with the flag off — the flag only gates the UI for creating new ones.
Rating fails with "no rule matched" and lists the input values. Add a default row (all inputs blank) at the bottom of the table, or add a specific rule covering the value combination that failed.
A 0 or 0.00 is being treated as a wildcard. It should not — zeros are preserved. If you are seeing this, make sure the cell shows its operator pill (e.g. [=] 0). If the cell appears truly empty, re-enter the value; completely empty rows are stripped on save.
My pasted Excel values brought in operator text like = or >= as part of the value. The HandsOnTable grid pastes values only; the operator pill is stored separately and defaults to =. If you see = inside a numeric cell, retype the cell — only the numeric portion belongs there.
My input expression doesn't resolve. Input expressions use the same syntax as a regular Evaluation rate-chain object — they go through eval_translator. If a regular Evaluation can't read the same expression, an Evaluation Table can't either. Test the expression in a regular Evaluation first.
An already-rated policy lost its custom variables. The Evaluation Table outputs are persisted in the rating details outputs dict on commit, and populate_custom_variables_for_rated_item rehydrates them. If they are missing, the policy was rated before the Evaluation Table was added — re-rate the policy.