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

# Rate a list of items

> Declare a list of records, rate each item in its own step, add the items up, and pick one item with a stated tie-breaker.

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

A policy with several vehicles or locations holds them as a list of records.
Each item calculates its own premium, and the policy adds the premiums up:

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

The example rates a business auto policy from the synthetic carrier Example Mutual.
Its manual rates each vehicle by class, discounts a fleet, and adds three policy charges.

## Rate a policy with three vehicles

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

> [accordion] Complete example: fleet/rater.rsl
>
> ```rsl fleet/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Business Auto",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Plate = Code(pattern: "[A-Z0-9]{2,7}"),
>     Class = {
>         light,
>         medium,
>         heavy
>     }
> }
>
> Constants = {
>     base_premium [cite: manual:p2] = 800,
>     medium_factor [cite: manual:p2] = 1.3,
>     heavy_factor [cite: manual:p2] = 1.8,
>     fleet_discount_factor [cite: manual:p3] = 0.9,
>     telematics_factor [cite: manual:p3] = 0.95,
>     heavy_vehicle_fee [cite: manual:p4] = 40,
>     inspection_fee [cite: manual:p4] = 100,
>     hazardous_fee [cite: manual:p4] = 75,
>     heavy_hazardous_fee [cite: manual:p4] = 150
> }
>
> Records = {
>     Policy = {
>         inputs {
>             vehicles: List(Vehicle) keyed by number
>                 where len(self) in [1, 20] [code: "VEHICLE-COUNT", message: "A policy rates one to twenty vehicles.", cite: manual:p1]
>                 where unique(self as v, v.plate) [code: "DUPLICATE-PLATE", message: "Each vehicle has a distinct plate.", cite: manual:p1]
>         },
>         step fleet [cite: manual:p3] {
>             fleet_factor: Factor = if len(self.vehicles) >= 3 then fleet_discount_factor else 1,
>             lead_number: Num =
>                 match argmax(self.vehicles as v, v.cost_new, then [cite: manual:p3]: v.model_year) {
>                     Some(v) => v.number,
>                     None => 0
>                 },
>             all_telematics: Bool = all(self.vehicles as v, v.telematics),
>             telematics_discount: Factor = if self.all_telematics then telematics_factor else 1
>         },
>         step charges [cite: manual:p4] {
>             heavy_count: Num = count(self.vehicles as v, v.class = @Class.heavy),
>             heavy_charge: Premium = heavy_vehicle_fee * self.heavy_count,
>             needs_inspection: Bool = exists(self.vehicles as v, v.cost_new > 150000),
>             inspection_charge: Premium = if self.needs_inspection then inspection_fee else 0,
>             hazardous_charge: Premium =
>                 match find(self.vehicles as v, v.hazardous) {
>                     Some(v) => if v.class = @Class.heavy then heavy_hazardous_fee else hazardous_fee,
>                     None => 0
>                 }
>         },
>         step premium [cite: manual:p5] {
>             vehicle_premium: Premium = sum(self.vehicles as v, v.premium),
>             premium: Premium [cite: manual:p5] =
>                 round(self.vehicle_premium * self.telematics_discount + self.heavy_charge + self.inspection_charge + self.hazardous_charge, 0, half_up)
>         }
>     } where sum(self.vehicles as v, v.cost_new) <= 1000000 [code: "FLEET-VALUE", message: "A policy rates vehicles worth 1,000,000 or less in total.", cite: manual:p1],
>     Vehicle = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             plate: Plate,
>             class: Class,
>             cost_new: Num {precision: int} where self in [1000, 500000],
>             model_year: Num {precision: int} where self in [1990, 2030],
>             telematics: Bool,
>             hazardous: Bool
>         },
>         step premium [cite: manual:p2] {
>             class_factor: Factor =
>                 match self.class {
>                     @Class.light => 1,
>                     @Class.medium => medium_factor,
>                     @Class.heavy => heavy_factor
>                 },
>             fleet_discount: Factor = if self.number = Policy.lead_number then 1 else Policy.fleet_factor,
>             premium: Premium [cite: manual:p2] =
>                 round(base_premium * self.class_factor * self.fleet_discount, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

A request sends the list as a JSON array, with one object for each vehicle:

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

rater = Rater.build("fleet/rater.rsl")
van = {"number": 1, "plate": "AZ1001", "class": "light", "cost_new": 42000, "model_year": 2021, "telematics": True, "hazardous": False}
tanker = {"number": 2, "plate": "AZ1002", "class": "heavy", "cost_new": 165000, "model_year": 2023, "telematics": True, "hazardous": True}
box_truck = {"number": 3, "plate": "AZ1003", "class": "medium", "cost_new": 78000, "model_year": 2025, "telematics": False, "hazardous": False}
policy = {"vehicles": [van, tanker, box_truck]}

explained = rater.explain(policy)
print(explained[explained["kind"] == "calculation"].to_string(index=False))
#        kind                       name value
# calculation               fleet_factor   0.9
# calculation                lead_number     2
# calculation             all_telematics False
# calculation        telematics_discount     1
# calculation                heavy_count     1
# calculation               heavy_charge    40
# calculation           needs_inspection  True
# calculation          inspection_charge   100
# calculation           hazardous_charge   150
# calculation   vehicles[0].class_factor     1
# calculation   vehicles[1].class_factor   1.8
# calculation   vehicles[2].class_factor   1.3
# calculation vehicles[0].fleet_discount   0.9
# calculation vehicles[1].fleet_discount     1
# calculation vehicles[2].fleet_discount   0.9
# calculation        vehicles[0].premium   720
# calculation        vehicles[1].premium  1440
# calculation        vehicles[2].premium   936
# calculation            vehicle_premium  3096
# calculation                    premium  3386
```

