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

# What rslc checks and guarantees

> The promises a checked rater keeps for every request, the check behind each promise, the message when a check fails, and the limits behind them.

[Install Relativities and download the synthetic examples](/relativities/setup) before running this page.

`rslc check` verifies structural and calculation properties of the implemented rater. This page explains those properties, their assumptions, and the diagnostics.

A passing check establishes properties of the declared inputs, formulas, tables, and citations.
Source review and independent rating examples establish whether those declarations reflect the applicable filing.
[Validate a program](/relativities/how-to/validate-a-program) covers that separate evidence.

Each failing example on this page makes a small change to the dwelling fire rater from
[Write your first rater](/relativities/rsl/tutorial/first-rater).

```mermaid theme={null}
flowchart LR
  request["Request"] --> rules["Check every input and rule"]
  rules --> refusal["Refusal: detected violations, with codes"]
  rules --> calc["Calculate"]
  calc --> premium["Premium, rounded as declared"]
```

## The promises at a glance

| Promise | The check behind it |
| - | - |
| A bad request gets explicit refusal reasons | Each input has a type and a range, and each rule is declared |
| An accepted request always gets a premium | Each division, list, table lookup and choice is proven safe |
| The numbers are exact, and they round as declared | Each precision is proven or rounded by a stated rule |
| Required declarations carry source citations | Each table, constant and rounding rule carries a citation |
| The rater returns a rounded premium | The rater declares its outcome, and the outcome rounds |
| The work of one rating is known in advance | The check counts the steps and prints the cost line |

## A bad request gets explicit refusal reasons

**The promise.** The rater checks the whole request before it calculates anything. A request that
breaks a rule gets a **refusal** that lists detected violations with codes, messages, and available citations.
The [refusal contract](/relativities/rsl/reference/refusals) defines evaluation order and truncation limits.

**The check.** The rater checks each input against its declaration:

* the value has the declared type, such as a number, a date or a member of an enum;
* a number sits on its precision, such as whole cents;
* a number sits inside its range;
* each required input is present;
* each rule from the filing holds.

Send a Coverage A as text and a protection class of 11:

```python theme={null}
import json

import rslc

rater = rslc.PreparedRater("relativities-examples/rsl/dwelling-fire")
policy = {"Policy": {"dwelling": {"coverage_a": "250000", "protection_class": 11}}}
print(rater.rate(json.dumps(policy)))
# {"ok":false,"refusal":{"error":"BusinessRejection","truncated":false,"violations":[{"cite":null,"code":"INPUT-TYPE","constraint":"coverage_a: Num {precision: 0.01} where self in [50000, 2000000]","message":"coverage_a must be a number","path":"Policy.dwelling.coverage_a","value":"250000"},{"cite":"manual:p4","code":"PROTECTION-CLASS","constraint":"protection_class: where self in [1, 10]","message":"Protection class must be 1 to 10.","path":"Policy.dwelling.protection_class","value":11}]}}
```

The refusal names both problems. `PROTECTION-CLASS` is a rule from the filing, so the refusal is a
`BusinessRejection`. A request that breaks only type and range rules gets `InvalidInputs`:

```python theme={null}
print(rater.rate(json.dumps({"Policy": {"items": [{"value": 1200}]}})))
# {"ok":false,"refusal":{"error":"InvalidInputs","truncated":false,"violations":[{"cite":null,"code":"INPUT-TYPE","constraint":"dwelling: Dwelling","message":"missing required input dwelling","path":"Policy.dwelling","value":null}]}}
```

**When the check fails.** Each number input needs a range. With the range taken off
`coverage_a`, the check stops:

```text theme={null}
FAIL rater.rsl (strict)
error: E-INPUT-DOMAIN rater.rsl:59:13: numeric input `Dwelling.coverage_a` declares no domain: add a range `where self in [min, max]` (one side may be `_`). Every numeric input states the values the filing and the rating API accept, for example `coverage_a: Num {precision: 0.01} where self in [0, _]`
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

[Refusals](/relativities/rsl/reference/refusals) lists each refusal code.

## An accepted request always gets a premium

**The promise.** A request that passes the rules gets a premium. The calculation finishes for
every such request, with a value at each step.

**The check.** `rslc` works out the range of each value from the input rules. It then proves each
risky step safe for every value in that range:

| Step | Proof | Message |
| - | - | - |
| Divide by `b` | `b` excludes 0 | `E-DIV-ZERO` |
| Take the `max`, `min` or `avg` of a list | the list has at least one element | `E-EMPTY-FOLD` |
| Look up a band in a table | the key falls inside the table's bands | `E-KEY-RANGE` |
| Look up a row in a table | the supplied table covers each reachable key | `E-LOOKUP-GAP` |
| Read one value of a map | the map has a value for that member | `E-MAP-DOMAIN` |
| Choose with `match` | each possible case has an arm | `E-MATCH` |

`if` narrows the range inside each branch. So `if b != 0 then a / b else 0` passes.

**When the check fails.** The message names the step, the range it found, and the fixes. This
calculation averages the item charges:

```rsl theme={null}
            average_item: Premium = self.item_charge / len(self.items),
