> ## 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: tidy raters

> These messages keep a rater short: each one names a construct that a simpler form replaces.

*Generated from the `rslc` compiler.*

## E-ORPHAN-COMPUTED

**Every calculation feeds a rating outcome.**

A rater declares what it returns in `RatingOutcomes`. The check follows each outcome back through the calculations it reads, and through the calculations those read. A calculation outside that chain is an error.

Such a calculation is one of two things. It is left over from an earlier draft, or it is a filed step that the premium forgot to apply. The second kind changes the premium, so the check asks you to decide which one you have.

A calculation that a rule on the record reads is also in use. In a rater with such a rule, the message names `a declared rating outcome or calculated admission guard`.

**Example**

The step computes `payroll_in_thousands`, and `premium` repeats the division in place of reading it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            payroll_in_thousands: Num = self.payroll / 1000,
            premium: Premium [cite: manual:p3] = round(self.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-ORPHAN-COMPUTED bad/rater.rsl:26:13: computed attribute `Policy.payroll_in_thousands` is not reachable from a declared rating outcome
  = because: every calculation feeds a rating outcome; a calculation that none reaches is dead code or a missing wire
  = help: delete `payroll_in_thousands`, or read it where the filing applies it (for example `self.payroll_in_thousands` in the attribute that feeds the premium)
  = applicability: manual
```

**Fix**

Read the calculation where the filing applies it. Here `premium` reads `self.payroll_in_thousands`. When the calculation is left over from a draft, delete it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            payroll_in_thousands: Num = self.payroll / 1000,
            premium: Premium [cite: manual:p3] = round(self.payroll_in_thousands * rate_per_1000, 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
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             payroll_in_thousands: Num = self.payroll / 1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * 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 {
>             payroll_in_thousands: Num = self.payroll / 1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll_in_thousands * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-UNREAD-INPUT

**Every input rates or decides eligibility.**

The caller supplies every input the rater declares. The check asks that each one does some work. An outcome reads it through the calculations, or a business rule reads it to accept or refuse the risk.

An unread input is a question the caller answers for zero effect. More often it marks a filed step that the premium forgot, and the rater then returns the wrong number for every risk.

A plain range on the field, such as `where self in [0.5, 2]`, describes the input and leaves it unread. A rule that carries a `code` and a `message` decides eligibility, so it counts as a reader.

**Example**

The policy asks for `experience_mod`, and the premium leaves it out.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.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-UNREAD-INPUT bad/rater.rsl:24:13: input `Policy.experience_mod` is not read by a rating outcome or admission rule
  = because: every input feeds a rating outcome or decides admission; an input that neither reads is dead code the caller must still supply, or a missing wire
  = help: research the source documentation for `experience_mod`: if it rates or decides eligibility, read it there and correct the rater
  = applicability: manual
```

**Fix**

Look the input up in the filing and read it where the filing applies it. Here the premium multiplies by `self.experience_mod`. Delete an input that the filing leaves unused.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.experience_mod, 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
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             experience_mod: Num {precision: 0.01} where self in [0.5, 2]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * 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],
>             experience_mod: Num {precision: 0.01} where self in [0.5, 2]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.experience_mod, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-TABLE-UNUSED-DIM

**Each table dimension changes an output.**

A table's `dims` are the keys a lookup supplies. The check reads the table's rows and asks that each dimension matters. Somewhere in the file, moving along that dimension changes an output.

A dimension that leaves every output the same is outside the filed function. It multiplies the rows, it adds an argument to every lookup, and it suggests the rate depends on a fact that the filing ignores.

Carriers often print an exhibit this way, with one column repeated for every band. The rater keeps the numbers and the citation and drops the repeated key.

**Example**

`BaseRate` is keyed on territory and payroll band, and each territory has one rate across all three bands.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        urban,
        rural
    },
    PayrollBand = IntBands(0, 250000, 1000000..)
}

Tables = {
    BaseRate [cite: manual:p3] :
        table dims (territory: Territory, payroll: PayrollBand) -> {
            rate_per_1000: Rate
        }
...
        },
        step rating {
            base_rate: Rate = lookup!(BaseRate, self.territory, self.payroll).rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
        }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,payroll,rate_per_1000