`explain` gives one row for each calculation of each vehicle, named by its position in the request.
The three vehicle premiums add up to 3,096. The charges of 40, 100 and 150 bring the annual premium to 3,386.

## Declare the list

Declare the item as its own record, with its inputs:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Vehicle = {
    inputs {
        number: Num {precision: int} where self in [1, 99],
        plate: Plate,
        class: Class,
        cost_new: Num {precision: int} where self in [1000, 500000],
        model_year: Num {precision: int} where self in [1990, 2030],
        telematics: Bool,
        hazardous: Bool
    },
```

Declare the list as an input of the policy:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
vehicles: List(Vehicle) keyed by number
    where len(self) in [1, 20] [code: "VEHICLE-COUNT", message: "A policy rates one to twenty vehicles.", cite: manual:p1]
    where unique(self as v, v.plate) [code: "DUPLICATE-PLATE", message: "Each vehicle has a distinct plate.", cite: manual:p1]
```

* `keyed by number` names the input that identifies each vehicle. Each vehicle in a request has its own `number`.
* `len(self) in [1, 20]` states how many vehicles a policy rates.
* `unique(self as v, v.plate)` states that a second input is distinct across the list.
* Each rule carries the code, the message and the citation of its refusal.

The rater refuses a request that breaks one of these rules, before it rates:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
requests = [
    {"vehicles": []},
    {"vehicles": [van, {**tanker, "number": 1}]},
    {"vehicles": [van, {**tanker, "plate": "AZ1001"}]},
]
print(rater.rate(requests).to_pandas()[["status", "reason_code", "reason"]].to_string())
#            status      reason_code                                                                                       reason
# 0         refused    VEHICLE-COUNT                                             vehicles: A policy rates one to twenty vehicles.
# 1  invalid_inputs        INPUT-KEY  vehicles[1].number: vehicles names each Vehicle once by number; this number is already used
# 2         refused  DUPLICATE-PLATE                                        vehicles[1].plate: Each vehicle has a distinct plate.
```

## Rate each item in its own step

Write the calculations of one vehicle in the `Vehicle` record. `self` is that vehicle:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
step premium [cite: manual:p2] {
    class_factor: Factor =
        match self.class {
            @Class.light => 1,
            @Class.medium => medium_factor,
            @Class.heavy => heavy_factor
        },
    fleet_discount: Factor = if self.number = Policy.lead_number then 1 else Policy.fleet_factor,
    premium: Premium [cite: manual:p2] =
        round(base_premium * self.class_factor * self.fleet_discount, 2, half_up)
}
```

The rater runs the step once for each vehicle.
The tanker is a heavy vehicle, so `vehicles[1].premium` is `800 × 1.8 = 1440`.

## Read a policy value from an item

A vehicle reads a value of its policy by the record's name, as in `Policy.fleet_factor`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
fleet_discount: Factor = if self.number = Policy.lead_number then 1 else Policy.fleet_factor,
```

The policy calculates `fleet_factor` once, from the length of the list:

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

The van and the box truck take the fleet factor of 0.9. The tanker is the lead vehicle and keeps its full premium.

## Add the items up

`sum` binds a name to each item and adds one value for each:

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

* `sum` of an empty list is 0.
* `max`, `min` and `avg` take the same two parts and need a list with one item or more.
* The length rule `[1, 20]` gives the check that proof. On a list that accepts zero items, these three get
  [`E-EMPTY-FOLD`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/calculations#e-empty-fold).

To round after each item, see [Round after each factor](https://docs.canary.effectiveai.app/rsl/how-to/round-a-premium.md#round-after-each-factor).

## Write a rule over the list total

A rule on the record reads a fold over its list. Write it after the record's closing brace:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
} where sum(self.vehicles as v, v.cost_new) <= 1000000 [code: "FLEET-VALUE", message: "A policy rates vehicles worth 1,000,000 or less in total.", cite: manual:p1],
```

`self` is the policy, so the fold reads `self.vehicles`. The rater checks the rule before it rates:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
at_limit = {"vehicles": [{**van, "cost_new": 500000}, {**tanker, "cost_new": 400000}, {**box_truck, "cost_new": 100000}]}
over_limit = {"vehicles": [{**van, "cost_new": 500000}, {**tanker, "cost_new": 400000}, {**box_truck, "cost_new": 100001}]}
print(rater.rate([at_limit, over_limit]).to_pandas()[["status", "premium", "reason_code", "reason"]].to_string())
#       status premium  reason_code                                                     reason
# 0  succeeded    3322          NaN                                                        NaN
# 1    refused     NaN  FLEET-VALUE  A policy rates vehicles worth 1,000,000 or less in total.
```

A total of 1,000,000 rates, and one dollar more is refused with the rule's code.
The rule sits on the root record, so the `reason` is the message alone.

## Count and test the items

`count`, `exists` and `all` take the list and a condition on one item:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
heavy_count: Num = count(self.vehicles as v, v.class = @Class.heavy),
heavy_charge: Premium = heavy_vehicle_fee * self.heavy_count,
needs_inspection: Bool = exists(self.vehicles as v, v.cost_new > 150000),
```

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
all_telematics: Bool = all(self.vehicles as v, v.telematics),
```

| Function | Result | On an empty list |
| - | - | - |
| `len` | The number of items | 0 |
| `count` | The number of items that meet the condition | 0 |
| `exists` | True when one item or more meets the condition | False |
| `all` | True when every item meets the condition | True |

The example has one heavy vehicle, so the heavy charge is 40. The box truck lacks telematics, so `all_telematics` is false.

## Pick one item

`argmax` gives the item with the greatest score. The manual names the vehicle with the greatest cost new as the lead vehicle:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
lead_number: Num =
    match argmax(self.vehicles as v, v.cost_new, then [cite: manual:p3]: v.model_year) {
        Some(v) => v.number,
        None => 0
    },
```

* The result is an option: `Some` item, or `None` for an empty list. `match` reads it, and both arms are required.
* `then [cite: manual:p3]: v.model_year` is the filed tie-breaker: of two vehicles with the same cost, the newer one leads.
* The list key breaks each remaining tie: the vehicle with the lowest `number` leads.
* `argmin` gives the item with the least score in the same way.

Each tie-breaker is stated, so a request rates the same way in any order of its list:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
same_cost = {"vehicles": [van, tanker, {**box_truck, "cost_new": 165000}]}
same_cost_and_year = {"vehicles": [{**box_truck, "cost_new": 165000, "model_year": 2023}, tanker, van]}
for request in [policy, same_cost, same_cost_and_year]:
    values = rater.explain(request).set_index("name")["value"]
    print(values["lead_number"], values["premium"])
# 2 3386
# 3 3346
# 2 3386
```

In the second request the box truck is newer, so it leads. In the third request the cost and year tie, so vehicle 2 leads.

`find` gives the first item, in key order, that meets a condition:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
hazardous_charge: Premium =
    match find(self.vehicles as v, v.hazardous) {
        Some(v) => if v.class = @Class.heavy then heavy_hazardous_fee else hazardous_fee,
        None => 0
    }
```

The tanker is the first vehicle that hauls hazardous cargo, and it is heavy, so the charge is 150.
A policy with zero hazardous vehicles takes the `None` arm.

A selection needs a keyed list. `argmax`, `argmin`, `find`, `topn` or `rank` on a list that lacks `keyed by` gets
[`E-SELECT-TIE`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/names#e-select-tie).

## Related

* [Lists](https://docs.canary.effectiveai.app/rsl/reference/functions.md#lists) in the function reference covers `filter`, `topn`, `rank`, `index` and `avg`.
* [Work with dates](https://docs.canary.effectiveai.app/rsl/how-to/work-with-dates.md) counts the incidents in a look-back window.
* [Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) lists the reason codes a rater returns for a list.


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