```

```text theme={null}
FAIL rater.rsl (strict)
error: E-DIV-ZERO rater.rsl:53:56: divisor `len(self.items)` has range 0..10, which includes 0; guard the division (`if len(self.items) != 0 then … else …`), or narrow the inputs it reads with a refinement
cost premium: 13 + 2*|Policy.items| (each list of size 1: 15, 3: 19, 10: 33)
```

A policy can have zero items, so the division can fail. Each fix makes a choice the filing has to
make:

* **The filing refuses the case.** Change the rule on the input, for example `where len(self) in [1, 10]`. The rater then refuses a policy with zero items.
* **The filing rates the case.** Handle it in the calculation, with the filed value in the other branch:

```rsl theme={null}
            average_item: Premium =
                if len(self.items) != 0 then self.item_charge / len(self.items) else 0,
```

The same choice fixes the other messages. With `max` in place of `sum` over the items:

```text theme={null}
FAIL rater.rsl (strict)
error: E-EMPTY-FOLD rater.rsl:52:54: `max` over `self.items` needs at least one element, but the collection can be empty; guard it (`if count(self.items as x, true) > 0 then max(…) else …`) or reject the empty case explicitly
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

With the protection class range widened to `[0, 10]`, class 0 falls below the first band:

```text theme={null}
FAIL rater.rsl (strict)
error: E-KEY-RANGE rater.rsl:51:75: key `self.dwelling.protection_class` has range 0..10, but `ProtectionClass` (dim `protection` of `BaseRate`) covers 1..; values below 1 have no row: write `max(self.dwelling.protection_class, 1)` or add an open bottom `..<1`; or narrow the inputs it reads with a refinement, or, when the filing has an all-other row, add `otherwise [cite: source:region]: { … }` to the table
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

A `match` over the perils that lists only fire and wind:

```text theme={null}
FAIL rater.rsl (strict)
error: E-MATCH rater.rsl:53:17: match on enum `Peril` is missing: theft
cost premium: 16 + 2*|Policy.items| (each list of size 1: 18, 3: 22, 10: 36)
```

## The numbers are exact, and they round as declared

**The promise.** The rater calculates with exact decimals, so `1.1 + 2.2` is exactly `3.3`. A
value changes only where a `round` says so. Each machine gets the same premium, to the cent.

**The check.** A type with a precision, such as `Money` in whole cents, is a promise about the
value. The check proves the formula lands on that precision. Where it can land between cents, the
formula needs a `round` with the filed mode.

**When the check fails.** Here `charge` is typed `Money`, but 1.5% of a value in cents can land on
a fraction of a cent:

```rsl theme={null}
            charge: Money [cite: manual:p6] = self.value * 0.015
```

```text theme={null}
FAIL rater.rsl (strict)
error: E-COMPUTED-RANGE rater.rsl:69:47: `charge` is declared `Money`, a multiple of 0.01, but its body is not proven to be one, and nothing rounds a value implicitly. Round it with the filed mode: `round(<body>, 2, half_up)` (or `half_even`, `down`, …), or declare it with a type that has no precision
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

The fix is the filed rule: `round(self.value * 0.015, 2, half_up)`.
[Exact numbers](/relativities/rsl/explanation/exact-numbers) explains the arithmetic.

## Required declarations carry source citations

**The promise.** Each required table, constant, and rounding calculation carries a citation or an explicit reason.
A reviewer checks the source region against the implemented value or rule.

**The check.** The check requires a citation on each table, each constant, and each calculation
that rounds. It also checks that each citation names a declared source, in the right form for
that kind of document.

**When the check fails.** With the citation taken off `BaseRate`:

