> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Check messages: calculations that can fail

> A calculation gives a value for every request the rater accepts. These messages name a request that leaves a calculation short of a value.

*Generated from the `rslc` compiler.*

## E-CYCLE

**A calculation must avoid reading its own value.**

A rater computes each calculation once, after the calculations it reads. The check follows every read, across records, lists and map elements. It reports any chain of reads that returns to where it started.

A calculation inside such a loop has an undefined value, so every rating would fail. The message prints the loop as a path, such as `Policy.fee -> Policy.premium -> Policy.fee`.

**Example**

The fee is a share of the premium, and the premium includes the fee.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            fee: Premium [cite: manual:p4] = round(self.premium * fee_share, 2, half_up),
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 + self.fee, 2, half_up)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-CYCLE bad/rater.rsl:27:13: attribute dependency cycle: Policy.fee -> Policy.premium -> Policy.fee; every rating would fail at runtime. Compute the shared value in its own attribute so that no attribute needs itself
```

**Fix**

Compute the shared part in its own calculation, and let both read it. Here `base` holds the premium before the fee.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            base: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
            fee: Premium [cite: manual:p4] = round(self.base * fee_share, 2, half_up),
            premium: Premium [cite: manual:p4] = round(self.base + self.fee, 2, half_up)
        }
    }
```

**Other causes**

