> ## 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: the rating manual

> Labels, notes, stages and concepts describe a rater as its manual reads. These messages compare that description with the calculations.

*Generated from the `rslc` compiler.*

## E-CONCEPT-UNKNOWN

**A concept type names a vocabulary concept.**

A concept type wraps a value's type and says what the value is in the rating manual: `Relativity(Factor)`, `Subtotal(Premium)`, `Exposure(Num)`. The check asks each concept to be a name from the rater's vocabulary.

A rater that declares RSL 0.4.1 reads the `rating@2` vocabulary, unless `spec` names another with `vocabulary`. One shared list of concepts lets every reader and every tool treat a `Relativity` the same way in every rater.

When the name is close to a concept, the help line gives that concept.

**Example**

The concept `PolicyPremiums` ends in a stray `s`. The vocabulary's name is `PolicyPremium`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: PolicyPremiums(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-CONCEPT-UNKNOWN bad/rater.rsl:26:22: `PolicyPremiums` is not a concept of the vocabulary rating@2
  = because: a concept type names a concept of the rater's vocabulary
  = help: PolicyPremium
  = applicability: machine
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Write the concept the help line names. The plain type `Premium`, with the concept left out, also passes.

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

**Other causes**

* A table column's concept type, such as `rate: BaseRates(Rate)`, gets the same message with the column's name. Correct the concept there.