urban,0,2.40
urban,250000,2.40
urban,1000000,2.40
rural,0,1.90
rural,250000,1.90
rural,1000000,1.90
```

```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-TABLE-UNUSED-DIM bad/rater.rsl:25:5: BaseRate.rate_per_1000 is the same for every value of `payroll`. Remove `payroll` from the table dims and from its lookups, or write two tables and select one with `match`
  = because: a dimension that changes no output is not part of the filed function; it widens every key and hides the table's shape
  = help: delete `payroll` from `BaseRate : table dims (…)`, keep one CSV row per remaining key (the numbers and the citation stay the same), and drop that argument from every `lookup!(BaseRate, …)`
  = applicability: manual
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Delete the dimension from `dims`, keep one row for each remaining key, and drop the argument from each lookup. Here the `PayrollBand` type goes too, since the table was its only reader.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        urban,
        rural
    }
}

Tables = {
    BaseRate [cite: manual:p3] :
        table dims (territory: Territory) -> {
            rate_per_1000: Rate
        }
...
        },
        step rating {
            base_rate: Rate = lookup!(BaseRate, self.territory).rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
        }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,rate_per_1000
urban,2.40
rural,1.90
```

> [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 = {
>     Territory = {
>         urban,
>         rural
>     },
>     PayrollBand = IntBands(0, 250000, 1000000..)
> }
>
> Tables = {
>     BaseRate [cite: manual:p3] :
>         table dims (territory: Territory, payroll: PayrollBand) -> {
>             rate_per_1000: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(BaseRate, self.territory, self.payroll).rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/BaseRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,payroll,rate_per_1000
> urban,0,2.40
> urban,250000,2.40
> urban,1000000,2.40
> rural,0,1.90
> rural,250000,1.90
> rural,1000000,1.90
> ```
>
> ```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 = {
>     Territory = {
>         urban,
>         rural
>     }
> }
>
> Tables = {
>     BaseRate [cite: manual:p3] :
>         table dims (territory: Territory) -> {
>             rate_per_1000: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(BaseRate, self.territory).rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/BaseRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate_per_1000
> urban,2.40
> rural,1.90
> ```

## E-IF-AS-TABLE

**A long band chain belongs in a table.**

An `if` chain that sorts one number into bands and returns a fixed value for each band is a filed table written as code. The check reports such a chain once it reaches eight arms, counting the final `else`.

A chain qualifies when every condition compares the same number with literal cut points and every arm is a literal. Shorter chains, and chains whose arms compute something, stay as they are.

A table states each cut once and carries a citation to the exhibit that prints it. The compiler then proves that the table answers every admitted value.

**Example**

