> ## 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.

# Write your first rater

> Turn a small synthetic rating manual into a working RSL rater, then calculate a dwelling-fire premium.

[Install Relativities](https://docs.canary.effectiveai.app/relativities/setup.md) before starting. The installation includes the `rslc` compiler.

To understand the syntax first, read [RSL at a glance](https://docs.canary.effectiveai.app/rsl/language-tour.md).

## The rating plan

This example uses these rules:

* Multiply Coverage A, in thousands of dollars, by a rate for each peril.
* Choose rates from the dwelling’s protection class.
* Add 1.5% of each scheduled item’s value.
* Add a \$35 policy fee.
* Round the total premium to whole dollars, half up.

A \$250,000 dwelling in protection class 5, with scheduled items worth \$1,200 and \$800, produces a \$1,028 premium.

The rates, rules, and source citations are synthetic.

## Create the files

Create a folder for the rater and its rate table:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
mkdir -p dwelling/tables
cd dwelling
```

Your files have this layout:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
dwelling/
  rater.rsl
  tables/
    BaseRate.csv
```

### Rating algorithm

Save the following source as `rater.rsl` in the `dwelling/` folder.

> [accordion] rater.rsl
>
> ```rsl dwelling/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Dwelling Fire (tutorial)",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Peril = {
>         fire,
>         wind,
>         theft
>     },
>     ProtectionClass = IntBands(1, 4, 7, 10..),
>     Money = Num {precision: 0.01}
> }
>
> Constants = {
>     expense_fee [cite: manual:p9] = 35
> }
>
> Tables = {
>     BaseRate [cite: manual:p3] :
>         table dims (protection: ProtectionClass) -> {
>             rate: Rate[Peril]
>         }
>         columns: {
>             rate[fire]: "fire",
>             rate[wind]: "wind",
>             rate[theft]: "theft"
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             dwelling: Dwelling,
>             items: List(Item)
>                 where len(self) in [0, 10] [code: "TOO-MANY-ITEMS", message: "A policy schedules at most ten items.", cite: manual:p6]
>         },
>         step premium {
>             base_rate[p: Peril]: Rate =
>                 lookup!(BaseRate, self.dwelling.protection_class).rate[p],
>             peril_premium[p: Peril]: Premium [cite: manual:p8] =
>                 round(self.dwelling.coverage_a / 1000 * self.base_rate[p], 2, half_up),
>             item_charge: Premium [cite: manual:p6] = sum(self.items as i, i.charge),
>             premium: Premium [cite: manual:p9] =
>                 round(sum(Peril as p, self.peril_premium[p]) + self.item_charge + expense_fee, 0, half_up)
>         }
>     },
>     Dwelling = {
>         inputs {
>             coverage_a: Money where self in [50000, 2000000],
>             protection_class: Num {precision: int}
>                 where self in [1, 10] [code: "PROTECTION-CLASS", message: "Protection class must be 1 to 10.", cite: manual:p4]
>         }
>     },
>     Item = {
>         inputs {
>             value: Money where self in [0.01, 50000]
>         },
>         step item {
>             charge: Premium [cite: manual:p6] = round(self.value * 0.015, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

The source defines the policy’s inputs, reads the rate table, calculates premiums, and publishes the final `premium` outcome.
`base_rate` gives the table lookup a name, so `peril_premium` is arithmetic over named values.

`artifact(...)` identifies a source document in Effective by its artifact ID. This tutorial uses a placeholder ID for its synthetic manual.
Citations such as `manual:p3` refer to pages in that document.

### Rate table

Save this table as `tables/BaseRate.csv` in the same folder:

```csv dwelling/tables/BaseRate.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
protection,fire,wind,theft
1,2.10,0.80,0.40
4,2.60,0.80,0.45
7,3.40,0.85,0.50
10,5.20,0.90,0.60
```

Each row starts a protection-class band: 1–3, 4–6, 7–9, or 10 and above.

## Check and package the rater

Run the compiler check:

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS rater.rsl (strict)
cost premium: 16 + 2*|Policy.items| (each list of size 1: 18, 3: 22, 10: 36)
```

The check validates the implemented algorithm. Source review and independent rating examples establish whether a rater follows its filing.

Package the rater into a bundle:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc export rater.rsl --format bundle --out dwelling.zip
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
WROTE bundle: dwelling.zip
```

The bundle contains the source, rate tables, and generated files needed to load the rater.

## Rate a policy

From the same `dwelling/` folder, load the bundle and supply the policy facts:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
from relativities import Rater

rater = Rater.load("dwelling.zip")

policy = {
    "dwelling": {
        "coverage_a": 250000,
        "protection_class": 5,
    },
    "items": [
        {"value": 1200},
        {"value": 800},
    ],
}

rater.rate(policy).to_pandas()[["status", "premium"]]
#       status premium
# 0  succeeded    1028
```

The premium is \$1,028:

| Component | Calculation | Premium |
| - | - | - |
| Fire | 250 × 2.60 | \$650.00 |
| Wind | 250 × 0.80 | \$200.00 |
| Theft | 250 × 0.45 | \$112.50 |
| Scheduled items | 1,200 × 1.5% + 800 × 1.5% | \$30.00 |
| Policy fee | Fixed amount | \$35.00 |
| Total | \$1,027.50, rounded half up | \$1,028 |

> [accordion] Inspect the calculations
>
> ```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> rater.explain(policy)
> #            kind                       name   value
> # 0         input        dwelling.coverage_a  250000
> # 1         input  dwelling.protection_class       5
> # 2         input             items[0].value    1200
> # 3         input             items[1].value     800
> # 4   calculation             base_rate.fire     2.6
> # 5   calculation             base_rate.wind     0.8
> # 6   calculation            base_rate.theft    0.45
> # 7   calculation         peril_premium.fire     650
> # 8   calculation         peril_premium.wind     200
> # 9   calculation        peril_premium.theft   112.5
> # 10  calculation            items[0].charge      18
> # 11  calculation            items[1].charge      12
> # 12  calculation                item_charge      30
> # 13  calculation                    premium    1028
> ```

While you edit a rater, `Rater.build` compiles it straight from the source file and skips the export:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
draft = Rater.build("rater.rsl")
draft.rate(policy).to_pandas()[["status", "premium"]]
#       status premium
# 0  succeeded    1028
```

`Rater.build` takes the `.rsl` file, or the folder that holds it. A source that fails to compile raises `BundleError`, and `rslc check` prints each message.
The bundle from `rslc export`, loaded with `Rater.load`, is the packaged route: one file to keep and share.

## Test an input rule

Submit a protection class outside the accepted range:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused = rater.rate({"dwelling": {"coverage_a": 250000, "protection_class": 11}})
refused.to_pandas().loc[0, ["status", "reason_code", "reason"]].to_dict()
# {'status': 'refused',
#  'reason_code': 'PROTECTION-CLASS',
#  'reason': 'dwelling.protection_class: Protection class must be 1 to 10.'}
```

The rater returns the rule’s code, the input that broke the rule, and the rule’s message.
For a rule on an input, `reason` holds the input's path, then the message.
For a rule on a record, `reason` holds the record's path, then the message, and the message alone for the root record `Policy`.
[Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) explains how to inspect the rule’s source citation.

## Next step

Read [RSL at a glance](https://docs.canary.effectiveai.app/rsl/language-tour.md) to understand the language used in this example.


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