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

# Functions

> Every RSL function, with its call forms, its rules and an example.

*Generated from the `rslc` compiler.*

RSL has a fixed set of functions. A call to any other name gets `E-UNRESOLVED`.
`lookup!` and `interpolate!` end in `!`: the check proves that every call finds a value.

| Function | Purpose |
| - | - |
| [`lookup`](#lookup) | Reads one row of a table by its keys. The check proves that every call finds a row. |
| [`interpolate`](#interpolate) | Reads a value between two rows of a table by linear interpolation, with each rounding stated. |
| [`locate`](#locate) | Finds the cell of a geography that holds a point. |
| [`round`](#round) | Rounds a number to `scale` decimal places with the stated rounding mode. |
| [`root`](#root) | Takes the `n`th root of a number, rounded to `scale` decimal places. |
| [`pow`](#pow) | Raises a number to a whole power exactly, or to a fractional power with a stated rounding. |
| [`abs`](#abs) | Gives the size of a number, with its sign dropped. |
| [`floor`](#floor) | Gives the greatest whole number at or below a number. |
| [`ceil`](#ceil) | Gives the least whole number at or above a number. |
| [`min`](#min) | Gives the least of several values, or of one value for each item. |
| [`max`](#max) | Gives the greatest of several values, or of one value for each item. |
| [`sum`](#sum) | Adds one value for each item of a list or each member of an enumeration. |
| [`product`](#product) | Multiplies one value for each item of a list or each member of an enumeration. |
| [`avg`](#avg) | Gives the mean of several values, or of one value for each item. |
| [`len`](#len) | Gives the number of items in a list. |
| [`count`](#count) | Counts the items that meet a condition. |
| [`exists`](#exists) | Is true when one item or more meets a condition. |
| [`all`](#all) | Is true when every item meets a condition. |
| [`find`](#find) | Gives the first item that meets a condition. |
| [`filter`](#filter) | Keeps the items that meet a condition. |
| [`argmax`](#argmax) | Gives the item with the greatest score, with each tie-breaker stated. |
| [`argmin`](#argmin) | Gives the item with the least score, with each tie-breaker stated. |
| [`topn`](#topn) | Keeps the `n` items with the greatest scores. |
| [`rank`](#rank) | Gives the position of one item when the list is ordered by a score. |
| [`index`](#index) | Gives the position of a record in a plain list, counted from 0. |
| [`unique`](#unique) | States that a key, or each value, is distinct across a list. |
| [`diff_days`](#diff_days) | Counts the days from one date to a later date. |
| [`diff_months`](#diff_months) | Counts the calendar months from one date to a later date. The day of the month is ignored. |
| [`diff_years`](#diff_years) | Counts the full years from one date to a later date, as an age in years is counted. |
| [`add_days`](#add_days) | Shifts a date by a whole number of days. |
| [`add_months`](#add_months) | Shifts a date by a whole number of months. A day past the month's end becomes its last day. |
| [`add_years`](#add_years) | Shifts a date by a whole number of years. |
| [`year`](#year) | Gives the year of a date. |
| [`month`](#month) | Gives the month of a date, from 1 to 12. |
| [`Some`](#some) | Makes a present `Option` from a value. |
| [`select`](#select) | Gives a value between a filed low and a filed high, at a chosen position. |
| [`nl`](#nl) | Holds the place of a calculation that is still in plain words. A strict check fails while one remains. |

## Tables and geographies

### `lookup`

Reads one row of a table by its keys. The check proves that every call finds a row.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
lookup!(Table, key, …).column
lookup!(Table, key, …).column[member]
```

**Result:** The column's type.

Use `lookup!` to read a filed value from a table: a base rate, a factor, a territory code. The check proves that every key the call can pass has a row, so each rating finds its value.

**Rules**

* The first argument names a table from `Tables`. Each later argument is one key, in the order of the table's `dims`.
* Write `.column` after the call to pick the output column. For a map column, add the member in brackets: `.rate[p]`.
* A key for an enumeration, `Code` or `Bool` dimension has that type. A key for a band or value-set dimension is a number.
* A band dimension gives the row of the band that holds the key. A value-set or `Code` dimension gives the row with the same value.
* A table with an `otherwise` row gives that row for every key that the filed rows leave out.
* The `!` is part of the name. A call spelled `lookup(…)` gets `E-LOOKUP-SYNTAX`.
* A number key that can fall outside its dimension gets `E-KEY-RANGE`. Bound the input, clamp the key with `max` or `min`, or add an `otherwise` row.
* A key combination that lacks a row gets `E-LOOKUP-GAP`. Add the filed row, narrow the key, or add an `otherwise` row.
* A key of type `Option` gets `E-TYPE`. Open it with `match` first, and pass the value inside.
* A call that passes fewer or more keys than the table has `dims` gets `E-TYPE`. An unknown table or column gets `E-UNRESOLVED`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
base_rate: Rate = lookup!(BaseRate, self.territory, self.construction).rate
```

The read takes two enumeration keys. A frame home in the suburban territory gives a base rate of 690.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
home_age_factor: Factor = lookup!(HomeAgeFactor, self.home_age).factor,
deductible_factor: Factor = lookup!(DeductibleFactor, self.deductible).factor,
zip_factor: Factor = lookup!(ZipFactor, self.zip).factor
```

A home aged 25 falls in the band that starts at 25 and gives 1.12. A deductible of 2,500 is a listed value and gives 0.85. A ZIP code that the table leaves out gives the `otherwise` factor of 1.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
base_rate[p: Peril]: Rate =
    lookup!(BaseRate, self.dwelling.protection_class).rate[p],
```

The column `rate` holds one rate for each peril, and `[p]` reads the rate of peril `p`. Protection class 5 falls in the band that starts at 4, so the fire rate is 2.60.

### `interpolate`

Reads a value between two rows of a table by linear interpolation, with each rounding stated.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
interpolate!(Table, key, …, x, ratio: (scale, mode), step: (scale, mode), result: (scale, mode)).column
interpolate!(Table, key, …, x, ratio: exact, step: exact, result: (scale, mode)).column
```

**Result:** The column's type.

Use `interpolate!` when a filing prints factors at set points and reads values between them on a straight line. The call states each rounding that the filing states. The check proves that the key stays between the first and last filed points.

**Rules**

* The last dimension of the table holds the filed points as a value set, such as `IntValues(100000, 200000, 300000)`. Only the last key interpolates. Earlier keys select rows as they do in `lookup!`.
* At a filed point, the result is that row's cell, exactly as filed. The three roundings apply only between points.
* Between two points, the ratio is the key's distance from the lower point divided by the gap between the points. The step is the ratio times the difference of the two cells. The result is the lower cell plus the step.
* `ratio:`, `step:` and `result:` each take `(scale, mode)` and round that quantity as `round` does. Write all three, in any order.
* `ratio:` and `step:` also take `exact`, for a filing that rounds only the result. `result:` always takes a scale and a mode.
* A key that can fall outside the filed points gets `E-INTERP-RANGE`. Bound the input, or write the filed clamp around the key, such as `max(min(x, 750000), 100000)`.
* A last dimension that is a band or a `Code`, and a table with an `otherwise` row, get `E-INTERP-KEY`.
* A point that the key can reach and that lacks a row gets `E-LOOKUP-GAP`.
* A missing or repeated rounding gets `E-LOOKUP-SYNTAX`, and so does a call spelled `interpolate(…)`.
* `result: exact` gets `E-ROUND`, and so does a `ratio:` scale below 0.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
amount_factor: Factor [cite: manual:p5] =
    interpolate!(AmountFactor, self.coverage_a, ratio: (4, half_up), step: (4, half_up), result: (3, half_up)).factor,
```

The table files 1.000 at 200,000 and 1.270 at 300,000. A Coverage A amount of 260,000 gives a ratio of 0.6000, a step of 0.1620 and a factor of 1.162.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
hail_factor: Factor [cite: manual:p6] =
    interpolate!(HailFactor, self.territory, self.coverage_a, ratio: exact, step: exact, result: (3, half_up)).factor
```

This table has a territory key before the amount, and its filing rounds only the result. The rural rows file 1.10 at 200,000 and 1.14 at 300,000, so an amount of 233,333 gives 1.113.

### `locate`

Finds the cell of a geography that holds a point.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
locate(Geography, latitude, longitude)
```

**Result:** `Option` of the cell's key.

Use `locate` when a filing assigns a territory or a factor from the latitude and longitude of the risk. It finds the filed cell of a geography that holds the point. A cell that it finds is always a row of the cell table.

**Rules**

* The first argument names a geography from `Geographies`. The second is the latitude and the third is the longitude, as numbers in decimal degrees.
* The result is an `Option` of the cell table's key. `Some(key)` names a filed row, so `lookup!` with that key always finds it.
* The result is `None` for a point outside every filed cell. Handle both cases with `match`, and state what the filing does for such a point.
* A point on a line between cells belongs to each cell that touches it. With `on_line: least_key`, the cell with the least key wins.
* A first argument that is other than a declared geography gets `E-GEOGRAPHY`. So does a call with fewer than three arguments, and a coordinate that is other than a number.
* The check places the point of each table row in its cell. It fails when two rows share a cell, or when a row's point lies on a cell line.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
WildfireGrid [cite: manual:p3] = degree_grid {
    cell: 0.01,
    origin_latitude: 0,
    origin_longitude: 0,
    cells: WildfireCell,
    latitude: latitude,
    longitude: longitude,
    on_line: least_key
}
```

The geography lays a grid of 0.01-degree cells over the rows of the table `WildfireCell`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rated_cell: Option(CellId) =
    match self.surveyed_cell {
        Some(c) => Some(c),
        None => locate(WildfireGrid, self.latitude, self.longitude)
    },
wildfire_factor: Factor =
    match self.rated_cell {
        Some(c) => lookup!(WildfireCell, c).factor,
        None => unmapped_factor
    }
```

A home at latitude 33.4567 and longitude -112.0712 lies in cell `W101` and takes its factor of 1.10. A home at latitude 33.5 lies outside the four filed cells and takes the unmapped factor of 1.5.

## Numbers and rounding

### `round`

Rounds a number to `scale` decimal places with the stated rounding mode.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
round(x, scale, mode)
```

**Result:** The type of `x`.

Use `round` wherever the filing states a rounding: a premium to the cent, a factor to three places. Every other operation keeps exact values, so a value changes only where you write `round`.

**Rules**

* `scale` is a whole-number literal from -1000 to 1000: the number of decimal places. A scale of 2 rounds to cents, 0 to whole units and -2 to hundreds.
* `mode` is one of seven names. Every call states its mode.
* `half_up` rounds to the nearest value, and a tie goes away from zero: 2.5 gives 3 and -2.5 gives -3.
* `half_down` rounds to the nearest value, and a tie goes toward zero: 2.5 gives 2 and -2.5 gives -2.
* `half_even` rounds to the nearest value, and a tie goes to the even digit: 2.5 gives 2 and 3.5 gives 4.
* `up` rounds away from zero: 2.4 gives 3 and -2.4 gives -3.
* `down` rounds toward zero: 2.4 gives 2 and -2.4 gives -2.
* `ceiling` rounds toward positive infinity: 2.4 gives 3 and -2.4 gives -2.
* `floor` rounds toward negative infinity: 2.4 gives 2 and -2.4 gives -3.
* Each rating outcome passes through a `round` in a calculation that cites the rounding rule. An outcome that lacks one gets `E-OUTCOME-ROUND`.
* A calculation that rounds carries a citation, on itself or on its step. One that lacks both gets `E-PROVENANCE`.
* A call with two arguments, an unknown mode, or a scale that is other than a whole-number literal gets `E-ROUND`.

**Example**

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

The premium is the payroll in thousands times the rate of 2, rounded to the cent. A payroll of 1,234,567 gives 2,469.134 before rounding and 2,469.13 after it.

### `root`

Takes the `n`th root of a number, rounded to `scale` decimal places.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
root(x, n, scale, mode)
```

**Result:** The type of `x`.

Use `root` for a filed square root or higher root, such as a credibility factor. Most roots have endless decimals, so the call states a scale and a mode. The result is the true root, rounded once.

**Rules**

* `n` is the degree, a whole number of 1 or more: 2 for a square root, 3 for a cube root.
* `scale` and `mode` follow the rules of `round`. The square root of 2 at scale 3 gives 1.414 with `half_up` and 1.415 with `up`.
* An even degree needs a value of zero or more. The check proves it from the input ranges, and a value that can be negative gets `E-ROOT`. Clamp the value with `max(x, 0)`.
* An odd degree takes any value: the cube root of -8 is -2.
* A degree that is a calculation must be a proven whole number of 1 or more, or the call gets `E-ROOT`.
* A literal degree below 1, a fractional degree, an unknown mode, or a call with other than four arguments gets `E-ROOT`.
* A calculation that calls `root` rounds, so it carries a citation on itself or on its step.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
credibility: Factor [cite: manual:p4] =
    min(root(self.claim_count / full_credibility_claims, 2, 2, half_up), 1),
```

Credibility is the square root of the claim count over the full-credibility standard of 400, to two places, capped at 1. A count of 100 gives 0.50, and a count of 900 gives 1.

### `pow`

Raises a number to a whole power exactly, or to a fractional power with a stated rounding.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
pow(x, n)
pow(x, exponent, scale, mode)
```

**Result:** The type of `x`.

Use `pow` to raise a number to a power: a trend compounded over years, or a filed curve with a fractional exponent. A whole power is exact. A fractional power states its rounding, and the result is the true power, rounded once.

**Rules**

* `pow(x, n)` takes a whole-number exponent and gives the exact power. The exponent is a literal or a calculation that the check proves whole, such as an input with `precision: int`.
* An exponent of 0 gives 1.
* A negative exponent needs a base other than zero. The check proves it from the input ranges, and a base that can be zero gets `E-POW-EXPONENT`.
* An exponent that can hold a fraction gets `E-POW-EXPONENT` in the two-argument form. So does a literal exponent above 131072.
* `pow(x, exponent, scale, mode)` takes a fractional literal exponent, such as `-0.15` or `1.5`. It rounds the power to `scale` with `mode`, as `round` does.
* As a fraction in lowest terms, the exponent has a numerator and a denominator of at most 1000. `0.125` is 1/8 and passes. `0.123456789` gets `E-POW-EXPONENT`.
* An exponent with an even denominator, such as `0.5`, needs a base of zero or more. A base that can be negative gets `E-ROOT`.
* A whole exponent in the four-argument form gets `E-POW-EXPONENT`. Write `round(pow(x, n), scale, mode)`.
* An exponent that is a calculation gets `E-POW-EXPONENT` in the four-argument form.
* A calculation that calls the four-argument form rounds, so it carries a citation on itself or on its step.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
trend_factor: Factor = pow(annual_trend, self.trend_years)
```

The annual trend of 1.03 compounds over a whole number of years. Two years give exactly 1.0609.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
size_factor: Factor [cite: manual:p3] = pow(self.revenue / base_revenue, -0.15, 4, half_up),
```

The size factor follows a filed curve with the exponent -0.15, to four places. Revenue of 2,500,000 is 2.5 times the base and gives 0.8716.

### `abs`

Gives the size of a number, with its sign dropped.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
abs(x)
```

**Result:** `Num`.

Use `abs` when a rule reads the size of a difference and ignores its direction, such as the distance of a modification from 1. The result is exact.

**Rules**

* `abs` takes one number and gives its distance from zero. `abs(-2.5)` and `abs(2.5)` both give 2.5.
* The result is zero or more, and the check uses that fact in later proofs.
* A call with zero arguments gets `E-TYPE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
mod_swing: Num = abs(self.experience_mod - 1),
applied_mod: Factor = if self.mod_swing <= mod_tolerance then 1 else self.experience_mod
```

A modification within 0.05 of 1 rates at 1. A modification of 0.96 has a swing of 0.04 and rates at 1. A modification of 1.2 has a swing of 0.2 and applies as it is.

### `floor`

Gives the greatest whole number at or below a number.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
floor(x)
```

**Result:** `Num`.

Use `floor` to count whole units and drop the rest: full years in a number of months, full thousands in a limit. The result is exact.

**Rules**

* `floor` takes one number and gives the greatest whole number at or below it. 3.9 gives 3, and 3 gives 3.
* A negative number moves away from zero: -2.5 gives -3.
* `floor(x)` equals `round(x, 0, floor)`.
* The check knows that the result is whole. A calculation typed `Num {precision: int}` accepts it as it is.
* A call with zero arguments gets `E-TYPE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
claim_free_years: Num = min(floor(self.claim_free_months / 12), 5),
claim_free_discount: Factor = 1 - claim_free_credit * self.claim_free_years,
```

The discount counts full claim-free years, up to 5. Forty months hold 3 full years and give a discount factor of 0.94.

### `ceil`

Gives the least whole number at or above a number.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
ceil(x)
```

**Result:** `Num`.

Use `ceil` when a filing charges for each unit "or part of a unit": each \$1,000 of revenue, each started day. The result is exact.

**Rules**

* `ceil` takes one number and gives the least whole number at or above it. 3.1 gives 4, and 3 gives 3.
* A negative number moves toward zero: -2.5 gives -2.
* `ceil(x)` equals `round(x, 0, ceiling)`.
* The check knows that the result is whole. A calculation typed `Num {precision: int}` accepts it as it is.
* A call with zero arguments gets `E-TYPE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
exposure_units: Num = ceil(self.revenue / 1000),
```

The rate applies to each \$1,000 of revenue or part of it. Revenue of 1,250,000.00 gives 1,250 units, and revenue of 1,250,000.01 gives 1,251.

### `min`

Gives the least of several values, or of one value for each item.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
min(a, b, …)
min(list as v, value)
min(Enum as m, value)
```

**Result:** The type of the values.

Use `min` to cap a value at a filed maximum, or to find the least value across a list or an enumeration. The result is exact: it is one of the values, unchanged.

**Rules**

* `min(a, b, …)` takes one or more numbers and gives the least.
* `min(list as v, value)` computes `value` once for each item of the list, with `v` naming the item, and gives the least.
* `min(Enum as m, value)` computes `value` once for each member of an enumeration, or of a refined subset of one, and gives the least.
* Every value is a number.
* The list form needs a list that holds one item or more. A list that can be empty gets `E-EMPTY-FOLD`.
* To satisfy that rule, bound the list input with `where len(self) in [1, 5]`, or guard the call with `if len(list) > 0 then … else …`.
* `by` and `round_each` belong to `sum` and `product`. On `min` they get `E-FOLD-KEY`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
capped_modifier: Factor = max(min(self.schedule_modifier, highest_modifier), lowest_modifier),
```

The inner call caps the schedule modifier at the highest filed value, 1.25.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
oldest_year: Num = min(self.locations as l, l.year_built),
```

The policy reads the earliest year built across its locations. Buildings from 1965 and 1998 give 1965.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
protection_credit: Factor = min(Protection as k, self.credit[k]),
```

Each location takes the better of its two protection credits, and the credits stay separate. A sprinkler credit of 0.85 and an alarm credit of 0.95 give 0.85.

### `max`

Gives the greatest of several values, or of one value for each item.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
max(a, b, …)
max(list as v, value)
max(Enum as m, value)
```

**Result:** The type of the values.

Use `max` to hold a value at or above a filed minimum, or to find the greatest value across a list or an enumeration. The result is exact: it is one of the values, unchanged.

**Rules**

* `max(a, b, …)` takes one or more numbers and gives the greatest.
* `max(list as v, value)` computes `value` once for each item of the list, with `v` naming the item, and gives the greatest.
* `max(Enum as m, value)` computes `value` once for each member of an enumeration, or of a refined subset of one, and gives the greatest.
* Every value is a number.
* The list form needs a list that holds one item or more. A list that can be empty gets `E-EMPTY-FOLD`.
* To satisfy that rule, bound the list input with `where len(self) in [1, 5]`, or guard the call with `if len(list) > 0 then … else …`.
* `by` and `round_each` belong to `sum` and `product`. On `max` they get `E-FOLD-KEY`.
* To read another field of the item with the greatest value, use `argmax`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
premium: Premium [cite: manual:p6] = round(max(self.modified_premium, minimum_premium), 0, half_up)
```

The policy pays the modified premium or the minimum premium of 500, whichever is greater. A modified premium of 226.50 gives 500.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
largest_limit: Num = max(self.locations as l, l.largest_limit),
```

The policy reads the largest limit across its locations. The input rule on `locations` asks for one location or more, so the call passes the check.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
largest_limit: Num = max(Coverage as c, self.limit[c])
```

Each location reads the largest of its three coverage limits. Limits of 400,000, 150,000 and 100,000 give 400,000.

### `sum`

Adds one value for each item of a list or each member of an enumeration.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
sum(list as v, value)
sum(Enum as m, value)
sum(list as v by v.key, value, round_each: (scale, mode))
```

**Result:** The type of the values.

Use `sum` to add one value for each item of a list or each member of an enumeration: vehicle premiums into a policy premium, coverage premiums into a location premium. The plain forms are exact. The stepwise form rounds after each addition, as some filings state.

**Rules**

* `sum(list as v, value)` computes `value` once for each item, with `v` naming the item, and adds the results. An empty list gives 0.
* `sum(Enum as m, value)` computes `value` once for each member of an enumeration, or of a refined subset of one.
* The stepwise form `sum(list as v by v.key, value, round_each: (scale, mode))` adds the items in ascending order of the key. It rounds the running total after each item, as `round` does.
* The stepwise form reads a list. An enumeration takes the plain form.
* The key is a number or `Date` input of the item, read as `v.field`. Any other key gets `E-FOLD-KEY`.
* The key is distinct across the list. Rating refuses a request that repeats a key value as invalid input.
* `by` and `round_each` go together. One of them alone gets `E-FOLD-KEY`.
* A calculation with `round_each` rounds, so it carries a citation on itself or on its step. One that lacks both gets `E-PROVENANCE`.
* To add separate values, write `a + b`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
vehicle_premium: Premium = sum(self.vehicles as v, v.premium),
```

The policy adds the premium of each vehicle. Vehicle premiums of 1,260.00, 696.00 and 1,663.20 give 3,619.20.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
coverage_total: Premium = sum(Coverage as c, self.coverage_premium[c]),
```

Each location adds its premium for each member of `Coverage`. Premiums of 480, 375 and 80 give 935.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
location_premium: Premium [cite: manual:p6] =
    sum(self.locations as l by l.number, l.premium, round_each: (0, half_up)),
```

The policy adds the locations in order of their number and rounds to the dollar after each one. Location premiums of 100.50 and 200.50 give 101, then 302. The exact sum is 301.

### `product`

Multiplies one value for each item of a list or each member of an enumeration.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
product(a, b, …)
product(list as v, value)
product(Enum as m, value)
product(list as v by v.key, value, round_each: (scale, mode))
```

**Result:** The type of the values.

Use `product` to multiply one factor for each item of a list or each member of an enumeration. The plain forms are exact. The stepwise form rounds after each multiplication, as many filings state for a chain of factors.

**Rules**

* `product(list as v, value)` computes `value` once for each item, with `v` naming the item, and multiplies the results. An empty list gives 1.
* `product(Enum as m, value)` computes `value` once for each member of an enumeration, or of a refined subset of one.
* The stepwise form `product(list as v by v.key, value, round_each: (scale, mode))` multiplies the items in ascending order of the key. It rounds the running product after each item, as `round` does.
* The key alone sets the order of a stepwise product, so the order of the request leaves the result unchanged.
* The stepwise form reads a list. An enumeration takes the plain form.
* The key is a number or `Date` input of the item, read as `v.field`. Any other key gets `E-FOLD-KEY`.
* The key is distinct across the list. Rating refuses a request that repeats a key value as invalid input.
* `by` and `round_each` go together. One of them alone gets `E-FOLD-KEY`.
* A calculation with `round_each` rounds, so it carries a citation on itself or on its step. One that lacks both gets `E-PROVENANCE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
endorsement_factor: Factor = product(self.endorsements as e, e.factor)
```

The policy multiplies the factor of each endorsement. Factors of 1.05 and 1.1 give exactly 1.155, and a policy with zero endorsements gives 1.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
account_factor: Factor = product(Adjustment as k, self.adjustment[k])
```

The account factor multiplies one adjustment for each member of `Adjustment`. A large-limit factor of 0.9 and an old-building factor of 1.1 give 0.99.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
schedule_modifier: Factor [cite: manual:p5] =
    product(self.modifiers as m by m.number, m.factor, round_each: (3, half_up)),
```

The filing rounds the schedule modifier to three places after each factor. Modifiers numbered 1, 2 and 3 with factors 0.875, 0.925 and 0.955 give 0.875, then 0.809, then 0.773.

### `avg`

Gives the mean of several values, or of one value for each item.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
avg(a, b, …)
avg(list as v, value)
avg(Enum as m, value)
```

**Result:** The type of the values.

Use `avg` for the mean of one value across the items of a list, such as the average age of the rated drivers. The mean is exact: the sum divided by the number of items.

**Rules**

* `avg(list as v, value)` computes `value` once for each item, with `v` naming the item, and gives the mean.
* The result keeps every decimal. Ages of 45, 40 and 22 give exactly 107/3, so round the mean where the filing states a rounding.
* The list holds one item or more. A list that can be empty gets `E-EMPTY-FOLD`.
* To satisfy that rule, bound the list input with `where len(self) in [1, 6]`, or guard the call with `if len(list) > 0 then … else …`.
* `by` and `round_each` belong to `sum` and `product`. On `avg` they get `E-FOLD-KEY`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rated_drivers: List(Driver) = filter(self.drivers as d, not d.excluded),
average_age: Num =
    if len(self.rated_drivers) > 0
    then avg(self.rated_drivers as d, d.age)
    else 0,
mature_discount: Factor = if self.average_age >= 50 then mature_factor else 1,
```

The filtered list can be empty, so the guard on `len` comes first. Rated drivers aged 62 and 58 give an average age of 60 and the mature discount of 0.97.

## Lists

### `len`

Gives the number of items in a list.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
len(list)
```

**Result:** `Num`.

Use `len` for the number of items in a list: vehicles on a policy, drivers that a filter keeps. It also bounds the size of a list input, and the check uses that bound in its proofs.

**Rules**

* `len(list)` takes a list input or a calculated list, such as the result of `filter` or `topn`. An empty list gives 0.
* In a rule on a list input, `self` is the list: `where len(self) in [1, 4]`. Rating refuses a request whose list falls outside the bound.
* A bound that starts at 1 or more proves the list holds an item. `avg`, `min` and `max` over that list then pass the check.
* A guard such as `if len(list) > 0 then … else …` gives the same proof for a calculated list.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
vehicles: List(Vehicle) keyed by number
    where len(self) in [1, 4] [code: "VEHICLE-COUNT", message: "A policy rates one to four vehicles.", cite: manual:p1],
```

The rule admits one to four vehicles. Rating refuses a request with five vehicles and gives the code `VEHICLE-COUNT`.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
multi_car_discount: Factor = if len(self.vehicles) >= 2 then multi_car_factor else 1,
```

A policy with two or more vehicles takes the multi-car factor of 0.95.

### `count`

Counts the items that meet a condition.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
count(list as v, condition)
count(Enum as m, condition)
```

**Result:** `Num`.

Use `count` for the number of items that meet a condition: drivers under 25, accidents inside a look-back window. The result is a whole number.

**Rules**

* `count(list as v, condition)` tests the condition once for each item, with `v` naming the item. It gives the number of items for which the condition is true.
* `count(Enum as m, condition)` tests the condition once for each member of an enumeration.
* An empty list gives 0.
* For the size of a whole list, use `len`.
* `by` belongs to a stepwise `sum` or `product`. On `count` it gets `E-FOLD-KEY`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
youthful_count: Num = count(self.drivers as d, d.age < 25),
youthful_charge: Premium = youthful_surcharge * self.youthful_count,
```

Each driver under 25 adds a charge of 75. A household with drivers aged 45 and 19 has a count of 1 and a charge of 75.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
chargeable_accidents: Num =
    count(self.accidents as a, a.occurred >= add_months(self.effective_date, -36)),
```

The condition reads a field of the item and a field of the policy. For an effective date of 2026-06-01, accidents on 2023-06-01 and 2023-05-31 give a count of 1.

### `exists`

Is true when one item or more meets a condition.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
exists(list as v, condition)
exists(Enum as m, condition)
```

**Result:** `Bool`.

Use `exists` to ask whether one item or more meets a condition: any driver with a filing, any vehicle used for business. The result is a `Bool`.

**Rules**

* `exists(list as v, condition)` tests the condition for the items of the list, with `v` naming the item. It is true when the condition holds for one item or more.
* `exists(Enum as m, condition)` tests the condition for the members of an enumeration.
* An empty list gives false.
* `exists` is the opposite of `all` with the opposite condition. `not exists(list as v, c)` is true when every item fails `c`.
* `by` belongs to a stepwise `sum` or `product`. On `exists` it gets `E-FOLD-KEY`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
has_filing: Bool = exists(self.drivers as d, d.filing),
filing_charge: Premium = if self.has_filing then filing_fee else 0
```

One filing fee of 25 applies when any driver needs a filing. Two drivers with filings still give one fee of 25.

### `all`

Is true when every item meets a condition.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
all(list as v, condition)
all(Enum as m, condition)
```

**Result:** `Bool`.

Use `all` to ask whether every item meets a condition: every driver free of points, every location sprinklered. It also states an input rule that each item of a list must meet. The result is a `Bool`.

**Rules**

* `all(list as v, condition)` tests the condition for the items of the list, with `v` naming the item. It is true when the condition holds for every item.
* `all(Enum as m, condition)` tests the condition for the members of an enumeration.
* An empty list gives true.
* In a rule on a list input, `self` is the list: `where all(self as d, d.points <= 6)`. With a `code` and a `message`, rating refuses a request that breaks the rule and gives that code.
* A call that lacks the `as` name or the condition gets `E-TYPE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
claim_free: Bool = all(self.drivers as d, d.points = 0),
claim_free_discount: Factor = if self.claim_free then claim_free_factor else 1,
```

The household is claim free when every driver has zero points. Two drivers with zero points give the discount factor of 0.9, and one point on either driver gives 1.

### `find`

Gives the first item that meets a condition.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
find(list as v, condition)
```

**Result:** `Option` of the record.

Use `find` to pick the one item that meets a condition, such as the driver whose rank matches a vehicle's. The answer depends on the list's key and the data, and the order of the request leaves it unchanged.

**Rules**

* `find(list as v, condition)` gives an `Option` of the item: `Some(item)` for the first item that meets the condition, and `None` when zero items meet it.
* "First" follows the list's `keyed by` key, in ascending order. Over a list of values, it follows the values themselves.
* A list of records needs a key: declare the input as `List(Driver) keyed by number`. A list that lacks a key gets `E-SELECT-TIE`.
* Open the result with `match`, and state what the filing does in the `None` case.
* `.field` after the call reads one field of the item. The result is an `Option` of that field.
* `find` takes the list and the condition only. `by` gets `E-FOLD-KEY`, and `then` gets `E-SELECT-TIE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
driver_factor: Factor =
    match find(Policy.drivers as d, d.rating_rank = self.rating_rank) {
        Some(d) => d.class_factor,
        None => Policy.extra_vehicle_factor
    }
```

Each vehicle takes the class factor of the driver that holds the same rank. With two drivers and three vehicles, the vehicle ranked third gets `None` and takes the extra-vehicle factor.

### `filter`

Keeps the items that meet a condition.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
filter(list as v, condition)
```

**Result:** A list of the record.

Use `filter` to keep the items of a list that meet a condition, and then count them, add them or select from them. The result is a list of the same items.

**Rules**

* `filter(list as v, condition)` tests the condition once for each item, with `v` naming the item. It keeps the items for which the condition is true.
* Type a calculation that holds the result as a list of the record, such as `List(Driver)`.
* Pass the result to any function that reads a list: `len`, `sum`, `avg`, `topn`, or `filter` again.
* The result can be empty. `avg`, `min` and `max` over it need a guard, such as `if len(kept) > 0 then … else …`, or they get `E-EMPTY-FOLD`.
* An item keeps its place in the input list. `index(v)` on an item of the result gives its position in the list as sent.
* `by` belongs to a stepwise `sum` or `product`. On `filter` it gets `E-FOLD-KEY`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rated_drivers: List(Driver) = filter(self.drivers as d, not d.excluded),
average_age: Num =
    if len(self.rated_drivers) > 0
    then avg(self.rated_drivers as d, d.age)
    else 0,
```

The average age reads only the drivers that the policy rates. With drivers aged 45, 40 and 22 rated and a driver aged 60 excluded, the average is 107/3.

### `argmax`

Gives the item with the greatest score, with each tie-breaker stated.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
argmax(list as v, score, then [cite: …]: priority)
```

**Result:** `Option` of the record.

Use `argmax` to pick the item with the greatest score and then read its other fields: the class factor of the driver with the most points. Every tie has a stated winner, so the order of the request leaves the answer unchanged.

**Rules**

* `argmax(list as v, score)` computes the score once for each item, with `v` naming the item. It gives an `Option` of the item: `Some(item)`, or `None` for an empty list.
* The score is a number.
* `then [cite: source:region]: priority` states a filed tie rule. Among items with the greatest score, the item with the greatest priority wins.
* The priority carries the citation of the rule that states it. A `then` that lacks one gets `E-CITATION`.
* Among items that are still tied, the item with the least `keyed by` key wins.
* A list of records needs a key: declare the input as `List(Driver) keyed by number`. A list that lacks a key gets `E-SELECT-TIE`.
* A priority written as a plain third argument gets `E-SELECT-TIE`. Write it with `then`.
* Open the result with `match`. To read only the greatest score, use `max`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
extra_vehicle_factor: Factor =
    match argmax(self.drivers as d, d.points, then [cite: manual:p4]: d.class_factor) {
        Some(d) => d.class_factor,
        None => 1
    }
```

A vehicle beyond the number of drivers rates with the driver that has the most points. Drivers 2 and 3 both have 5 points, with class factors of 1.75 and 2.1. The filed tie rule picks the higher class factor, so the result is 2.1.

### `argmin`

Gives the item with the least score, with each tie-breaker stated.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
argmin(list as v, score, then [cite: …]: priority)
```

**Result:** `Option` of the record.

Use `argmin` to pick the item with the least score and then read its other fields: the age of the driver with the fewest years licensed. Every tie has a stated winner, so the order of the request leaves the answer unchanged.

**Rules**

* `argmin(list as v, score)` computes the score once for each item, with `v` naming the item. It gives an `Option` of the item: `Some(item)`, or `None` for an empty list.
* The score is a number.
* `then [cite: source:region]: priority` states a filed tie rule. Among items with the least score, the item with the greatest priority wins.
* The priority carries the citation of the rule that states it. A `then` that lacks one gets `E-CITATION`.
* Among items that are still tied, the item with the least `keyed by` key wins.
* A list of records needs a key: declare the input as `List(Driver) keyed by number`. A list that lacks a key gets `E-SELECT-TIE`.
* A priority written as a plain third argument gets `E-SELECT-TIE`. Write it with `then`.
* Open the result with `match`. To read only the least score, use `min`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
newest_driver_age: Num =
    match argmin(self.drivers as d, d.years_licensed) {
        Some(d) => d.age,
        None => 99
    },
inexperienced_surcharge: Factor = if self.newest_driver_age < 21 then inexperienced_factor else 1,
```

The surcharge reads the age of the driver with the fewest years licensed. Drivers licensed for 27 years and 2 years, aged 45 and 19, give an age of 19 and a surcharge of 1.1.

### `topn`

Keeps the `n` items with the greatest scores.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
topn(list as v, score, n)
topn(list as v, score, n, then [cite: …]: priority)
```

**Result:** A list of the record.

Use `topn` to keep the items with the greatest scores, such as the two drivers with the most points. Every tie at the cut has a stated winner, so the order of the request leaves the result unchanged.

**Rules**

* `topn(list as v, score, n)` computes the score once for each item, with `v` naming the item. It gives a list of the `n` items with the greatest scores.
* A list with fewer than `n` items gives every item. An `n` of 0 gives an empty list.
* `n` is a whole number of zero or more. A literal such as `-1` or `2.5` gets `E-TOPN-LIMIT`, and so does a calculation that can be negative or fractional.
* `then [cite: source:region]: priority` states a filed tie rule. Among items tied at the cut, the item with the greatest priority stays. A `then` that lacks a citation gets `E-CITATION`.
* Among items that are still tied, the item with the least `keyed by` key stays.
* A list of records needs a key: declare the input as `List(Driver) keyed by number`. A list that lacks a key gets `E-SELECT-TIE`.
* Pass the result to any function that reads a list, such as `sum`, `avg` or `len`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
household_points: Num = sum(topn(self.drivers as d, d.points, 2) as d, d.points),
points_surcharge: Factor = 1 + household_points_load * self.household_points,
```

Only the two drivers with the most points count toward the household. Drivers with 0, 3 and 5 points give 8 household points and a surcharge of 1.08.

### `rank`

Gives the position of one item when the list is ordered by a score.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rank(list as v, score, target, asc | desc, then [cite: …]: priority)
```

**Result:** `Num` when a record ranks itself in its own list, else `Option(Num)`.

Use `rank` for the place of one item when a list is put in order of a score: the highest-rated driver first, then the next. Each item gets its own position, counted from 0.

**Rules**

* `rank(list as v, score, target, direction)` orders the list by the score, with `v` naming the item. It gives the position of the item whose `keyed by` key equals `target`.
* `direction` is `desc` or `asc`. With `desc`, the greatest score takes position 0. With `asc`, the least score takes position 0.
* The list is a list of records with a key, and `target` has the type of that key. A list that lacks a key gets `E-SELECT-TIE`.
* `then [cite: source:region]: priority` states a filed tie rule. Among items with equal scores, the greater priority takes the earlier position, in both directions. A `then` that lacks a citation gets `E-CITATION`.
* Among items that are still tied, the lesser key takes the earlier position. Items with equal scores still get separate positions.
* The result is `Num` when a record ranks itself in the list that holds it: `rank(Policy.drivers as d, …, self.number, desc)` inside `Driver`.
* For any other target the result is `Option(Num)`, and it is `None` when the list holds zero items with that key.
* A call with other than four arguments, another direction, or a score that is other than a number gets `E-TYPE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rating_rank: Num = rank(Policy.drivers as d, d.class_factor, self.number, desc)
```

Each driver takes its place by class factor, highest first. Drivers 1, 2 and 3 with class factors of 1, 1.75 and 2.1 take positions 2, 1 and 0.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rating_rank: Num = rank(Policy.vehicles as v, v.symbol, self.number, desc),
driver_factor: Factor =
    match find(Policy.drivers as d, d.rating_rank = self.rating_rank) {
        Some(d) => d.class_factor,
        None => Policy.extra_vehicle_factor
    }
```

Vehicles take their places by symbol, and `find` pairs each vehicle with the driver in the same position. Two vehicles with symbol 10, numbered 7 and 3, take positions 1 and 0.

### `index`

Gives the position of a record in a plain list, counted from 0.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
index(self)
index(v)
```

**Result:** `Num`.

Use `index` when a rule depends on where a record sits in a plain list as the request sends it: the first trailer listed, each additional location. Positions count from 0.

**Rules**

* `index(self)` gives the position of the current record in the list that holds it.
* `index(v)` gives the position of the item that `v` names in a call such as `count`, `filter` or `min`.
* The list is a plain `List(R)`. A list declared `keyed by` is a map from key to record, and `index` on it gets `E-KEYED-ORDER`.
* The position follows the order of the request.
* An item of a filtered list keeps its position in the list as sent.
* The record sits in a list input of its parent. A record held any other way gets `E-TYPE`.
* The argument is `self` or the name after `as`. A field, or a call with two arguments, gets `E-TYPE`.
* A rating that reads `index` changes when the request reorders the list. When a filing orders items by a rating value, use `rank`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
position_factor: Factor = if index(self) = 0 then 1 else additional_trailer_factor,
```

The first trailer listed rates at 1, and each later trailer takes the additional-trailer factor of 0.9. Two trailers worth 4,000 each have charges of 40.00 and 36.00.

### `unique`

States that a key, or each value, is distinct across a list.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
where unique(self as v, v.key)
where unique(self as v, v)
```

**Result:** A list refinement.

Use `unique` to state that one field has a distinct value on every item of a list input: one license for each driver, one address for each location. Rating refuses a request that repeats a value, before any calculation runs.

**Rules**

* Write it as a rule on a list input, where `self` is the list: `where unique(self as v, v.field)`.
* The key is one required input of the item, read as `v.field`. A calculation, an optional input or an expression gets `E-REFINEMENT`.
* On a list of values, the key is the value itself: `where unique(self as v, v)`.
* With a `code` and a `message`, rating refuses a request that repeats a value and gives that code. A rule that carries only the call reports invalid input with the code `INPUT-UNIQUE`.
* `unique` stands alone as one `where` rule. Inside `not`, inside a calculation, or anywhere else, it gets `E-REFINEMENT`.
* A list `keyed by` a field already has distinct values of that field. Use `unique` for a second field.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
drivers: List(Driver) keyed by number
    where len(self) in [1, 6] [code: "DRIVER-COUNT", message: "A policy rates one to six drivers.", cite: manual:p1]
    where unique(self as d, d.license) [code: "DUPLICATE-LICENSE", message: "Each driver has a distinct license.", cite: manual:p1],
```

Drivers carry distinct numbers through `keyed by` and distinct licenses through `unique`. Rating refuses a request with two drivers on license `A00000001` and gives the code `DUPLICATE-LICENSE`.

## Dates

### `diff_days`

Counts the days from one date to a later date.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
diff_days(later, earlier)
```

**Result:** `Num`.

Use `diff_days` for the length of a period in days: a policy term for a pro rata premium, the days since a lapse. The count is exact and follows the calendar, leap days included.

**Rules**

* `diff_days(later, earlier)` takes two dates and gives the number of days from the second to the first.
* The same date twice gives 0. From 2026-02-01 to 2026-03-01 gives 28.
* When the first date is the earlier one, the result is negative: one day back gives -1.
* The result is a whole number, and the check knows its range from the ranges of the two dates.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
term_days: Num = diff_days(self.expiration_date, self.effective_date),
premium: Premium [cite: manual:p6] =
    round(
        annual_premium * self.age_factor * self.persistency_discount * self.advance_quote_discount
            * self.accident_surcharge * self.vehicle_age_discount * self.season_factor * self.term_days
            / 365,
        2,
        half_up
    )
```

The premium is pro rata by day. A term from 2026-06-01 to 2026-12-01 has 183 days and earns 183/365 of the annual premium.

### `diff_months`

Counts the calendar months from one date to a later date. The day of the month is ignored.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
diff_months(later, earlier)
```

**Result:** `Num`.

Use `diff_months` for a count of calendar months between two dates, such as months of prior insurance. It compares the year and the month and ignores the day.

**Rules**

* `diff_months(later, earlier)` takes two dates. It gives 12 times the difference of the years plus the difference of the months.
* The day of the month has zero effect. From 2026-02-28 to 2026-03-01 gives 1, and from 2025-12-01 to 2026-01-31 also gives 1.
* Two dates in the same calendar month give 0.
* When the first date is in an earlier month, the result is negative.
* For a count that respects the day, compare against a shifted date: `d >= add_months(start, 36)`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
months_insured: Num = diff_months(self.effective_date, self.insured_since),
persistency_discount: Factor = if self.months_insured >= 36 then persistency_factor else 1,
```

The discount asks for 36 calendar months of insurance. A policy effective 2026-06-01 and insured since 2023-06-30 counts 36 months and takes the factor 0.95.

### `diff_years`

Counts the full years from one date to a later date, as an age in years is counted.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
diff_years(later, earlier)
```

**Result:** `Num`.

Use `diff_years` for an age or a tenure in full years. It counts as people count an age: a year completes on the anniversary of the earlier date.

**Rules**

* `diff_years(later, earlier)` takes two dates and gives the number of full years from the second to the first.
* A year counts on the anniversary. From 2000-03-15, the date 2026-03-14 gives 25 and 2026-03-15 gives 26.
* A start date of February 29 has its anniversary on March 1 in a common year. From 2024-02-29, the date 2027-02-28 gives 2 and 2027-03-01 gives 3.
* Less than one full year gives 0.
* When the first date is the earlier one, the result is negative: the same count with a minus sign.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
age: Num = diff_years(self.effective_date, self.date_of_birth),
```

The rider's age is the age on the effective date. A rider born 2001-06-02 is 24 on 2026-06-01 and 25 one day later.

### `add_days`

Shifts a date by a whole number of days.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
add_days(date, n)
```

**Result:** `Date`.

Use `add_days` to move a date by a fixed number of days: a quote deadline, a grace period, a waiting period. The result is a date that you compare with other dates.

**Rules**

* `add_days(date, n)` gives the date `n` days after `date`. A negative `n` moves back.
* `n` is a whole-number literal, such as `30` or `-7`. An input, a calculation or a fraction gets `E-TYPE`.
* The result stays inside the years 0001 to 9999. The check proves it from the range of the date, and a date input that lacks a bound gets `E-DATE-RANGE`.
* Give each date input a range, such as `where self in [2026-01-01, 2030-12-31]`.
* A calculation of type `Date` holds the result. A rating outcome is a number, a `Bool` or an enumeration member, so compare the date before it reaches an outcome.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
advance_quote: Bool = self.quote_date <= add_days(self.effective_date, -7),
advance_quote_discount: Factor = if self.advance_quote then advance_quote_factor else 1
```

The discount applies to a quote made seven days or more before the effective date. For an effective date of 2026-06-01, a quote on 2026-05-25 qualifies and a quote on 2026-05-26 pays the full rate.

### `add_months`

Shifts a date by a whole number of months. A day past the month's end becomes its last day.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
add_months(date, n)
```

**Result:** `Date`.

Use `add_months` to move a date by whole calendar months: a 36-month look-back for accidents, a six-month term. The day of the month stays the same where the target month has that day.

**Rules**

* `add_months(date, n)` gives the same day of the month, `n` months later. A negative `n` moves back.
* A day past the end of the target month becomes its last day. 2026-01-31 plus one month gives 2026-02-28, and in 2024 it gives 2024-02-29.
* The same rule holds going back: 2026-03-31 minus one month gives 2026-02-28.
* `n` is a whole-number literal, such as `6` or `-36`. An input, a calculation or a fraction gets `E-TYPE`.
* The result stays inside the years 0001 to 9999. The check proves it from the range of the date, and a date input that lacks a bound gets `E-DATE-RANGE`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
chargeable_accidents: Num =
    count(self.accidents as a, a.occurred >= add_months(self.effective_date, -36)),
accident_surcharge: Factor = 1 + accident_load * self.chargeable_accidents,
```

An accident is chargeable for 36 months. For an effective date of 2026-06-01 the window opens on 2023-06-01: an accident on that day counts, and one on 2023-05-31 falls outside.

### `add_years`

Shifts a date by a whole number of years.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
add_years(date, n)
```

**Result:** `Date`.

Use `add_years` to move a date by whole years: the day a rider turns 65, the latest end of a one-year term. The month and the day stay the same.

**Rules**

* `add_years(date, n)` gives the same month and day, `n` years later. A negative `n` moves back.
* February 29 becomes February 28 when the target year is a common year. 2024-02-29 plus one year gives 2025-02-28, and minus four years gives 2020-02-29.
* `n` is a whole-number literal, such as `1` or `-10`. An input, a calculation or a fraction gets `E-TYPE`.
* The result stays inside the years 0001 to 9999. The check proves it from the range of the date, and a date input that lacks a bound gets `E-DATE-RANGE`.
* The call works in a calculation and in a `where` rule.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
senior: Bool = self.effective_date >= add_years(self.date_of_birth, 65),
```

A rider is a senior from the 65th birthday. A rider born 1961-06-01 is a senior for a policy effective 2026-06-01.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
where self.expiration_date <= add_years(self.effective_date, 1) [code: "TERM-LENGTH", message: "A term runs at most one year.", cite: manual:p1],
```

The rule on the policy limits a term to one year. For an effective date of 2026-06-01, rating accepts an expiration date of 2027-06-01 and refuses 2027-06-02 with the code `TERM-LENGTH`.

### `year`

Gives the year of a date.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
year(date)
```

**Result:** `Num`.

Use `year` to read the calendar year of a date, such as the policy year for the age of a vehicle. The result is a whole number.

**Rules**

* `year(date)` takes one date and gives its year: `year(2026-06-01)` is 2026.
* A call with zero or two arguments gets `E-TYPE`, and so does an argument that is a number.
* In an RSL 0.4.1 rater, read `year` inside a comparison, a `max` or `min`, or a lookup key.
* A calculation whose body is `year(d)` alone, or plain arithmetic on it such as `year(d) - self.model_year`, gets `E-CHAIN` in an RSL 0.4.1 rater.
* For an age in full years between two dates, use `diff_years`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
vehicle_age: Num = min(max(year(self.effective_date) - self.model_year, 0), 10),
vehicle_age_discount: Factor = 1 - vehicle_age_credit * self.vehicle_age
```

The vehicle age is the policy year minus the model year, held between 0 and 10. A 2021 model on a policy effective in 2026 has an age of 5 and a discount factor of 0.9. A 2027 model has an age of 0.

### `month`

Gives the month of a date, from 1 to 12.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
month(date)
```

**Result:** `Num`.

Use `month` to read the calendar month of a date, for a rule that depends on the season. The result is a whole number from 1 to 12.

**Rules**

* `month(date)` takes one date and gives its month: January is 1 and December is 12.
* A call with zero or two arguments gets `E-TYPE`, and so does an argument that is a number.
* In an RSL 0.4.1 rater, read `month` inside a comparison, a `max` or `min`, or a lookup key.
* A calculation whose body is `month(d)` alone, or plain arithmetic on it, gets `E-CHAIN` in an RSL 0.4.1 rater.
* For a count of months between two dates, use `diff_months`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
riding_season: Bool = month(self.effective_date) >= 4 and month(self.effective_date) <= 9,
season_factor: Factor = if self.riding_season then riding_season_factor else 1,
```

A policy that starts from April to September takes the riding-season factor. An effective date of 2026-06-01 has month 6 and gives 1.1.

## Options, choices and placeholders

### `Some`

Makes a present `Option` from a value.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Some(value)
```

**Result:** `Option` of the value's type.

Use `Some` to give a present value where the rater expects an `Option`: a calculation that holds a cell code for some risks and `None` for others. `None` is the matching word for an absent value.

**Rules**

* `Some(value)` takes one value. A call with zero or two arguments gets `E-TYPE`.
* It stands only where an `Option` is expected, such as a calculation typed `Option(Factor)` or an arm of a `match` with that type. Anywhere else it gets `E-TYPE`.
* The value has the type inside the `Option`. Wrapping a value that is already an `Option` gets `E-TYPE`.
* Every arm of a `match` or `if` has the same type. When one arm gives an `Option`, wrap the plain values of the other arms in `Some`.
* In a `match` pattern, `Some(c) => …` opens a present value and names it `c`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rated_cell: Option(CellId) =
    match self.surveyed_cell {
        Some(c) => Some(c),
        None => locate(WildfireGrid, self.latitude, self.longitude)
    },
```

A surveyed cell wins over the located cell. `locate` gives an `Option`, so the first arm wraps the surveyed cell `c` in `Some` to match it. A request with the surveyed cell `W202` rates in `W202` wherever its point lies.

### `select`

Gives a value between a filed low and a filed high, at a chosen position.

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
select(low..high, position)
```

**Result:** The type of the bounds.

Use `select` when a filing gives a low and a high value and the underwriter chooses a point between them, as in schedule rating. The result is exact.

**Rules**

* `select(low..high, position)` gives `low + position * (high - low)`.
* A position of 0 gives `low`, 1 gives `high` and 0.5 gives the midpoint.
* `low`, `high` and `position` are numbers. Each is a constant, an input, a calculation or a literal.
* The call computes any position as written. Bound the position input with `where self in [0, 1]` to keep the result between `low` and `high`.
* The result keeps every decimal. Round it where the filing states a rounding.
* `select` belongs to RSL 0.4.1. A rater that declares `rsl: "0.4"` gets `E-RSL-PATCH`.

**Example**

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
schedule_factor: Factor = select(schedule_low..schedule_high, self.risk_position)
```

The filed schedule factor runs from 0.75 to 1.25. A risk position of 0.3 gives 0.75 plus 0.3 times 0.5, which is 0.9.

### `nl`

Holds the place of a calculation that is still in plain words. A strict check fails while one remains.

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

**Result:** The expected type.

Use `nl` while you build a rater, to hold the place of a calculation that still waits for its formula. The text says in plain words what the calculation must do. The rest of the rater keeps its types, and the check lists each placeholder.

**Rules**

* Write `nl "text"`, with the text in double quotes. It takes the type that its place expects.
* It stands wherever a value goes: as a whole calculation, inside arithmetic, or as a condition.
* A strict `rslc check` fails while a placeholder remains. It prints one `obligation` line for each, with the position and the text.
* `rslc check --allow-incomplete` passes when placeholders are the only open items. Every other message still fails the check.
* A rater builds and rates only after each placeholder that an outcome reads has its formula.
* Put a citation on the calculation, before the `=`: `factor: Factor [cite: manual:p4] = nl "…"`.

**Example**

The experience factor waits for its formula, and the premium already reads it.

```rsl nl/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 {
            experience_factor: Factor = nl "Apply the experience rating plan on page 4.",
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.experience_factor, 2, half_up)
        }
    }
}

RatingOutcomes = {
    premium: Policy.premium
}
```

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL nl/rater.rsl (strict)
obligation: 25 nl/rater.rsl:26:41: Unresolved typed hole Hole:0 (Apply the experience rating plan on page 4.)
cost premium: 2 (each list of size 1: 2, 3: 2, 10: 2)
```

The strict check fails and names the placeholder at line 26. Replace `nl "…"` with the formula, and the check passes.

## Removed functions

| Function | Message | Replacement |
| - | - | - |
| `reject` | `E-REJECT-REMOVED` | State the rule as a refinement with a `code` and a `message`. |
| `checked` | `E-CHECKED-REMOVED` | State the rule as a refinement on the type. |


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