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

# Interpolate between table rows

> Read a factor between two filed points, state the three roundings of the interpolation, and keep the key inside the table.

[Install Relativities](https://docs.canary.effectiveai.app/relativities/setup.md) before running the synthetic examples.

A filed table gives a factor at stated points, and the manual says to interpolate between them.
`interpolate!` is that table read. It takes the table, the key, and three roundings:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
limit_factor: Factor [cite: manual:p4] =
    interpolate!(LimitFactor, self.limit, ratio: (2, half_up), step: (3, half_up), result: (3, half_up)).factor,
```

The example rates a personal liability policy from the synthetic carrier Example Mutual.
Its manual files an increased-limit factor at four limits and a deductible credit at four deductibles.

## Rate a limit between two filed points

Save the rater as `liability/rater.rsl`:

> [accordion] Complete example: liability/rater.rsl
>
> ```rsl liability/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Personal Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     LimitPoint = IntValues(100000, 300000, 500000, 1000000),
>     DeductiblePoint = IntValues(500, 1000, 2500, 5000)
> }
>
> Constants = {
>     base_premium [cite: manual:p3] = 120
> }
>
> Tables = {
>     LimitFactor [cite: manual:p4] :
>         table dims (limit: LimitPoint) -> {
>             factor: Factor
>         },
>     DeductibleCredit [cite: manual:p5] :
>         table dims (deductible: DeductiblePoint) -> {
>             credit: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             limit: Num {precision: int} where self in [100000, 1000000],
>             deductible: Num {precision: int} where self in [250, 10000]
>         },
>         step rating {
>             limit_factor: Factor [cite: manual:p4] =
>                 interpolate!(LimitFactor, self.limit, ratio: (2, half_up), step: (3, half_up), result: (3, half_up)).factor,
>             deductible_credit: Factor [cite: manual:p5] =
>                 interpolate!(DeductibleCredit, max(min(self.deductible, 5000), 500), ratio: exact, step: exact, result: (3, half_up)).credit,
>             premium: Premium [cite: manual:p3] =
>                 round(base_premium * self.limit_factor * (1 - self.deductible_credit), 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

Save each table beside it, with one row for each filed point:

```csv liability/tables/LimitFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
limit,factor
100000,1.00
300000,1.25
500000,1.40
1000000,1.60
```

```csv liability/tables/DeductibleCredit.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
deductible,credit
500,0.00
1000,0.08
2500,0.17
5000,0.25
```

Rate a limit of 365,000, which lies between the filed 300,000 and 500,000:

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

rater = Rater.build("liability/rater.rsl")
print(rater.explain({"limit": 365000, "deductible": 500}))
#           kind               name   value
# 0        input              limit  365000
# 1        input         deductible     500
# 2  calculation       limit_factor     1.3
# 3  calculation  deductible_credit       0
# 4  calculation            premium     156
```

The limit factor is 1.3, between the filed 1.25 and 1.40. The annual premium is `120 × 1.3 = 156`.

## Declare the filed points

List the filed points as a values type in `Types`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
LimitPoint = IntValues(100000, 300000, 500000, 1000000),
```

Key the table by that type:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
LimitFactor [cite: manual:p4] :
    table dims (limit: LimitPoint) -> {
        factor: Factor
    },
```

* Use `IntValues` for whole points and `DecimalValues` for points with decimals.
* List two points or more.
* Put the interpolated key last in `dims`. Each key before it reads its row as `lookup!` does.
* Select a number column after the call, here `.factor`.

## State the three roundings

Between two filed points `p1` and `p2` with values `v1` and `v2`, the read does three calculations:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
ratio  = round((x - p1) / (p2 - p1))      with ratio:
step   = round(ratio × (v2 - v1))         with step:
result = round(v1 + step)                 with result:
```

Each rounding is a pair of decimal places and a [rounding mode](https://docs.canary.effectiveai.app/rsl/how-to/round-a-premium.md#choose-the-mode).
Write all three, as the manual states them. A read that omits one gets `E-LOOKUP-SYNTAX`.

Check the limit of 365,000 by hand:

| Part | Calculation | Rounding | Value |
| - | - | - | - |
| ratio | (365,000 − 300,000) ÷ 200,000 = 0.325 | `(2, half_up)` | 0.33 |
| step | 0.33 × (1.40 − 1.25) = 0.0495 | `(3, half_up)` | 0.050 |
| result | 1.25 + 0.050 = 1.300 | `(3, half_up)` | 1.300 |

At a filed point, the read returns the value in that row as filed.

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
result = rater.rate([{"limit": 300000, "deductible": 500}, {"limit": 365000, "deductible": 500}])
print(result.to_pandas()[["status", "premium"]])
#       status premium
# 0  succeeded     150
# 1  succeeded     156
```

The limit of 300,000 is a filed point, so its factor is 1.25 and its premium is 150.

## Round only the result

Some manuals round only the interpolated value. Write `exact` for `ratio:` and `step:`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
deductible_credit: Factor [cite: manual:p5] =
    interpolate!(DeductibleCredit, max(min(self.deductible, 5000), 500), ratio: exact, step: exact, result: (3, half_up)).credit,
```

`exact` keeps the part as an exact fraction. `result:` always takes decimal places and a mode.

A deductible of 2,000 lies two thirds of the way from 1,000 to 2,500:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(rater.explain({"limit": 365000, "deductible": 2000}))
#           kind               name   value
# 0        input              limit  365000
# 1        input         deductible    2000
# 2  calculation       limit_factor     1.3
# 3  calculation  deductible_credit    0.14
# 4  calculation            premium  134.16
```

The credit is `0.08 + 2/3 × (0.17 − 0.08) = 0.14`. The premium is `120 × 1.3 × (1 − 0.14) = 134.16`.

## Keep the key inside the filed points

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS liability/rater.rsl (strict)
cost premium: 5 (each list of size 1: 5, 3: 5, 10: 5)
```

A passing check proves two facts for every request the rater accepts:

* The key lies between the first filed point and the last.
* The table has a row for each filed point. A missing row gets
  [`E-LOOKUP-GAP`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/calculations#e-lookup-gap).

The manual decides what happens outside the filed points. Write its rule in one of two ways.

**Accept only the filed range.** The `limit` input states the range of the filed points:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
limit: Num {precision: int} where self in [100000, 1000000],
```

**Hold the end values.** The `deductible` input accepts 250 to 10,000, and the manual holds the end credits flat.
The read clamps its key to the first and last points:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
interpolate!(DeductibleCredit, max(min(self.deductible, 5000), 500), ratio: exact, step: exact, result: (3, half_up)).credit,
```

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
result = rater.rate([{"limit": 365000, "deductible": 250}, {"limit": 365000, "deductible": 10000}])
print(result.to_pandas()[["status", "premium"]])
#       status premium
# 0  succeeded     156
# 1  succeeded     117
```

A deductible of 250 takes the credit filed at 500, and 10,000 takes the credit filed at 5,000.

### A key outside the filed points

The rater `wide/rater.rsl` accepts limits up to 2,000,000 and reads the same table:

> [accordion] Complete example: wide/rater.rsl and its table
>
> ```rsl wide/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Personal Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     LimitPoint = IntValues(100000, 300000, 500000, 1000000)
> }
>
> Constants = {
>     base_premium [cite: manual:p3] = 120
> }
>
> Tables = {
>     LimitFactor [cite: manual:p4] :
>         table dims (limit: LimitPoint) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             limit: Num {precision: int} where self in [100000, 2000000]
>         },
>         step rating {
>             limit_factor: Factor [cite: manual:p4] =
>                 interpolate!(LimitFactor, self.limit, ratio: (2, half_up), step: (3, half_up), result: (3, half_up)).factor,
>             premium: Premium [cite: manual:p3] = round(base_premium * self.limit_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv wide/tables/LimitFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> limit,factor
> 100000,1.00
> 300000,1.25
> 500000,1.40
> 1000000,1.60
> ```

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

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

The message names the range of the key and the range of the filed points.
Narrow the input, clamp the key, or write the filed rule for larger limits as its own calculation.
[`E-INTERP-RANGE`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/calculations#e-interp-range) shows the corrected rater.

## Interpolate on filed points only

The rater `bands/rater.rsl` declares the limits as bands and interpolates on them:

> [accordion] Complete example: bands/rater.rsl and its table
>
> ```rsl bands/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Personal Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     LimitBand = IntBands(100000, 300000, 500000, 1000000..)
> }
>
> Constants = {
>     base_premium [cite: manual:p3] = 120
> }
>
> Tables = {
>     LimitFactor [cite: manual:p4] :
>         table dims (limit: LimitBand) -> {
>             factor: Factor
>         }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             limit: Num {precision: int} where self in [100000, 1000000]
>         },
>         step rating {
>             limit_factor: Factor [cite: manual:p4] =
>                 interpolate!(LimitFactor, self.limit, ratio: (2, half_up), step: (3, half_up), result: (3, half_up)).factor,
>             premium: Premium [cite: manual:p3] = round(base_premium * self.limit_factor, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```csv bands/tables/LimitFactor.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> limit,factor
> 100000,1.00
> 300000,1.25
> 500000,1.40
> 1000000,1.60
> ```

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bands/rater.rsl (strict)
error: E-INTERP-KEY bands/rater.rsl:38:17: `interpolate!` reads filed points, but dim `limit` of `LimitFactor` is the band type `LimitBand`: a band row is one factor for a whole interval, and `lookup!` on it would take the lower point's factor. Declare the filed points as a values type, for example `limitPoint = IntValues(p1, p2, …, pn)` (or `DecimalValues`), and key `LimitFactor` by it. A filed interval with one factor stays a band read by `lookup!`
```

A band gives one factor to a whole interval, so `lookup!` reads it.
To interpolate, declare the points with `IntValues` or `DecimalValues`, as `liability/rater.rsl` does.
[`E-INTERP-KEY`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/tables#e-interp-key) explains the message.

## Related

* [Use a filed rate table](https://docs.canary.effectiveai.app/rsl/how-to/read-a-filed-table.md) declares tables, CSV files and `lookup!`.
* [Round a premium](https://docs.canary.effectiveai.app/rsl/how-to/round-a-premium.md) describes each rounding mode.
* [`interpolate`](https://docs.canary.effectiveai.app/rsl/reference/functions#interpolate) in the function reference gives the call's forms.


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