* A calculation reads its own value, on the same record or on another record of its list. The message reads ``attribute `Policy.premium` reads its own value``.
* One element of a map reads another element of the same map.
* The `when` condition of a follow-up input reads that same input. Write the condition from inputs that are asked earlier.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     fee_share [cite: manual:p4] = 0.05
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             fee: Premium [cite: manual:p4] = round(self.premium * fee_share, 2, half_up),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 + self.fee, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     fee_share [cite: manual:p4] = 0.05
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             fee: Premium [cite: manual:p4] = round(self.base * fee_share, 2, half_up),
>             premium: Premium [cite: manual:p4] = round(self.base + self.fee, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-OPTION-MATCH

**An optional value needs both match arms.**

An `Option` input is one a request may leave out. A calculation reads it with `match`, which has a `Some(…)` arm for a given value and a `None` arm for an absent one. The check asks for both arms.

Both cases happen in real requests. The `None` arm makes you decide what the filing says for a missing answer, and the `Some(…)` arm puts the given value to use.

**Example**

The prior premium is optional, and the `match` covers only a request that gives it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            renewal_floor: Num = match self.prior_premium { Some(prior) => prior * renewal_floor_share },
            premium: Premium [cite: manual:p3] =
                round(max(self.payroll / 1000 * rate_per_1000, self.renewal_floor), 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-OPTION-MATCH bad/rater.rsl:28:34: match on Option must cover both None and Some
```

**Fix**

Add a `None` arm with the value the filing gives when the answer is absent. A `_` arm also covers the missing case.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            renewal_floor: Num =
                match self.prior_premium { Some(prior) => prior * renewal_floor_share, None => 0 },
            premium: Premium [cite: manual:p3] =
                round(max(self.payroll / 1000 * rate_per_1000, self.renewal_floor), 2, half_up)
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     renewal_floor_share [cite: manual:p4] = 0.9
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             prior_premium: Option(Num) {precision: 0.01} where self in [0, 10000000]
>         },
>         step rating {
>             renewal_floor: Num = match self.prior_premium { Some(prior) => prior * renewal_floor_share },
>             premium: Premium [cite: manual:p3] =
>                 round(max(self.payroll / 1000 * rate_per_1000, self.renewal_floor), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     renewal_floor_share [cite: manual:p4] = 0.9
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             prior_premium: Option(Num) {precision: 0.01} where self in [0, 10000000]
>         },
>         step rating {
>             renewal_floor: Num =
>                 match self.prior_premium { Some(prior) => prior * renewal_floor_share, None => 0 },
>             premium: Premium [cite: manual:p3] =
>                 round(max(self.payroll / 1000 * rate_per_1000, self.renewal_floor), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-MATCH

**A match needs an arm for every value.**

A `match` picks one arm by the value of its subject. The check asks for an arm for each value the subject can hold. For an enum, that means each member the subject can be. For a `Bool`, that means `true` and `false`.

A value that lacks an arm would leave the calculation undefined for a request the rater accepts. The message names the missing members, so you learn which filed case to add.

The check counts only the values that reach the `match`. An input narrowed with `where self in {…}` needs arms for the narrowed members only.

**Example**

`Hazard` has three members, and the `match` gives a factor for two of them.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            hazard_factor: Num = match self.hazard { @Hazard.low => low_factor, @Hazard.medium => 1 },
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-MATCH bad/rater.rsl:37:34: match on enum `Hazard` is missing: high
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Add an arm for `high`. Where the filing gives one value to every remaining member, end the `match` with a `_` arm.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            hazard_factor: Num =
                match self.hazard { @Hazard.low => low_factor, @Hazard.medium => 1, @Hazard.high => high_factor },
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
```

**Other causes**

* A `match` on a `Bool` has one arm. The message reads `match on Bool must cover both true and false`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Hazard = {
>         low,
>         medium,
>         high
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     low_factor [cite: manual:p4] = 0.9,
>     high_factor [cite: manual:p4] = 1.25
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard: Hazard
>         },
>         step rating {
>             hazard_factor: Num = match self.hazard { @Hazard.low => low_factor, @Hazard.medium => 1 },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Hazard = {
>         low,
>         medium,
>         high
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     low_factor [cite: manual:p4] = 0.9,
>     high_factor [cite: manual:p4] = 1.25
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard: Hazard
>         },
>         step rating {
>             hazard_factor: Num =
>                 match self.hazard { @Hazard.low => low_factor, @Hazard.medium => 1, @Hazard.high => high_factor },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-TOPN-LIMIT

**A topn limit must be a whole count.**

`topn(list as x, score, limit)` keeps the `limit` elements with the highest score. A count of elements is a whole number of 0 or more. The check works out the range of the limit from the inputs it reads. Every value in that range must be such a count.

A negative or fractional limit would stop a rating. The message prints the limit and the range the compiler worked out for it.

**Example**

The rater charges every location except the smallest. With an empty list of locations the limit is -1.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            rated: List(Location) = topn(self.locations as l, l.payroll, len(self.locations) - 1),
            premium: Premium [cite: manual:p3] =
                round(sum(self.rated as l, l.payroll) / 1000 * rate_per_1000, 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-TOPN-LIMIT bad/rater.rsl:26:74: the `topn` limit `len(self.locations) - 1` has range -1..9; it must be an `Int in 0..`: clamp it with max(…, 0), keep it whole, or narrow the inputs it reads with a refinement
cost premium: 2 + 2*|Policy.locations| (each list of size 1: 4, 3: 8, 10: 22)
```

**Fix**

Clamp the limit with `max(…, 0)`. Where the filing asks for at least one location, narrow the list in its place: `where len(self) in [1, 10]`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            rated: List(Location) = topn(self.locations as l, l.payroll, max(len(self.locations) - 1, 0)),
            premium: Premium [cite: manual:p3] =
                round(sum(self.rated as l, l.payroll) / 1000 * rate_per_1000, 2, half_up)
```

**Other causes**

* The limit is a calculation that can give a fraction, such as `len(self.locations) / 2`. Make it whole with `floor(…)`.
* The limit is a literal such as `-1` or `2.5`. The message reads `topn limit must be a nonnegative whole number`.
* The call has two arguments and leaves the limit out. The message reads `topn requires a limit`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) keyed by number where len(self) in [0, 10]
>         },
>         step rating {
>             rated: List(Location) = topn(self.locations as l, l.payroll, len(self.locations) - 1),
>             premium: Premium [cite: manual:p3] =
>                 round(sum(self.rated as l, l.payroll) / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) keyed by number where len(self) in [0, 10]
>         },
>         step rating {
>             rated: List(Location) = topn(self.locations as l, l.payroll, max(len(self.locations) - 1, 0)),
>             premium: Premium [cite: manual:p3] =
>                 round(sum(self.rated as l, l.payroll) / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-LOOKUP-GAP

**A lookup needs a row for every key.**

`lookup!` reads the one table row that matches its keys. The check works out which key values each lookup can receive, from the types and ranges of the inputs behind them. Every such combination needs a row in the table.

A combination that lacks a row would stop the rating of a request the rater accepts. The message names the smallest missing combination and the ways to close the gap.

Each lookup is checked on its own. A lookup behind a narrowed input or inside a `match` arm needs rows only for the keys that reach it.

**Example**

`Hazard` has three members, and the table files a factor for `low` and `medium` only.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            hazard: Hazard
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-LOOKUP-GAP bad/rater.rsl:42:37: this lookup of `HazardFactor` can reach (hazard: high), but `HazardFactor` has no row for it. A lookup's key type must exclude every absent row: narrow the `hazard` key so it cannot be `high`: type the input it reads `Hazard where self != @Hazard.high`, or handle `high` in its own `match` arm that does not look up `HazardFactor`; or key the lookup by an identity enum whose mapping table lists only filed tuples (for example `VehicleIdentity` filled by `VehicleIdentityMap`); or add the filed row to `HazardFactor`; or when the filing has an all-other row, add `otherwise [cite: source:region]: { … }`
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Decide what the filing says about `high`. Here the carrier declines it, so the input excludes it with `where self != @Hazard.high`.

Where the filing prints a factor for `high`, add that row to the CSV. Where the filing has an all-other row, add `otherwise [cite: manual:p4]: { factor: … }` to the table.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            hazard: Hazard where self != @Hazard.high
        },
        step rating {
```

**Other causes**

* A row has the cell `unfiled`, which marks a cell the filing leaves blank. The lookup can still reach that row.
* A table with two or more keys lacks one combination, while each key value has rows elsewhere. Add the row or an `otherwise` row.
* A key is a `Code`, which accepts any text that fits its pattern. Type the input `Key(Table)` to accept the filed codes only, or add an `otherwise` row.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Hazard = {
>         low,
>         medium,
>         high
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     HazardFactor [cite: manual:p4] :
>         table dims (hazard: Hazard) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard: Hazard
>         },
>         step rating {
>             hazard_factor: Factor = lookup!(HazardFactor, self.hazard).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/HazardFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> hazard,factor
> low,0.9
> medium,1
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Hazard = {
>         low,
>         medium,
>         high
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     HazardFactor [cite: manual:p4] :
>         table dims (hazard: Hazard) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             hazard: Hazard where self != @Hazard.high
>         },
>         step rating {
>             hazard_factor: Factor = lookup!(HazardFactor, self.hazard).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.hazard_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/HazardFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> hazard,factor
> low,0.9
> medium,1
> ```

## E-ROOT

**A root needs a valid value and degree.**

`root(x, n, scale, mode)` gives the `n`th root of `x`, rounded to `scale` decimal places with the stated mode. A square root, or any even root, exists only for a value of 0 or more. The check works out the range of `x` from the inputs it reads and reports an even root whose value can go below 0.

The check also covers the other arguments. The degree is a whole number of 1 or more. The scale is a whole-number literal, and the mode is one of the rounding modes.

A root that passes gives a value for every request the rater accepts.

**Example**

The charge is the square root of payroll above 1,000,000. For a smaller payroll the value under the root is negative.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            large_account_charge: Num [cite: manual:p4] = root(self.payroll - large_account, 2, 2, half_up),
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 + self.large_account_charge, 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-ROOT bad/rater.rsl:27:64: `root` of `self.payroll - large_account` with a degree that can be even needs a radicand >= 0, but it has range -1000000..99000000; clamp it with `max(self.payroll - large_account, 0)` or reject negative values
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Clamp the value with `max(…, 0)`, so a smaller payroll gets a charge of 0. Where the charge applies to large accounts only, narrow the input in its place: `where self in [1000000, 100000000]`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            large_account_charge: Num [cite: manual:p4] =
                root(max(self.payroll - large_account, 0), 2, 2, half_up),
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 + self.large_account_charge, 2, half_up)
```

**Other causes**

* The call has fewer or more than four arguments. Write all four, as in `root(x, 2, 2, half_up)`.
* The degree is a literal below 1 or a fraction, such as `0` or `2.5`.
* The degree is an input or a calculation whose range includes a fraction or a value below 1.
* The scale is a fraction or a calculation. Write a whole-number literal: 2 for cents, 0 for whole dollars.
* The mode is a name outside `half_up`, `half_down`, `half_even`, `up`, `down`, `ceiling` and `floor`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     large_account [cite: manual:p4] = 1000000
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             large_account_charge: Num [cite: manual:p4] = root(self.payroll - large_account, 2, 2, half_up),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 + self.large_account_charge, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     large_account [cite: manual:p4] = 1000000
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             large_account_charge: Num [cite: manual:p4] =
>                 root(max(self.payroll - large_account, 0), 2, 2, half_up),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 + self.large_account_charge, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-POW-EXPONENT

**A power needs an exponent it computes exactly.**

RSL calculates with exact numbers, so two systems that run the same rater agree to the last digit. `pow(x, n)` with a whole exponent is exact. A fractional power such as `pow(x, 0.85)` has endless decimal places, so it needs a rounding.

The four-argument form states that rounding: `pow(x, e, scale, mode)` rounds the power to `scale` decimal places with the stated mode. The check asks every `pow` to be one of these two forms and to give a value for every request.

**Example**

The exponent 0.85 is fractional, and the call lacks a scale and a mode.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            exposure: Num [cite: manual:p4] = pow(self.payroll / 1000, 0.85),
            premium: Premium [cite: manual:p3] = round(self.exposure * rate_per_1000, 2, half_up)
        }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-POW-EXPONENT bad/rater.rsl:26:72: a fractional power is not an exact number: write pow(x, e, scale, mode) with the scale and mode the filing states, or root(x, n, scale, mode) for an nth root
```

**Fix**

Add the scale and the rounding mode that the filing states for the power.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            exposure: Num [cite: manual:p4] = pow(self.payroll / 1000, 0.85, 4, half_up),
            premium: Premium [cite: manual:p3] = round(self.exposure * rate_per_1000, 2, half_up)
        }
```

**Other causes**

* A whole exponent comes with a scale and a mode, as in `pow(x, 2, 4, half_up)`. The power is exact: write `round(pow(x, 2), 4, half_up)`.
* The call has three arguments. Write two for a whole exponent and four for a fractional one.
* The exponent of the four-argument form is an input or a calculation. Write a literal such as `0.85`.
* The exponent is a fraction whose lowest terms pass 1000, such as `0.8585`, which is 1717/2000. Keep the numerator and the denominator at 1000 or below.
* The exponent of the two-argument form is an input that accepts fractions. Type it with `precision: int`.
* The exponent can be negative while the base can be 0. Narrow the base above 0, or guard it with `if`.
* A large whole exponent can push the result past the size limit of an exact number. Round the base first, or narrow its range.
* The scale or the mode is malformed. The scale is a whole-number literal, and the mode is a rounding mode such as `half_up`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             exposure: Num [cite: manual:p4] = pow(self.payroll / 1000, 0.85),
>             premium: Premium [cite: manual:p3] = round(self.exposure * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             exposure: Num [cite: manual:p4] = pow(self.payroll / 1000, 0.85, 4, half_up),
>             premium: Premium [cite: manual:p3] = round(self.exposure * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-KEY-RANGE

**A lookup key must stay inside the bands.**

A table keyed by bands has a row for each band. The bands cover a stretch of the number line, such as 1 and up. The check works out the range of each lookup key from the inputs it reads. That range must fit inside the stretch the bands cover.

A key outside the bands matches zero rows, so the rating of that request would stop. The message prints the range of the key and the stretch the bands cover.

**Example**

The bands of `SizeBand` start at 1, and the input `employees` accepts 0.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            employees: Num {precision: int} where self in [0, 5000]
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-KEY-RANGE bad/rater.rsl:38:55: key `self.employees` has range 0..5000, but `SizeBand` (dim `size` of `SizeFactor`) covers 1..; values below 1 have no row: write `max(self.employees, 1)` or add an open bottom `..<1`; or narrow the inputs it reads with a refinement, or, when the filing has an all-other row, add `otherwise [cite: source:region]: { … }` to the table
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Start the range of `employees` at 1.

Where the filing rates the values below the first band, say how. Clamp the key with `max(self.employees, 1)`, open the bottom band with `..<1`, or add an `otherwise` row with its citation.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            employees: Num {precision: int} where self in [1, 5000]
        },
        step rating {
```

**Other causes**

* The key can pass the top of the last band. Open the last band, as in `250..`, or clamp the key with `min`.
* The key is a calculation, such as `self.employees - 1`. Cover its whole range with the bands, or narrow the inputs it reads.
* The table is keyed by a list of values, such as `IntValues(1, 10, 50)`, and the key is a plain number. Type the input with the values type. This cause comes with `E-LOOKUP-GAP`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     SizeBand = IntBands(1, 10, 50, 250..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (size: SizeBand) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [0, 5000]
>         },
>         step rating {
>             size_factor: Factor = lookup!(SizeFactor, self.employees).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> size,factor
> 1,1.2
> 10,1.1
> 50,1
> 250,0.9
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     SizeBand = IntBands(1, 10, 50, 250..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (size: SizeBand) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [1, 5000]
>         },
>         step rating {
>             size_factor: Factor = lookup!(SizeFactor, self.employees).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> size,factor
> 1,1.2
> 10,1.1
> 50,1
> 250,0.9
> ```

## E-KEY-HOLE

**A calculated band key needs a filed rounding.**

Bands are filed at a precision. `IntBands(0, 30000, 60000..)` files whole numbers: one band ends at 29,999 and the next starts at 30,000. A calculated key can land between those two values, at 29,999.5.

The filing decides which band such a value belongs to, and the rater has to say so. The check asks every calculated key of a band table to sit on the precision of its bands. An input key already does, because its type states its precision.

**Example**

The average wage is a division, so it can fall between two whole numbers. `WageBand` files whole numbers.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            average_wage: Num = self.payroll / self.employees,
            wage_factor: Factor = lookup!(WageFactor, self.average_wage).factor,
            premium: Premium [cite: manual:p3] =
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-KEY-HOLE bad/rater.rsl:39:55: key `self.average_wage` is computed and has no decimal grid, but `WageBand` (dim `wage` of `WageFactor`) cuts the line at multiples of 1: a key between two filed values falls into a band by the band spelling alone, not by the filing. Declare what the filing says, with its citation: if it rounds the key, write the rounding in a cited step, `round(self.average_wage, 0, <filed mode>)`; if it defines the bands over the unrounded value (interpolation segments, "up to and including x", "x and over"), declare `WageBand = IntBands(key: unrounded; …)` and cite that rule on `WageFactor`, spelling each cut `x` or `>x` as the filing assigns a value exactly at the cut. A rejection is not a declaration: the key is computed, so it must be rated
cost premium: 4 (each list of size 1: 4, 3: 4, 10: 4)
```

**Fix**

Round the key the way the filing does, and cite the rule.

Where the filing defines the bands over the unrounded value, declare that on the type: `WageBand = IntBands(key: unrounded; 0, 30000, 60000..)`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            average_wage: Num [cite: manual:p4] = round(self.payroll / self.employees, 0, half_up),
            wage_factor: Factor = lookup!(WageFactor, self.average_wage).factor,
            premium: Premium [cite: manual:p3] =
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     WageBand = IntBands(0, 30000, 60000..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     WageFactor [cite: manual:p4] :
>         table dims (wage: WageBand) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [1, 5000]
>         },
>         step rating {
>             average_wage: Num = self.payroll / self.employees,
>             wage_factor: Factor = lookup!(WageFactor, self.average_wage).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.wage_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/WageFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> wage,factor
> 0,1.2
> 30000,1
> 60000,0.9
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     WageBand = IntBands(0, 30000, 60000..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     WageFactor [cite: manual:p4] :
>         table dims (wage: WageBand) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [1, 5000]
>         },
>         step rating {
>             average_wage: Num [cite: manual:p4] = round(self.payroll / self.employees, 0, half_up),
>             wage_factor: Factor = lookup!(WageFactor, self.average_wage).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.wage_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/WageFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> wage,factor
> 0,1.2
> 30000,1
> 60000,0.9
> ```

## E-INTERP-RANGE

**An interpolated key stays between the filed points.**

`interpolate!` reads a table whose last key lists the filed points. Between two points it draws a straight line from one filed value to the next. The check works out the range of the key and asks that it stays between the first point and the last.

Outside the filed points the table is silent: the filing may hold the end value flat, extend the line or decline the risk. The rater states which, so the choice is the filing's and a reviewer sees it.

**Example**

The filed points run from 100,000 to 5,000,000, and `payroll` accepts values from 0 to 100,000,000.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            size_factor: Factor [cite: manual:p4] =
                interpolate!(SizeFactor, self.payroll, ratio: (4, half_up), step: (4, half_up), result: (4, half_up)).factor,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-INTERP-RANGE bad/rater.rsl:38:42: `interpolate!` key `self.payroll` has range 0..100000000, but the filed points of `PayrollPoint` (dim `payroll` of `SizeFactor`) run from 100000 to 5000000; outside them the filing gives no line. Write the filed clamp or extrapolation around the read, such as `max(min(self.payroll, 5000000), 100000)` with its citation, or narrow the input with a refinement (`where self in [100000, 5000000]`)
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Write the rule the filing gives for values outside the points. Here the filing holds the end factors flat, so the key is clamped to the first and last points.

Where the carrier declines such risks, narrow the input: `where self in [100000, 5000000]`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            size_factor: Factor [cite: manual:p4] =
                interpolate!(SizeFactor, max(min(self.payroll, 5000000), 100000), ratio: (4, half_up), step: (4, half_up), result: (4, half_up)).factor,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     PayrollPoint = IntValues(100000, 500000, 1000000, 5000000)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (payroll: PayrollPoint) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             size_factor: Factor [cite: manual:p4] =
>                 interpolate!(SizeFactor, self.payroll, ratio: (4, half_up), step: (4, half_up), result: (4, half_up)).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> payroll,factor
> 100000,1.2
> 500000,1
> 1000000,0.9
> 5000000,0.8
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     PayrollPoint = IntValues(100000, 500000, 1000000, 5000000)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (payroll: PayrollPoint) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             size_factor: Factor [cite: manual:p4] =
>                 interpolate!(SizeFactor, max(min(self.payroll, 5000000), 100000), ratio: (4, half_up), step: (4, half_up), result: (4, half_up)).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> payroll,factor
> 100000,1.2
> 500000,1
> 1000000,0.9
> 5000000,0.8
> ```

## E-DIV-ZERO

**A divisor must exclude zero.**

A division by zero stops a rating. The check works out the range of every divisor from the ranges of the inputs it reads. It reports any divisor whose range includes 0.

A rater that passes divides safely for every input it accepts. You learn about the zero case when you write the rater, before a quote fails on it.

**Example**

The input `employees` accepts 0, and the premium divides payroll by it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            employees: Num {precision: int} where self in [0, 5000]
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-DIV-ZERO bad/rater.rsl:28:38: divisor `self.employees` has range 0..5000, which includes 0; guard the division (`if self.employees != 0 then … else …`), or narrow the inputs it reads with a refinement
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Start the range of `employees` at 1, so the divisor stays above zero. Where the filing rates a zero count, guard the division: `if self.employees != 0 then … else …`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            employees: Num {precision: int} where self in [1, 5000]
        },
        step rating {
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [0, 5000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / self.employees * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             employees: Num {precision: int} where self in [1, 5000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / self.employees * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-DATE-RANGE

**A shifted date must stay in the calendar.**

`add_days`, `add_months` and `add_years` move a date by a whole number of days, months or years. RSL dates run from 0001-01-01 to 9999-12-31. The check works out the earliest and latest value of the date being moved. The moved date has to stay inside the calendar for every one of them.

A `Date` input accepts any date in the calendar until you bound it. A shift back from the first year, or forward from the last, leaves the calendar, so that rating would stop.

The bound also documents the input. A policy effective date has a real earliest value, and stating it rejects a mistyped year before rating starts.

**Example**

The rater moves `effective` back three years, and the input accepts dates as early as 0001-01-01.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            effective: Date,
            founded: Date
        },
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-DATE-RANGE bad/rater.rsl:29:48: shifting `self.effective` can leave the supported dates (0001-01-01 to 9999-12-31); bound its earliest date, such as `where self in [1900-01-01, _]`
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Give the input an earliest date. An underscore leaves the other side open.

A forward shift, such as `add_years(self.effective, 1)`, needs a latest date in the same way: `where self in [_, 2100-12-31]`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            effective: Date where self in [2026-01-01, _],
            founded: Date
        },
```

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     new_venture_factor [cite: manual:p4] = 1.25
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             effective: Date,
>             founded: Date
>         },
>         step rating {
>             new_venture: Bool = self.founded > add_years(self.effective, -3),
>             venture_factor: Num = if self.new_venture then new_venture_factor else 1,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.venture_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     new_venture_factor [cite: manual:p4] = 1.25
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             effective: Date where self in [2026-01-01, _],
>             founded: Date
>         },
>         step rating {
>             new_venture: Bool = self.founded > add_years(self.effective, -3),
>             venture_factor: Num = if self.new_venture then new_venture_factor else 1,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.venture_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-EMPTY-FOLD

**Max, min and avg need one element.**

`max`, `min` and `avg` over a list give the largest, smallest or average value of its elements. An empty list has zero elements, so its largest value is undefined. `sum` and `count` are different: they give 0 for an empty list.

The check asks every list read by `max`, `min` or `avg` to hold at least one element. A request with an empty list then stops at the input, with a clear reason, before rating starts.

**Example**

The list of locations accepts zero elements, and `max` reads it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            locations: List(Location) where len(self) in [0, 10]
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-EMPTY-FOLD bad/rater.rsl:26:36: `max` over `self.locations` needs at least one element, but the collection can be empty; guard it (`if count(self.locations as x, true) > 0 then max(…) else …`) or refine the input list, `where len(self) in [1, _]`
cost premium: 2 + |Policy.locations| (each list of size 1: 3, 3: 5, 10: 12)
```

**Fix**

Ask for at least one location in the list's own range.

Where the filing rates a policy with an empty list, guard the read: `if len(self.locations) > 0 then max(…) else 0`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            locations: List(Location) where len(self) in [1, 10]
        },
        step rating {
```

**Other causes**

* `max`, `min`, `avg` or `sum` folds over a narrowed enum that keeps zero members. The message reads `` `NoPeril` has no members, so the fold has no operands ``.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) where len(self) in [0, 10]
>         },
>         step rating {
>             largest_payroll: Num = max(self.locations as l, l.payroll),
>             premium: Premium [cite: manual:p3] = round(self.largest_payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) where len(self) in [1, 10]
>         },
>         step rating {
>             largest_payroll: Num = max(self.locations as l, l.payroll),
>             premium: Premium [cite: manual:p3] = round(self.largest_payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-QUADRATIC

**Walk a list once, on its parent.**

A calculation on a list element runs once for each element. When that calculation also walks the whole list, a list of 10 costs 100 steps and a list of 1,000 costs a million. The check reports such a walk when its result is the same for every element.

The same total computed many times is wasted work, and it hides a value worth naming. Computed once on the parent record, it gets a name that every element reads.

A walk whose result differs by element passes the check. `rslc check` prints the cost of each outcome, and such a walk shows there as a squared term: `|Policy.locations|^2`.

**Example**

Each location compares its payroll with the policy total, and each one adds up every location to get that total.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(sum(self.locations as l, l.charge), 2, half_up)
        }
...
        },
        step rating {
            main_site: Bool = self.payroll * 2 > sum(Policy.locations as x, x.payroll),
            site_factor: Num = if self.main_site then main_site_factor else 1,
            charge: Premium [cite: manual:p3] =
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-QUADRATIC bad/rater.rsl:35:50: Location.main_site evaluates `sum(Policy.locations as x, x.payroll)` once per Location, and each evaluation walks every element of |Policy.locations|, so work grows with |Policy.locations|^2. The walk does not depend on the repeated element: compute it once on Policy, `main_site_sum = sum(self.locations as x, x.payroll)`, and read it in its place as `Policy.main_site_sum`.
cost premium: 1 + 4*|Policy.locations| + |Policy.locations|^2 (each list of size 1: 6, 3: 22, 10: 141)
```

**Fix**

Compute the total once on `Policy`, and read it from each location as `Policy.total_payroll`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            total_payroll: Num = sum(self.locations as x, x.payroll),
            premium: Premium [cite: manual:p3] = round(sum(self.locations as l, l.charge), 2, half_up)
        }
...
        },
        step rating {
            main_site: Bool = self.payroll * 2 > Policy.total_payroll,
            site_factor: Num = if self.main_site then main_site_factor else 1,
            charge: Premium [cite: manual:p3] =
```

**Other causes**

* A fold inside another fold over the same list repeats a walk, as in `count(self.locations as l, l.payroll * 2 > sum(self.locations as x, x.payroll))`. Give the inner fold its own calculation and read that.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     main_site_factor [cite: manual:p4] = 0.95
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) where len(self) in [1, 10]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(sum(self.locations as l, l.charge), 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             main_site: Bool = self.payroll * 2 > sum(Policy.locations as x, x.payroll),
>             site_factor: Num = if self.main_site then main_site_factor else 1,
>             charge: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.site_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     main_site_factor [cite: manual:p4] = 0.95
> }
>
> Records = {
>     Policy = {
>         inputs {
>             locations: List(Location) where len(self) in [1, 10]
>         },
>         step rating {
>             total_payroll: Num = sum(self.locations as x, x.payroll),
>             premium: Premium [cite: manual:p3] = round(sum(self.locations as l, l.charge), 2, half_up)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             main_site: Bool = self.payroll * 2 > Policy.total_payroll,
>             site_factor: Num = if self.main_site then main_site_factor else 1,
>             charge: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.site_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-MAP-DOMAIN

**A map read must name a defined member.**

A map is a calculation with one value for each member of an enum: `rate[c: Coverage]`. A map over a narrowed enum, such as `rate[c: StandardCoverage]`, has values for the narrowed members only. A read names the member it wants: `self.rate[@Coverage.general]` or `self.rate[self.coverage]`.

The check works out which members the key of each read can be. Every one of them must be a member the map defines. A read at any other member has an undefined value, so the rating would stop.

The message lists the members the read can reach and the members the map defines.

**Example**

`rate` has values for `general` and `professional`. The input `coverage` also accepts `abuse`, and the premium reads `rate` at that input.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            coverage: Coverage
        },
        step rating {
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-MAP-DOMAIN bad/rater.rsl:40:45: `rate[self.coverage]` can read {abuse}, which `rate` does not define (it defines {general, professional}); match on `self.coverage` first, or give `rate` those elements
cost premium: 4 (each list of size 1: 4, 3: 4, 10: 4)
```

**Fix**

Type the input with the narrowed enum, so the key stays inside the map.

Where the filing rates the other members elsewhere, test the key first: `if self.coverage in StandardCoverage then self.rate[self.coverage] else …`. A `match` on the key with an arm for `abuse` works the same way.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000],
            coverage: StandardCoverage
        },
        step rating {
```

**Other causes**

* A read leaves the key out, as in `self.rate`. Name the member: `self.rate[c]`.
* A map or a fold names a type that is a number or a band, as in `rate[c: SizeBand]`. Key it by an enum or a narrowed enum.
* A map-valued table column is read with an input as its key, as in `lookup!(T, …).rate[self.coverage]`. Read the column into a map first, and index that map.
* A map-valued table column is read with a member or a binder of another enum.
* A map-valued table column is read at a member that the column leaves out.
* A plain table column is read with a subscript. Remove the `[…]`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         general,
>         professional,
>         abuse
>     },
>     StandardCoverage = Coverage where self in {@Coverage.general, @Coverage.professional}
> }
>
> Constants = {
>     general_rate [cite: manual:p3] = 2,
>     professional_rate [cite: manual:p3] = 1.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             coverage: Coverage
>         },
>         step rating {
>             rate[c: StandardCoverage]: Num =
>                 match c { @Coverage.general => general_rate, @Coverage.professional => professional_rate },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[self.coverage], 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Coverage = {
>         general,
>         professional,
>         abuse
>     },
>     StandardCoverage = Coverage where self in {@Coverage.general, @Coverage.professional}
> }
>
> Constants = {
>     general_rate [cite: manual:p3] = 2,
>     professional_rate [cite: manual:p3] = 1.5
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             coverage: StandardCoverage
>         },
>         step rating {
>             rate[c: StandardCoverage]: Num =
>                 match c { @Coverage.general => general_rate, @Coverage.professional => professional_rate },
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * self.rate[self.coverage], 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.