`size_factor` sorts payroll into eight bands with seven comparisons and a final `else`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
}
...
        },
        step rating {
            size_factor: Factor =
                if self.payroll < 50000 then 1.30
                else if self.payroll < 100000 then 1.20
                else if self.payroll < 250000 then 1.10
                else if self.payroll < 500000 then 1.00
                else if self.payroll < 1000000 then 0.95
                else if self.payroll < 2500000 then 0.90
                else if self.payroll < 5000000 then 0.85
                else 0.80,
            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-IF-AS-TABLE bad/rater.rsl:27:17: `size_factor` is an `if` chain of 8 arms that classifies one number. Declare its cuts as a dimension, such as `SizeFactorBand = IntBands(…)` or `DecimalBands(…)`, and look the value up in a table keyed on that dimension
  = because: a filed classification is data: a table states each cut once, cites it, and the compiler proves it total
  = help: Types: `SizeFactorBand = IntBands(..<c1, c1, c2, …)`; Tables: `SizeFactor [cite: <source>:<region>] : table dims (value: SizeFactorBand) -> { size_factor: <T> }` with one CSV row per arm; body: `size_factor = lookup!(SizeFactor, <value>).size_factor`
  = applicability: placeholders
```

**Fix**

Declare the cuts as a band type, put one row for each arm in a cited table, and look the value up. Use `DecimalBands` when the cuts are decimal numbers.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Types = {
    PayrollBand = IntBands(0, 50000, 100000, 250000, 500000, 1000000, 2500000, 5000000..)
}

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
}

Tables = {
    SizeFactor [cite: manual:p4] :
        table dims (payroll: PayrollBand) -> {
            size_factor: Factor
        }
}
...
        },
        step rating {
            size_factor: Factor = lookup!(SizeFactor, self.payroll).size_factor,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.size_factor, 2, half_up)
```

```csv tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
payroll,size_factor
0,1.30
50000,1.20
100000,1.10
250000,1.00
500000,0.95
1000000,0.90
2500000,0.85
5000000,0.80
```

> [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 {
>             size_factor: Factor =
>                 if self.payroll < 50000 then 1.30
>                 else if self.payroll < 100000 then 1.20
>                 else if self.payroll < 250000 then 1.10
>                 else if self.payroll < 500000 then 1.00
>                 else if self.payroll < 1000000 then 0.95
>                 else if self.payroll < 2500000 then 0.90
>                 else if self.payroll < 5000000 then 0.85
>                 else 0.80,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.size_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 = {
>     PayrollBand = IntBands(0, 50000, 100000, 250000, 500000, 1000000, 2500000, 5000000..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (payroll: PayrollBand) -> {
>             size_factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             size_factor: Factor = lookup!(SizeFactor, self.payroll).size_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,size_factor
> 0,1.30
> 50000,1.20
> 100000,1.10
> 250000,1.00
> 500000,0.95
> 1000000,0.90
> 2500000,0.85
> 5000000,0.80
> ```

## E-TABLE-DUPLICATE

**Each filed column lives in one table.**

The check compares the output columns of tables that share the same dimensions. When a column of one table equals a column of an earlier table on every row, the later table gets this message.

One filed number has one home. Two copies start equal and drift apart when a later filing updates one of them. A single table keeps every lookup on the same numbers.

The check also applies when each table cites its own exhibit. The table you keep carries both citations, so a reviewer still finds both pages.

**Example**

`ProfessionalTerritory` repeats the three territory factors of `GeneralTerritory`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Tables = {
    GeneralTerritory [cite: manual:p4] :
        table dims (territory: Territory) -> {
            factor: Factor
        },
    ProfessionalTerritory [cite: manual:p6] :
        table dims (territory: Territory) -> {
            factor: Factor
...
        step rating {
            general_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
            professional_factor: Factor = lookup!(ProfessionalTerritory, self.territory).factor,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * (general_rate * self.general_factor + professional_rate * self.professional_factor), 2, half_up)
```

```csv tables/ProfessionalTerritory.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,factor
urban,1.20
suburban,1.00
rural,0.85
```

```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-TABLE-DUPLICATE bad/rater.rsl:34:5: `ProfessionalTerritory.factor` equals `GeneralTerritory.factor` on all 3 rows. Look up `GeneralTerritory` and read `factor`, or merge the two tables into one table with both outputs
  = because: one filed number has one home; a copy can drift from its original
  = help: read `lookup!(GeneralTerritory, …).factor` where `ProfessionalTerritory.factor` is read, then delete `factor` from `ProfessionalTerritory` (or the whole table if it has no other output); when the filing prints both exhibits, cite both regions on `GeneralTerritory`
  = applicability: manual
cost premium: 5 (each list of size 1: 5, 3: 5, 10: 5)
```

**Fix**

Read the first table in both lookups, delete the copy, and cite both exhibits on the table you keep.

When the filing sets the two factors separately and they match today, merge them. One table with a `general` and a `professional` output passes the check.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Tables = {
    GeneralTerritory [cite: manual:p4, cite: manual:p6] :
        table dims (territory: Territory) -> {
            factor: Factor
...
        step rating {
            general_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
            professional_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * (general_rate * self.general_factor + professional_rate * self.professional_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 = {
>     Territory = {
>         urban,
>         suburban,
>         rural
>     }
> }
>
> Constants = {
>     general_rate [cite: manual:p3] = 2,
>     professional_rate [cite: manual:p5] = 0.75
> }
>
> Tables = {
>     GeneralTerritory [cite: manual:p4] :
>         table dims (territory: Territory) -> {
>             factor: Factor
>         },
>     ProfessionalTerritory [cite: manual:p6] :
>         table dims (territory: Territory) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             general_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
>             professional_factor: Factor = lookup!(ProfessionalTerritory, self.territory).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * (general_rate * self.general_factor + professional_rate * self.professional_factor), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/GeneralTerritory.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,factor
> urban,1.20
> suburban,1.00
> rural,0.85
> ```
>
> ```csv bad/tables/ProfessionalTerritory.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,factor
> urban,1.20
> suburban,1.00
> rural,0.85
> ```
>
> ```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 = {
>     Territory = {
>         urban,
>         suburban,
>         rural
>     }
> }
>
> Constants = {
>     general_rate [cite: manual:p3] = 2,
>     professional_rate [cite: manual:p5] = 0.75
> }
>
> Tables = {
>     GeneralTerritory [cite: manual:p4, cite: manual:p6] :
>         table dims (territory: Territory) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             general_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
>             professional_factor: Factor = lookup!(GeneralTerritory, self.territory).factor,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * (general_rate * self.general_factor + professional_rate * self.professional_factor), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/GeneralTerritory.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,factor
> urban,1.20
> suburban,1.00
> rural,0.85
> ```

## E-TABLE-SINGLE-MEMBER-KEY

**A table key has two or more values.**

A table dimension chooses among rows. The check reports a dimension whose type is an enum with one member, because every lookup then supplies the same value.

Filed exhibits often print such a column, for example a "Program" column that reads "Standard" on every row. Copying it into the rater adds a key that selects among one choice. Each lookup carries an extra argument, and the table looks as if the rate depends on it.

An enum that loads its members from a CSV file is exempt, since its member list lives outside the rater.

**Example**

`BaseRate` is keyed on `program`, and `Program` has the single member `standard`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        urban,
        rural
    },
    Program = {
        standard
    }
}
...
Tables = {
    BaseRate [cite: manual:p3] :
        table dims (program: Program, territory: Territory) -> {
            rate_per_1000: Rate
        }
...
        },
        step rating {
            base_rate: Rate = lookup!(BaseRate, @Program.standard, self.territory).rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
        }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
program,territory,rate_per_1000
standard,urban,2.40
standard,rural,1.90
```

```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-TABLE-SINGLE-MEMBER-KEY bad/rater.rsl:28:21: dim `program` of `BaseRate` is enum `Program`, which has one member (`standard`), so it selects nothing. Remove `program` from the table dims and from its lookups
  = because: a dimension with one value selects nothing; the table is constant along it
  = help: delete `program: Program` from `BaseRate : table dims (…)`, delete the `program` CSV column, and drop that argument from every `lookup!(BaseRate, …)`
  = applicability: manual
```

**Fix**

Delete the dimension from `dims`, delete its CSV column, and drop the argument from each lookup. The `Program` type goes too once the table stops reading it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        urban,
        rural
    }
}
...
Tables = {
    BaseRate [cite: manual:p3] :
        table dims (territory: Territory) -> {
            rate_per_1000: Rate
        }
...
        },
        step rating {
            base_rate: Rate = lookup!(BaseRate, self.territory).rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
        }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,rate_per_1000
urban,2.40
rural,1.90
```

> [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 = {
>     Territory = {
>         urban,
>         rural
>     },
>     Program = {
>         standard
>     }
> }
>
> Tables = {
>     BaseRate [cite: manual:p3] :
>         table dims (program: Program, territory: Territory) -> {
>             rate_per_1000: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(BaseRate, @Program.standard, self.territory).rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/BaseRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> program,territory,rate_per_1000
> standard,urban,2.40
> standard,rural,1.90
> ```
>
> ```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 = {
>     Territory = {
>         urban,
>         rural
>     }
> }
>
> Tables = {
>     BaseRate [cite: manual:p3] :
>         table dims (territory: Territory) -> {
>             rate_per_1000: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(BaseRate, self.territory).rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.base_rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/BaseRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate_per_1000
> urban,2.40
> rural,1.90
> ```

## E-HOIST

**A calculation belongs where its value varies.**

A record in a list computes its calculations once for each element. The check reports a calculation on a child record that reads only constants and the data of an ancestor record. Every element then computes the same value.

The value belongs to that ancestor. Declared there, it is computed once, and the rater shows which record the number depends on. That matters to a reviewer: a rate that varies by policy sits on the policy, and a rate that varies by location sits on the location.

A calculation that reads any input of its own record stays where it is.

**Example**

Each `Location` computes `modified_rate` from a constant and the policy's `experience_mod`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium = sum(self.locations as l, l.premium)
        }
...
        },
        step rating {
            modified_rate: Rate = rate_per_1000 * Policy.experience_mod,
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.modified_rate, 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-HOIST bad/rater.rsl:35:13: computed attribute `Location.modified_rate` reads only data of its ancestor `Policy`, so every `Location` of one `Policy` computes the same value. Define `modified_rate` on `Policy` and read `Policy.modified_rate`
  = because: a value that depends only on ancestor data belongs to that ancestor: it is computed once there and read by every child
  = help: move `modified_rate: … = …` into `Policy`, rewriting each `Policy.x` in its body as `self.x`; in `Location`, read `Policy.modified_rate` where it read `self.modified_rate`
  = applicability: manual
```

**Fix**

Move the calculation to the parent and write `self.` where it read `Policy.`. The child reads it as `Policy.modified_rate`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            modified_rate: Rate = rate_per_1000 * self.experience_mod,
            premium: Premium = sum(self.locations as l, l.premium)
        }
...
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * Policy.modified_rate, 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
> }
>
> Records = {
>     Policy = {
>         inputs {
>             experience_mod: Num {precision: 0.01} where self in [0.5, 2],
>             locations: List(Location) where len(self) in [1, 20]
>         },
>         step rating {
>             premium: Premium = sum(self.locations as l, l.premium)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             modified_rate: Rate = rate_per_1000 * Policy.experience_mod,
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * self.modified_rate, 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 {
>             experience_mod: Num {precision: 0.01} where self in [0.5, 2],
>             locations: List(Location) where len(self) in [1, 20]
>         },
>         step rating {
>             modified_rate: Rate = rate_per_1000 * self.experience_mod,
>             premium: Premium = sum(self.locations as l, l.premium)
>         }
>     },
>     Location = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * Policy.modified_rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-TABLE-UNUSED

**A lookup reads every table and column.**

Every table a rater declares is checked against its CSV file, cited and shipped with the rater. The check asks that a `lookup!` reads each table, and each output column of it.

A table that the calculations leave unread is one of two things. It is data from an earlier draft, or it is a filed factor that the premium forgot to apply. The second kind gives a wrong premium for every risk, so the check asks you to decide.

**Example**

The rater declares the `SizeFactor` table, and the premium leaves the size factor out.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.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-TABLE-UNUSED bad/rater.rsl:25:5: table `SizeFactor` is never looked up
  = because: every declared table feeds a calculation; an unread table is dead data that is still checked, cited and shipped
  = help: delete `SizeFactor` from `Tables` and delete `tables/SizeFactor.csv`, or look it up where the filing applies it: `lookup!(SizeFactor, …)`
  = applicability: manual
```

**Fix**

Look the table up where the filing applies it. Here `size_factor` reads the table and the premium multiplies by it.

When the filing leaves the table unused, delete it from `Tables` and delete its CSV file.

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

**Other causes**

* A lookup reads the table and skips one of its outputs: `no lookup reads output … of table …`. Read the output, or delete it from the table and from the CSV file.
* A geography in `Geographies` has a declaration and zero `locate` calls: `geography … is never located`. Locate a point in it, or delete it.

> [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 = {
>     PayrollBand = IntBands(0, 250000, 1000000..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (payroll: PayrollBand) -> {
>             size_factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/SizeFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> payroll,size_factor
> 0,1.10
> 250000,1.00
> 1000000,0.90
> ```
>
> ```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 = {
>     PayrollBand = IntBands(0, 250000, 1000000..)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     SizeFactor [cite: manual:p4] :
>         table dims (payroll: PayrollBand) -> {
>             size_factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             size_factor: Factor = lookup!(SizeFactor, self.payroll).size_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,size_factor
> 0,1.10
> 250000,1.00
> 1000000,0.90
> ```

## E-TABLE-CONSTANT-COLUMN

**A column with one value is a constant.**

A table holds numbers that vary with its keys. The check reads each output column and reports one that holds the same value on every row.

Such a column is a single filed number written once for each row. A constant states it once with its own citation. A reader then sees that the value is the same for every risk.

Filed exhibits often print a column this way, so a faithful copy of the exhibit gets the message too. The constant keeps the exhibit's citation.

**Example**

`TerritoryRate` carries a `minimum_premium` column, and every territory has the same \$500 minimum.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Tables = {
    TerritoryRate [cite: manual:p3] :
        table dims (territory: Territory) -> {
            rate_per_1000: Rate,
            minimum_premium: Premium
        }
}
...
        step rating {
            base_rate: Rate = lookup!(TerritoryRate, self.territory).rate_per_1000,
            minimum: Premium = lookup!(TerritoryRate, self.territory).minimum_premium,
            premium: Premium [cite: manual:p3] =
                round(max(self.payroll / 1000 * self.base_rate, self.minimum), 2, half_up)
        }
    }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,rate_per_1000,minimum_premium
urban,2.40,500
suburban,2.10,500
rural,1.90,500
```

```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-TABLE-CONSTANT-COLUMN bad/rater.rsl:25:5: table `TerritoryRate` has 1 output column(s) with one value on every row: `minimum_premium` = `500`
  = because: a column with one value is a constant, not table data: state it once and cite it
  = help: delete each constant column from `TerritoryRate`'s outputs and its CSV; where `lookup!(TerritoryRate, …).<col>` is read, use the constant instead: delete a factor of 1 or an addend of 0, otherwise declare `<col> [cite: manual:p3] = <value>` in `Constants`. A table whose every output is constant is deleted
  = applicability: manual
cost premium: 5 (each list of size 1: 5, 3: 5, 10: 5)
```

**Fix**

Declare the value in `Constants` with the table's citation, delete the column from the table and its CSV file, and read the constant.

A factor of 1 or an addend of 0 needs less: delete it from the calculation. Delete a table whose every output is constant.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Constants = {
    minimum_premium [cite: manual:p3] = 500
}

Tables = {
    TerritoryRate [cite: manual:p3] :
        table dims (territory: Territory) -> {
            rate_per_1000: Rate
        }
}
...
        step rating {
            base_rate: Rate = lookup!(TerritoryRate, self.territory).rate_per_1000,
            premium: Premium [cite: manual:p3] =
                round(max(self.payroll / 1000 * self.base_rate, minimum_premium), 2, half_up)
        }
    }
```

```csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
territory,rate_per_1000
urban,2.40
suburban,2.10
rural,1.90
```

**Other causes**

* One member of a keyed output, such as `rate[wind]`, holds one value on every row. The help then shows how to keep the varying members in the table.

> [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 = {
>     Territory = {
>         urban,
>         suburban,
>         rural
>     }
> }
>
> Tables = {
>     TerritoryRate [cite: manual:p3] :
>         table dims (territory: Territory) -> {
>             rate_per_1000: Rate,
>             minimum_premium: Premium
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(TerritoryRate, self.territory).rate_per_1000,
>             minimum: Premium = lookup!(TerritoryRate, self.territory).minimum_premium,
>             premium: Premium [cite: manual:p3] =
>                 round(max(self.payroll / 1000 * self.base_rate, self.minimum), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/TerritoryRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate_per_1000,minimum_premium
> urban,2.40,500
> suburban,2.10,500
> rural,1.90,500
> ```
>
> ```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 = {
>     Territory = {
>         urban,
>         suburban,
>         rural
>     }
> }
>
> Constants = {
>     minimum_premium [cite: manual:p3] = 500
> }
>
> Tables = {
>     TerritoryRate [cite: manual:p3] :
>         table dims (territory: Territory) -> {
>             rate_per_1000: Rate
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             territory: Territory,
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             base_rate: Rate = lookup!(TerritoryRate, self.territory).rate_per_1000,
>             premium: Premium [cite: manual:p3] =
>                 round(max(self.payroll / 1000 * self.base_rate, minimum_premium), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv good/tables/TerritoryRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> territory,rate_per_1000
> urban,2.40
> suburban,2.10
> rural,1.90
> ```

## E-TABLE-FORMULA

**A straight-line table is a formula.**

Some filed exhibits print the results of a formula, one row for each key. The check reads each numeric dimension of a table and tests whether an output equals `a * key + b` on every row. It reports the column once 32 or more single key values fit the line.

A formula states the rule once. A table of its values hides the rule, adds a row for each key, and stops at the last key the exhibit prints.

Shorter schedules stay tables: a filing that lists four driver counts is stating four numbers. A column with a single row off the line also stays.

**Example**

`WorkerCharge` lists a charge for 1 to 32 workers, and every charge is \$12.50 times the worker count.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Types = {
    WorkerCount = IntValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
}

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
}

Tables = {
    WorkerCharge [cite: manual:p4] :
        table dims (workers: WorkerCount) -> {
            charge: Premium
        }
}
...
        },
        step rating {
            worker_charge: Premium = lookup!(WorkerCharge, self.workers).charge,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 + self.worker_charge, 2, half_up)
```

```csv tables/WorkerCharge.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
workers,charge
1,12.50
2,25.00
3,37.50
4,50.00
5,62.50
6,75.00
7,87.50
8,100.00
9,112.50
10,125.00
11,137.50
12,150.00
13,162.50
14,175.00
15,187.50
16,200.00
17,212.50
18,225.00
19,237.50
20,250.00
21,262.50
22,275.00
23,287.50
24,300.00
25,312.50
26,325.00
27,337.50
28,350.00
29,362.50
30,375.00
31,387.50
32,400.00
```

```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-TABLE-FORMULA bad/rater.rsl:25:5: `WorkerCharge.charge` equals `12.5 * workers` on every row (32 single values of `workers`), so it is a formula printed as a table
  = because: a filed formula is stated once as a formula; a table of its values hides the rule and costs one row per key
  = help: compute it: `charge: <T> [cite: manual:p4] = 12.5 * <workers>`, where `<workers>` is the value the lookup passes; then delete `charge` from `WorkerCharge` (or the whole table if it has no other output)
  = applicability: placeholders
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Compute the value from the key the lookup passed, keep the table's citation on the calculation, and delete the table. The message prints the formula it found.

For a band type with an open top band, the printed formula carries a cap such as `min(workers, 33)`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
}

Constants = {
    rate_per_1000 [cite: manual:p3] = 2
}
...
        },
        step rating {
            worker_charge: Premium [cite: manual:p4] = 12.5 * self.workers,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 + self.worker_charge, 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 = {
>     WorkerCount = IntValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Tables = {
>     WorkerCharge [cite: manual:p4] :
>         table dims (workers: WorkerCount) -> {
>             charge: Premium
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             workers: Num {precision: int} where self in [1, 32]
>         },
>         step rating {
>             worker_charge: Premium = lookup!(WorkerCharge, self.workers).charge,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 + self.worker_charge, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bad/tables/WorkerCharge.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> workers,charge
> 1,12.50
> 2,25.00
> 3,37.50
> 4,50.00
> 5,62.50
> 6,75.00
> 7,87.50
> 8,100.00
> 9,112.50
> 10,125.00
> 11,137.50
> 12,150.00
> 13,162.50
> 14,175.00
> 15,187.50
> 16,200.00
> 17,212.50
> 18,225.00
> 19,237.50
> 20,250.00
> 21,262.50
> 22,275.00
> 23,287.50
> 24,300.00
> 25,312.50
> 26,325.00
> 27,337.50
> 28,350.00
> 29,362.50
> 30,375.00
> 31,387.50
> 32,400.00
> ```
>
> ```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],
>             workers: Num {precision: int} where self in [1, 32]
>         },
>         step rating {
>             worker_charge: Premium [cite: manual:p4] = 12.5 * self.workers,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 + self.worker_charge, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```


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