```text theme={null}
FAIL rater.rsl (strict)
error: E-PROVENANCE rater.rsl:31:5: table `BaseRate` has no citation
  = because: every filed number cites the filing region that states it; a number no filing states says why in `[reason: "…"]`
  = help: cite the region that prints it: `BaseRate [cite: manual:p12] : table dims (…)`
  = applicability: manual
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

[Citations](/relativities/rsl/explanation/citations) covers sources, citation forms and reasons.

## The rater returns a rounded premium

**The promise.** Each rater identifies its premium outcome and states the rounding operation that produces it.
The author establishes that the operation follows the filing.

**The check.** The rater needs a `RatingOutcomes` block with at least one `Premium` outcome.
True-or-false and named-value outcomes, such as eligibility, sit next to the premium. Each
outcome that is a number passes through a `round`, and that calculation cites the rounding rule.

**When the check fails.** A rater that leaves out the `RatingOutcomes` block:

```text theme={null}
FAIL rater.rsl (strict)
error: E-RATING-OUTCOME @0-0: strict rater check requires a non-empty top-level `RatingOutcomes` block
```

A premium that multiplies by 1.05 after the last `round`:

```rsl theme={null}
                (sum(Peril as p, self.peril_premium[p]) + self.item_charge + expense_fee) * 1.05
```

```text theme={null}
FAIL rater.rsl (strict)
error: E-OUTCOME-ROUND rater.rsl:53:13: rating outcome `premium` (Policy.premium) does not pass through an explicit round. State the filing's premium rounding rule with round(x, SCALE, MODE) and cite it, for example `round(x, 0, half_up)` for the nearest whole dollar or `round(x, 2, half_up)` for the nearest cent
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

A sum of rounded values counts as rounded. So a premium that adds rounded peril premiums, rounded
item charges and a whole-dollar fee passes.

## The work of one rating is known in advance

**The promise.** The check states how much work one rating takes. The work grows with the length
of each list in the request, at a rate the check prints.

**The check.** `rslc check` counts the steps for each outcome: each calculation, table lookup and
pass over a list. It prints the count as the **cost line**:

```text theme={null}
PASS rater.rsl (strict)
cost premium: 12 + 2*|Policy.items| (each list of size 1: 14, 3: 18, 10: 32)
```

`|Policy.items|` is the number of scheduled items. The premium takes 12 steps, plus 2 for each
item. The figures in brackets show the total for 1, 3 and 10 items. The count is an upper bound.

**When the check fails.** A calculation on each item that walks the whole item list makes the
work grow with the square of the list length. The check stops it with `E-QUADRATIC`, and names
the fix: compute the walk once, on the policy.

## The limits that make these promises possible

RSL restricts calculations and input constraints so its checker can establish these properties. The author remains responsible for faithful source interpretation.

### Calculations follow the manual, step by step

RSL repeats work in one way only: a calculation walks a list or an enum with `sum`, `product`,
`min`, `max`, `avg` or `count`. A calculation that needs its own value fails the check:

```rsl theme={null}
            tax: Premium [cite: manual:p9] = round(self.premium * 0.05, 2, half_up),
```

```text theme={null}
FAIL rater.rsl (strict)
error: E-CYCLE rater.rsl:53:13: attribute dependency cycle: Policy.tax -> Policy.premium -> Policy.tax; every rating would fail at runtime. Compute the shared value in its own attribute so that no attribute needs itself
```

Here the premium adds `tax`, and `tax` reads the premium. Compute the amount before tax in its own
calculation, and read it in both.

### Rules go on the inputs

A calculation always produces a value, so it stops short of refusing a request. Each rule that
refuses a request goes on an input, or on a record after its closing brace. The rater then
checks every rule before it calculates, and refuses with every reason at once.

### Tables cover every key a lookup reaches

Each lookup finds a row for every request the rules accept. When the filing leaves out some keys,
choose one of these:

* narrow the input so it excludes those keys, with a rule from the filing;
* type the input `Key(T)`, so the rater accepts only keys that table `T` prints;
* add a cited `otherwise` row when the filing prints an all-other value;
* write `unfiled` in a row the filing leaves blank, and narrow the input to exclude it.

[Read a filed table](/relativities/rsl/how-to/read-a-filed-table) shows each choice.

## Related

* [Check messages](/relativities/rsl/reference/check-messages) lists each message with its fix.
* [Refusals](/relativities/rsl/reference/refusals) describes each part of a refusal.
* [Test a rater](/relativities/rsl/how-to/test-a-rater) generates requests from the rules and rates them.


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