> [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 {
>             premium: PolicyPremiums(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 {
>             premium: PolicyPremium(Premium) [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-CONCEPT-TARGET

**A concept sits where its vocabulary allows.**

Each concept of the vocabulary describes inputs, calculations or both. `Exposure` describes both. `PolicyPremium` describes a calculation: a rater computes a premium, and a caller supplies facts. The check asks each concept type to sit on the kind of value it describes.

The check also asks for a concrete concept, written once as the outermost type of an input, a calculation or a table column.

**Example**

The input `payroll` takes `PolicyPremium`, a concept for calculations.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
    Policy = {
        inputs {
            payroll: PolicyPremium(Num) {precision: int} where self in [0, 100000000]
        },
        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-CONCEPT-TARGET bad/rater.rsl:23:22: `PolicyPremium` describes calculations, and `Policy.payroll` is an input
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Give the input a concept that describes inputs. Payroll is the exposure base, so `Exposure` fits. The plain type `Num` also passes.

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

**Other causes**

* The concept is abstract, such as `Amount` or `Multiplier`. An abstract concept groups its children: use one of the children the help line lists.
* A concept sits inside another type, as in `Subtotal(PolicyPremium(Premium))`. Write one concept, outermost.
* A table column takes a concept under `vocabulary: "rating@1"`. That vocabulary keeps concepts on inputs and calculations; `rating@2` accepts them on columns.

> [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: PolicyPremium(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
> }
> ```
>
> ```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: Exposure(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
> }
> ```

## E-CONCEPT-UNIT

**A concept's unit matches the value's type.**

Each concept has a unit: money, ratio, delta, rate, number, category, flag or date. A value's RSL type has a unit too. `Premium` is money, `Factor` and `Pct` are ratios, `Bool` is a flag and an enum is a category. The check asks the two units to agree.

A mismatch means the concept or the type is wrong, and either one misleads a reader of the rating manual. The help line lists concepts whose unit fits the type.

**Example**

The policy premium has the type `Premium`, which is money, and the ratio concept `Relativity`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Relativity(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-CONCEPT-UNIT bad/rater.rsl:26:22: `Policy.premium` is `Relativity`, which is ratio, but its type `Premium` is money
  = because: a `Relativity` value is ratio
  = help: BasePremium(Premium); concepts that fit: BasePremium, Charge, ExpenseConstant, Fee, MaximumPremium, MinimumPremium
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Use a money concept. `PolicyPremium` describes the premium of the policy.

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

**Other causes**

* The type is `Num`, which leaves the unit to the formula, and the formula gives another unit. `Relativity(Num) = self.manual_premium * 1.1` is money under a ratio concept.
* `Eligibility` sits on a `Factor`. A factor that is 1 for a valid request is a check: state it as an admission rule, `where … [code: …]`.
* A table column's concept disagrees with the column's type. Change the concept or the column type.

> [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 {
>             premium: Relativity(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 {
>             premium: PolicyPremium(Premium) [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-CONCEPT-SHAPE

**A concept's formula has the concept's shape.**

Some concepts promise a shape of formula. A `Relativity` is one filed factor. A `Composite` combines two or more factors. A `MinimumPremium` is applied with `max`, a `MaximumPremium` with `min`, and a `Rounding` holds a `round`. The check compares each of these concepts with the formula of its calculation.

The rating manual shows a value by its concept. A product called a `Relativity` sends a reviewer to the filing for one factor that the filing states as two.

**Example**

`modification` multiplies two factors and calls the product a `Relativity`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            modification: Relativity(Factor) = self.experience_mod * self.schedule_mod,
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.modification, 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-CONCEPT-SHAPE bad/rater.rsl:28:27: `Policy.modification` is `Relativity`, but its formula is a product of 2 factors, not a filed value
  = because: a product of factors is a `Composite`; move the product to its own calculation, or call the value `Composite`
  = help: Composite(Factor)
  = applicability: machine
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Call the product a `Composite`, as the help line says.

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

**Other causes**

* `Composite` sits on a single factor. Use `Relativity`.
* `MinimumPremium` sits on a value that lacks a `max(…)`. Apply the minimum with `max`, in its own formula or in a calculation that reads it.
* `MaximumPremium` sits on a value that lacks a `min(…)`. Apply the maximum with `min` the same way.
* `Rounding` sits on a formula that lacks a `round(…)`. Round the value, or use a concept such as `Subtotal`.

> [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: Factor {precision: 0.01} where self in [0.5, 2],
>             schedule_mod: Factor {precision: 0.01} where self in [0.75, 1.25]
>         },
>         step rating {
>             modification: Relativity(Factor) = self.experience_mod * self.schedule_mod,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.modification, 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: Factor {precision: 0.01} where self in [0.5, 2],
>             schedule_mod: Factor {precision: 0.01} where self in [0.75, 1.25]
>         },
>         step rating {
>             modification: Composite(Factor) = self.experience_mod * self.schedule_mod,
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.modification, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-UNIT-MIX

**Arithmetic combines units that agree.**

The check follows units through every formula. A `Premium` is money, a `Factor` or a `Pct` is a ratio, and a concept adds its own unit. Money times a ratio is money. Money plus a ratio mixes two units, so one operand or the operator is a mistake.

Both operands are numbers, so the formula has a value. The unit check catches the slip that the arithmetic allows.

The check runs on a rater that describes itself for the rating manual with a concept type, a label, a note or `Stages`.

**Example**

`premium` adds the experience modification to the base premium, where the manual multiplies.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
                round(self.payroll / 1000 * rate_per_1000, 2, half_up),
            premium: PolicyPremium(Premium) [cite: manual:p3] =
                round(self.base_premium + self.experience_mod, 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-UNIT-MIX bad/rater.rsl:29:13: `Policy.premium` adds money and a ratio
  = because: an amount and a factor have different units
  = help: multiply the ratio into the amount first, or check which value is the premium
  = applicability: manual
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Multiply the amount by the factor.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
                round(self.payroll / 1000 * rate_per_1000, 2, half_up),
            premium: PolicyPremium(Premium) [cite: manual:p3] =
                round(self.base_premium * self.experience_mod, 2, half_up)
        }
    }
```

**Other causes**

* `max(…)` or `min(…)` compares money with a ratio. Compare two amounts or two ratios.
* A formula multiplies money by money. Multiply an amount by a ratio, or divide two amounts for a ratio.
* A delta, such as a `ScheduleRating` credit of -0.10, multiplies or adds to an amount directly. Apply it as `amount * (1 + delta)`.
* An admission rule mixes units in its `where`. The message names the rule.

> [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: Factor {precision: 0.01} where self in [0.5, 2]
>         },
>         step rating {
>             base_premium: BasePremium(Premium) [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: PolicyPremium(Premium) [cite: manual:p3] =
>                 round(self.base_premium + self.experience_mod, 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: Factor {precision: 0.01} where self in [0.5, 2]
>         },
>         step rating {
>             base_premium: BasePremium(Premium) [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: PolicyPremium(Premium) [cite: manual:p3] =
>                 round(self.base_premium * self.experience_mod, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-STAGE-UNDECLARED

**A step names a stage that `Stages` declares.**

`Stages` lists the stages of the rating manual in order, each with a title: classification, base rates, factors, premium. A step joins a stage with `step name in stage`. The check asks each step to name a stage that `Stages` declares, and `Stages` to declare each stage once.

Stages let a reader follow the rater in the order the manual presents it. When the name is close to a declared stage, the help line gives that stage.

**Example**

The step joins `premiums`, and `Stages` declares `premium`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating in premiums [note: "Payroll times the filed rate gives the policy premium."] {
            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-STAGE-UNDECLARED bad/rater.rsl:29:21: stage `premiums` is not declared in `Stages`
  = because: a step names one of the stages `Stages` declares
  = help: in premium
  = applicability: machine
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Name the declared stage.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating in premium [note: "Payroll times the filed rate gives the policy premium."] {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

**Other causes**

* A step uses `in` in a rater that lacks a `Stages` section. Declare the stage, as the help line shows.
* `Stages` declares one stage twice. Keep one line for each stage.

> [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
>     }
> }
>
> Stages = {
>     premium: "Policy premium"
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating in premiums [note: "Payroll times the filed rate gives the policy premium."] {
>             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
>     }
> }
>
> Stages = {
>     premium: "Policy premium"
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating in premium [note: "Payroll times the filed rate gives the policy premium."] {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-STAGE-MISSING

**Every step joins a stage.**

A rater that declares `Stages` presents its whole calculation by stage. The check asks every `step` of every record to join a stage with `in`.

A step outside the stages falls out of the manual's order, and a reader loses the place of its values. The help line lists the declared stages.

A step that lacks both a stage and a note of its own also gets `E-NOTE-MISSING`.

**Example**

The rater declares the stage `premium`, and the step `rating` lacks `in`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating [note: "Payroll times the filed rate gives the policy premium."] {
            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-STAGE-MISSING bad/rater.rsl:29:9: step `Policy.rating` is in no stage
  = because: a rater that declares `Stages` places every step in one
  = help: step rating in <stage>, one of: premium
  = applicability: placeholders
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Add `in` and the stage after the step's name.

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

## E-STAGE-ORDER

**A stage reads its own or earlier stages.**

The order of `Stages` is the order of the rating manual. The check asks each calculation to read values from its own stage or from an earlier one.

A reader then meets every value before the stage that uses it, as in a filed manual.

**Example**

`Stages` lists `total` first, and the premium of `total` reads `payroll_premium` from the later stage `payroll`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Stages = {
    total: "Policy premium" [note: "The expense fee is added to the payroll premium."],
    payroll: "Payroll premium" [note: "Payroll times the filed rate gives the payroll premium."]
}
```

```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-STAGE-ORDER bad/rater.rsl:35:13: `Policy.premium` in stage `total` reads `Policy.payroll_premium` from the later stage `payroll`
  = because: a stage reads only values from its own stage or an earlier one
  = help: step total in payroll, or step payroll in total
  = applicability: manual
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

List the stages in the order the rating runs. When the order of `Stages` is right, move the misplaced step to another stage: the help line names both moves.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Stages = {
    payroll: "Payroll premium" [note: "Payroll times the filed rate gives the payroll premium."],
    total: "Policy premium" [note: "The expense fee is added to the payroll premium."]
}
```

> [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
>     }
> }
>
> Stages = {
>     total: "Policy premium" [note: "The expense fee is added to the payroll premium."],
>     payroll: "Payroll premium" [note: "Payroll times the filed rate gives the payroll premium."]
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     expense_fee [cite: manual:p3] = 50
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step payroll in payroll {
>             payroll_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         },
>         step total in total {
>             premium: Premium = self.payroll_premium + expense_fee
>         }
>     }
> }
>
> 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
>     }
> }
>
> Stages = {
>     payroll: "Payroll premium" [note: "Payroll times the filed rate gives the payroll premium."],
>     total: "Policy premium" [note: "The expense fee is added to the payroll premium."]
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2,
>     expense_fee [cite: manual:p3] = 50
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step payroll in payroll {
>             payroll_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         },
>         step total in total {
>             premium: Premium = self.payroll_premium + expense_fee
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-GROUP

**A `collect` group needs a title and members.**

`collect` gathers calculations into one row of the rating manual, under the calculation that heads the group. `title` names the row. A policy's discounts are a typical group: the manual shows a row "Policy discounts" that opens to each discount.

The check asks each group for a title and for members that are calculations of the head's record. Each calculation belongs to one group at most.

**Example**

`premium` collects `payroll_premium` and leaves the row unnamed.

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

```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-GROUP bad/rater.rsl:28:31: `collect` on `Policy.premium` has no `title:`
  = because: a group is one row, and the title is the row's name
  = help: premium [collect: …, title: "…"]
  = applicability: manual
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Add `title` beside `collect`.

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

**Other causes**

* `collect` sits on an input. Write it on the calculation that heads the group.
* A member is an input or a misspelt name. Name calculations of the head's record.
* Two groups name the same calculation. Keep it in one of them.

> [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,
>     expense_fee [cite: manual:p3] = 50
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             payroll_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium [collect: payroll_premium] = self.payroll_premium + expense_fee
>         }
>     }
> }
>
> 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,
>     expense_fee [cite: manual:p3] = 50
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             payroll_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium [collect: payroll_premium, title: "Policy premium"] =
>                 self.payroll_premium + expense_fee
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-NOTE-MISSING

**A step or its stage has a note.**

A note is one sentence that tells a reader what a step does. In a rater that declares `Stages`, the check asks every step for a note. The step's own `note` counts, and so does the `note` of its stage.

The rating manual prints the sentence above the step's values, so each part of the calculation opens with its purpose.

**Example**

The stage `premium` and its step `rating` both lack a note.

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

```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-NOTE-MISSING bad/rater.rsl:29:9: step `Policy.rating` has no note, and neither does its stage
  = because: a reader sees one sentence for every step, its own or its stage's
  = help: step rating [note: "…"], or a `note` on its stage
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Write the note on the stage, where it covers every step of the stage. A step that needs its own sentence takes one after its stage: `step rating in premium [note: "…"]`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Stages = {
    premium: "Policy premium" [note: "Payroll times the filed rate gives the policy premium."]
}
```

> [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
>     }
> }
>
> Stages = {
>     premium: "Policy premium"
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating in premium {
>             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
>     }
> }
>
> Stages = {
>     premium: "Policy premium" [note: "Payroll times the filed rate gives the policy premium."]
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating in premium {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-NOTE-SENTENCE

**A note is one short sentence.**

A `note` on a stage, a step, a value, a record or a table is one sentence of at most 200 characters. The check counts the characters and looks for a second sentence: a full stop, exclamation mark or question mark followed by more text.

The rating manual prints a note beside the thing it describes. One sentence keeps the manual quick to scan, and `cite` points to the page with the detail.

**Example**

The note of the step `rating` holds two sentences.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating [note: "Payroll times the filed rate. The result is the policy premium."] {
            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-NOTE-SENTENCE bad/rater.rsl:25:22: the note "Payroll times the filed rate. The result is the policy premium." is not one sentence: it has more than one sentence
  = because: a note is one sentence of at most 200 characters, in the reader's words
  = help: keep the sentence that says what the step does; put a section name in `cite`
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Say it in one sentence.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
            payroll: Num {precision: int} where self in [0, 100000000]
        },
        step rating [note: "Payroll times the filed rate gives the policy premium."] {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

**Other causes**

* The note runs past 200 characters. Shorten it and leave the detail to the cited page.

> [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 [note: "Payroll times the filed rate. The result is the policy premium."] {
>             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 [note: "Payroll times the filed rate gives the policy premium."] {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-MEANING-KEY

**A record's brackets take `label` and `note`.**

Brackets after a record's name describe the record in the rating manual: `Policy [label: "Policy", note: "…"]`. The check asks those brackets to hold `label` and `note` only.

Other keys belong elsewhere. `cite` goes where a filed number is: on a value, a table, a constant or a stage. `collect` and `title` go on the calculation that heads a group.

**Example**

The record `Policy` carries a `cite`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Records = {
    Policy [label: "Policy", cite: manual:p3] = {
        inputs {
            payroll: Num {precision: int} where self in [0, 100000000]
```

```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-MEANING-KEY bad/rater.rsl:21:30: `cite` on the record `Policy`: a record takes only `label:` and `note:`
  = because: a record's brackets hold its rating meaning: its label and its note
  = help: remove `cite` from `Policy [...]`
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Remove `cite` from the record's brackets. The premium below already cites the page.

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

> [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 [label: "Policy", cite: manual:p3] = {
>         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
> }
> ```
>
> ```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 [label: "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
> }
> ```

## E-ROUND-SPLIT

**A rounding stays with its arithmetic.**

The rating manual shows a calculated value as arithmetic over named values, and shows its rounding as part of that value. The check finds a calculation whose whole formula is `round(self.x, …)`, where that `round` is the only reader of `x`.

Such a pair turns one filed step into two rows. The unrounded row has a name that only the rounding uses.

A value that another calculation or a rating outcome also reads keeps its own row. The check covers every rater that declares RSL 0.4.1 or later.

**Example**

`manual_premium` holds the arithmetic, and `premium` only rounds it.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            manual_premium: Premium = self.payroll / 1000 * rate_per_1000,
            premium: Premium [cite: manual:p3] = round(self.manual_premium, 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-ROUND-SPLIT bad/rater.rsl:27:50: `Policy.premium` only rounds `Policy.manual_premium`, which nothing else reads, so the rounding sits apart from the arithmetic it rounds
  = because: a rounding is part of the value whose arithmetic it rounds
  = help: round the arithmetic itself: `premium = round(self.payroll / 1000 * rate_per_1000, 2, half_up)`, so the rounding belongs to the value, and remove `Policy.manual_premium`
  = applicability: manual
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Round the arithmetic in one calculation and remove the other. The help line writes the merged formula.

```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)
        }
    }
```

> [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 {
>             manual_premium: Premium = self.payroll / 1000 * rate_per_1000,
>             premium: Premium [cite: manual:p3] = round(self.manual_premium, 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 {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-MAX-BRANCH

**Write the larger of two values with `max`.**

The rating manual shows an `if` as a condition of the filed rule, and shows `max` or `min` as one value. The check finds an `if` that compares two values and returns one of them: `if a > b then a else b`. That is `max(a, b)`.

Written with `max`, a minimum premium reads as a minimum premium. The check covers `>`, `>=`, `<` and `<=`, and gives `min` where the smaller value wins.

The check covers every rater that declares RSL 0.4.1 or later.

**Example**

`premium` applies the minimum premium with an `if`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            manual_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
            premium: Premium =
                if self.manual_premium > minimum_premium then self.manual_premium else minimum_premium
        }
    }
```

```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-MAX-BRANCH bad/rater.rsl:29:17: `Policy.premium` writes `max(self.manual_premium, minimum_premium)` as an `if`, which the rating manual shows as a condition
  = because: the larger or the smaller of two values is `max` or `min`, which the manual shows as one value
  = help: write `max(self.manual_premium, minimum_premium)`
  = applicability: manual
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

**Fix**

Write the `max` that the help line gives.

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

> [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,
>     minimum_premium [cite: manual:p3] = 250
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             manual_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium =
>                 if self.manual_premium > minimum_premium then self.manual_premium else minimum_premium
>         }
>     }
> }
>
> 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,
>     minimum_premium [cite: manual:p3] = 250
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             manual_premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up),
>             premium: Premium = max(self.manual_premium, minimum_premium)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-CHAIN

**Arithmetic reads named values.**

The rating manual shows a calculation as a chain: named values joined by one operator, such as base rate times territory factor times class factor. The check asks each operand of a calculation's arithmetic to be a value a reader can name. Inputs, constants, numbers and other calculations qualify, and so does arithmetic over them.

An inline lookup, `if`, `max`, `min` or `round` among the operands is a step of the filing with its name left out. Give it a name, and the manual shows it as a row of its own.

The check covers every rater that declares RSL 0.4.1 or later.

**Example**

`premium` adds the expense fee to an inline `max`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] =
                round(max(self.payroll / 1000 * rate_per_1000, minimum_premium) + expense_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-CHAIN bad/rater.rsl:29:23: `Policy.premium` adds an inline `max` or `min` `max(self.payroll / 1000 * rate_per_1000, minimum_premium)`, which is not a named value, so the rating manual cannot show its arithmetic as a chain
  = because: a calculation is arithmetic over named values, constants and inputs
  = help: name it as its own value with a label and a note, `<name>: <Type> [label: "…", note: "…"] = max(self.payroll / 1000 * rate_per_1000, minimum_premium)`, and read `self.<name>` in `premium`
  = applicability: placeholders
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Move the `max` to its own calculation and read that calculation by name. A `label` and a `note` on it give the new row its words in the manual.

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

## E-ROLE-FLOW

**The premium build reads each role its way.**

In the `rating@2` vocabulary every concept has a role: premium, factor, base, fact, constraint or choice. Premiums, factors and bases form the premium build. The check follows how the premium build reads the other roles.

A choice is a value the underwriter enters, such as a position in a filed range. The premium build takes it as the position of `select(low..high, position)` or after `overridden by`. The filed range then stays in view beside the choice.

A constraint, such as a minimum premium or a rate cap, bounds a value through `max`, `min` or `select`. A flag decides a condition.

**Example**

`premium` multiplies by `schedule_position`, the underwriter's `Selection`, as if it were a factor.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.schedule_position, 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-ROLE-FLOW bad/rater.rsl:27:13: `Policy.premium` reads the choice `Policy.schedule_position` (`Selection`) in the premium build, outside `select(…)` and `overridden by`
  = because: the premium build takes an underwriter's choice only as the position of a `select(…)` or as an override; a choice, a constraint or a check may read it
  = help: write the filed value as the default and end it with `overridden by` and `Policy.schedule_position`, or make `Policy.schedule_position` the position of `select(low..high, position)`
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Read the choice as the position of a `select` over the filed range.

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

**Other causes**

* The premium build multiplies or adds a constraint, such as a `RateCap` or `Validation` value. Apply it with `max(…)` or `min(…)`, or state its test as an admission rule.
* The premium build uses a flag, such as an `Eligibility` value, as a number. Test the flag in an `if`.

> [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],
>             schedule_position: Selection(Num) {precision: 0.01} where self in [0, 1]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.schedule_position, 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],
>             schedule_position: Selection(Num) {precision: 0.01} where self in [0, 1]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * select(0.75..1.25, self.schedule_position), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-ROLE-CONCEPT

**A concept's role matches its use.**

In the `rating@2` vocabulary a premium, a factor and a base are operands of the premium build. The check compares that claim with what the rater does. It finds a value with one of those concepts that only bounds another value, only serves admission rules or only chooses.

Such a value is a constraint or a choice. Under the right concept, the rating manual shows it beneath the value it serves and keeps it out of the premium chain.

**Example**

`lowest_factor` is the low end of a `select` range and takes the factor concept `Relativity`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            lowest_factor: Relativity(Factor) [cite: manual:p3] = if self.payroll >= 1000000 then 0.6 else 0.75,
            schedule_factor: Factor = select(self.lowest_factor..1.25, self.schedule_position),
            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-ROLE-CONCEPT bad/rater.rsl:27:28: `Policy.lowest_factor` is `Relativity`, a factor, but it only bounds other values or serves checks
  = because: a factor is in the premium build, and a value that only checks or bounds read is a constraint
  = help: give it `Bound(Factor)`, or no concept: its role is then inferred from what reads it
  = applicability: manual
cost premium: 3 (each list of size 1: 3, 3: 3, 10: 3)
```

**Fix**

Use the constraint concept the help line names, `Bound`. The plain type `Factor` also passes, because the compiler infers the role from the reads.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            lowest_factor: Bound(Factor) [cite: manual:p3] = if self.payroll >= 1000000 then 0.6 else 0.75,
            schedule_factor: Factor = select(self.lowest_factor..1.25, self.schedule_position),
            premium: Premium [cite: manual:p3] =
```

**Other causes**

* Admission rules are the only readers of the value. The help line gives a constraint concept.
* The value serves only as the position of a `select(…)`. It is a choice: use the concept the help line names.

> [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],
>             schedule_position: Selection(Num) {precision: 0.01} where self in [0, 1]
>         },
>         step rating {
>             lowest_factor: Relativity(Factor) [cite: manual:p3] = if self.payroll >= 1000000 then 0.6 else 0.75,
>             schedule_factor: Factor = select(self.lowest_factor..1.25, self.schedule_position),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.schedule_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
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             schedule_position: Selection(Num) {precision: 0.01} where self in [0, 1]
>         },
>         step rating {
>             lowest_factor: Bound(Factor) [cite: manual:p3] = if self.payroll >= 1000000 then 0.6 else 0.75,
>             schedule_factor: Factor = select(self.lowest_factor..1.25, self.schedule_position),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.schedule_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-CONCEPT-PARENT

**A concept has a role.**

In a vocabulary with roles, each concept says what its value does in the rating: premium, factor, base, fact, constraint or choice. A child concept inherits the role of its parent. This check guards a value whose concept lacks a role along its whole line of parents.

A current rater stays clear of it. A rater reads the built-in vocabularies `rating@1` and `rating@2` only, and every concept of `rating@2` has a role. A concept name from outside the vocabulary gets `E-CONCEPT-UNKNOWN`.

## E-VOCABULARY

**The spec names a vocabulary rslc knows.**

A vocabulary is the list of concepts a rater may use, such as `Relativity` and `PolicyPremium`. The compiler has two built in: `rating@1` and `rating@2`. The optional `vocabulary` field of `spec` picks one, and the check asks it to be one of the two.

A rater that declares RSL 0.4.1 reads `rating@2` when `spec` leaves the field out. `rating@2` adds a role to every concept and checks how the premium build reads each role.

**Example**

`spec` asks for `rating@3`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
spec {
    rsl: "0.4.1",
    vocabulary: "rating@3",
    carrier: "Example Mutual",
    state: AZ,
```

```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-VOCABULARY bad/rater.rsl:3:17: `rating@3` is not a vocabulary rslc knows
  = because: `spec.vocabulary` names the vocabulary of the rater's concepts; rslc knows rating@1 and rating@2
  = help: vocabulary: "rating@2" or vocabulary: "rating@1", or no `vocabulary` for the RSL version's default
  = applicability: manual
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

Name `rating@2`, or remove the field to read the same vocabulary.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
spec {
    rsl: "0.4.1",
    vocabulary: "rating@2",
    carrier: "Example Mutual",
    state: AZ,
```

**Other causes**

* `spec` names `rating@2` and declares an RSL version before 0.4.1. Declare `rsl: "0.4.1"`, or remove `vocabulary` to read `rating@1`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     vocabulary: "rating@3",
>     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 {
>             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",
>     vocabulary: "rating@2",
>     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 {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```


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