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

# Ask the questions a rater needs

> You give the inputs of a renters rater their questions, follow-ups, item names and choices, then read them in the input contract.

A rater's inputs are facts that a person answers. The application that collects the answers is the **host**: a form, a conversation or an agent. The host reads the rater's **input contract** to learn what to ask, and when.

In this tutorial you take a renters rater with bare inputs and state its questions. Finish [Write your first rater](https://docs.canary.effectiveai.app/rsl/tutorial/first-rater.md) first.

## What you build

You make five changes to the inputs of one rater:

1. Give two inputs a question, help text and a section.
2. Add two follow-up inputs that a host asks only after a `true` answer.
3. Name the items of a list.
4. Mark the coverage limit as a choice with a default.
5. Mark an input that the host fills, and suggest an answer from it.

Then you export the rater, read each change in its input contract, and rate a request that leaves the follow-ups out.

The rates, rules and source citations are synthetic.

## Start from a plain rater

Create a folder for the starting rater:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
mkdir plain
```

Save this rater as `plain/rater.rsl`.

> [accordion] plain/rater.rsl
>
> ```rsl plain/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Renters (questions example)",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Money = Num {precision: 0.01},
>     County = {
>         maricopa,
>         pima,
>         other
>     },
>     ItemKind = {
>         jewelry,
>         bicycle,
>         instrument
>     }
> }
>
> Constants = {
>     base_premium [cite: manual:p2] = 120,
>     rate_per_1000 [cite: manual:p2] = 4.5,
>     urban_county_factor [cite: manual:p3] = 1.1,
>     old_building_factor [cite: manual:p3] = 1.15,
>     business_charge [cite: manual:p3] = 40,
>     item_rate [cite: manual:p4] = 0.012,
>     bicycle_rate [cite: manual:p4] = 0.03
> }
>
> Records = {
>     Policy = {
>         inputs {
>             county: County,
>             year_built: Num {precision: int} where self in [1800, 2100],
>             home_business: Bool,
>             contents_limit: Num {precision: 1000} where self in [20000, 100000],
>             items: List(Item) where len(self) in [0, 5]
>         },
>         step premium {
>             county_factor: Num = if self.county = @County.maricopa then urban_county_factor else 1,
>             age_factor: Num = if self.year_built < 1980 then old_building_factor else 1,
>             contents_premium: Premium [cite: manual:p2] =
>                 round(self.contents_limit / 1000 * rate_per_1000 * self.county_factor * self.age_factor, 2, half_up),
>             business_premium: Premium [cite: manual:p3] = if self.home_business then business_charge else 0,
>             item_premium: Premium [cite: manual:p4] = sum(self.items as i, i.charge),
>             premium: Premium [cite: manual:p5] =
>                 round(base_premium + self.contents_premium + self.business_premium + self.item_premium, 0, half_up)
>         }
>     },
>     Item = {
>         inputs {
>             kind: ItemKind,
>             value: Money where self in [1000, 25000]
>         },
>         step item {
>             rate: Rate = if self.kind = @ItemKind.bicycle then bicycle_rate else item_rate,
>             charge: Premium [cite: manual:p4] = round(self.value * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

The rater prices a renters policy from five inputs: the county, the year of construction, a home business, the contents limit and a list of scheduled valuables.

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

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

The input contract of this rater states the type of each input:

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

from relativities import Rater

plain = Rater.build("plain/rater.rsl")
fields = plain.input_schema["properties"]["Policy"]["properties"]
print(json.dumps(fields["home_business"]))
# {"type": "boolean"}
```

A host that reads this field knows its type and lacks the words to ask for it.

Copy the rater to a second folder. Edit the copy in the steps that follow:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
mkdir questions
cp plain/rater.rsl questions/rater.rsl
```

## Give an input its question

In `questions/rater.rsl`, replace the `year_built` and `home_business` inputs with these lines:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
year_built: Num {precision: int} [ask: "What year was the building built?", help: "The lease or the landlord states the year.", format: "year", group: "Home"]
    where self in [1800, 2100],
home_business: Bool [ask: "Do you run a business from the home?", help: "Count paid work you do at home for your own customers.", group: "Business"],
```

* `ask` is the question a person reads.
* `help` is a longer text that the host shows with the question.
* `group` names the section of the questionnaire.
* `format` tells the host how to take the answer. [Answer formats](https://docs.canary.effectiveai.app/rsl/reference/names.md#answer-formats) lists the names.

The annotations change what a host asks. The premium stays the same.

Check the rater now, and again after each later step:

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS questions/rater.rsl (strict)
...
```

## Add follow-up inputs

Some questions apply only after an earlier answer. A follow-up input states its condition after its type:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
name: Type when condition
```

Add two follow-up inputs after `home_business`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
business_property: Num {precision: int} when self.home_business [ask: "What is the business equipment worth?", format: "money", unit: "USD", group: "Business"]
    where self in [0, 20000],
customers_visit: Bool when self.home_business [ask: "Do customers visit the home?", group: "Business"],
```

A host asks both questions after a `true` answer to `home_business`. `unit` names the unit of the amount.

Each follow-up changes the premium. Add two constants to `Constants`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
equipment_rate [cite: manual:p3] = 0.02,
visitor_charge [cite: manual:p3] = 25,
```

Add two calculations to the `premium` step:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
equipment_premium: Premium [cite: manual:p3] =
    if self.home_business then round(self.business_property * equipment_rate, 2, half_up) else 0,
visitor_premium: Premium [cite: manual:p3] =
    if self.home_business and self.customers_visit then visitor_charge else 0,
```

Then add both to the total:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
premium: Premium [cite: manual:p5] =
    round(base_premium + self.contents_premium + self.business_premium + self.equipment_premium + self.visitor_premium + self.item_premium, 0, half_up)
```

A follow-up is absent where its condition is false. The two calculations show the two ways to read one:

* `equipment_premium` reads the amount inside `if self.home_business then …`, where the amount is present.
* `visitor_premium` reads the answer after `self.home_business and`, so an unasked question adds the same \$0 as an answer of `false`.

An unasked question stays distinct from an answer of `false`: the rater states which one it counts as. `not self.home_business or self.customers_visit` counts an unasked question as `true`.

A read of either follow-up outside its condition gets the check message [`E-FOLLOW-UP-READ`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/questions#e-follow-up-read).

## Name the items of a list

A person adds valuables one at a time, and each later question names the item it is about. Replace the `items` input:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
items: List(Item) keyed by item_id [ask: "Which valuables do you want to schedule?", help: "Add each item worth $1,000 or more.", group: "Valuables", called: "the {kind}|item {#}", complete: "Any other valuables?"]
    where len(self) in [0, 5]
```

* `keyed by item_id` makes `item_id` the key of each item.
* `called` names an item in a question. `{kind}` is the item's `kind` answer, and `{#}` is its place in the list.
* The `|` separates alternatives, and a host takes the first one that it has every answer for.
* `complete` is the question a host asks before it closes the list.

Replace the inputs of `Item`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
item_id: Num {precision: int} where self in [1, 99],
kind: ItemKind [ask: "What kind of item is item {#}?"],
value: Money [ask: "What is {this} worth?", format: "money", unit: "USD"]
    where self in [1000, 25000]
```

In a question, `{this}` stands for the name that `called` gives the item.

A `label` on an enum member gives the words a person reads for that value. In `Types`, add one to `instrument`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
instrument [label: "musical instrument"]
```

A host now asks "What kind of item is item 1?", then "What is the musical instrument worth?".

## Mark what the person buys

The contents limit is something the person buys. The other inputs are facts about the home. Replace the `contents_limit` input:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
contents_limit: Num {precision: 1000} [ask: "How much contents coverage do you want?", choice, shape: "limit", api_default: "30000", unit: "USD", group: "Coverage"]
    where self in [20000, 100000],
```

* `choice` marks the input as a choice. A host offers it as a coverage option and keeps it out of the questions.
* `ask` is the text a host shows beside the option.
* `shape` names the kind of choice. [Choice shapes](https://docs.canary.effectiveai.app/rsl/reference/names.md#choice-shapes) lists the names.
* `api_default` is the value the rater uses when a request leaves the input out. Write it as a JSON literal in quotes.

A `choice` requires an `api_default`, so a request always has a value for it.

## Let the host fill an answer

A host often has a source for an answer, such as a table of ZIP codes and counties. The rater names the source, and each host supplies a function of that name.

Add a type for the ZIP code to `Types`:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
Zip = Code(pattern: "[0-9]{5}"),
```

Then replace the `county` input with these three inputs:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
postal_code: Zip [ask: "What is the ZIP code of the home you rent?", format: "postal-code", group: "Home"],
zip_county: County [filled_by: "zip_lookup(postal_code).county"],
county: County from self.zip_county [ask: "Which county is the home in?", group: "Home"],
```

* `filled_by` has the form `source(input).field`. The host gives the `postal_code` answer to its source `zip_lookup` and stores the `county` field of the result.
* Leave `ask` off an input with `filled_by`. The host fills it and skips the question.
* `from self.zip_county` suggests an answer for `county`. The person accepts the suggestion or picks another county.

The premium reads `county` alone, so the price rests on an answer the person gave or accepted.

## Check and export the rater

Your file now matches the finished rater.

> [accordion] questions/rater.rsl
>
> ```rsl questions/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Renters (questions example)",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Money = Num {precision: 0.01},
>     Zip = Code(pattern: "[0-9]{5}"),
>     County = {
>         maricopa,
>         pima,
>         other
>     },
>     ItemKind = {
>         jewelry,
>         bicycle,
>         instrument [label: "musical instrument"]
>     }
> }
>
> Constants = {
>     base_premium [cite: manual:p2] = 120,
>     rate_per_1000 [cite: manual:p2] = 4.5,
>     urban_county_factor [cite: manual:p3] = 1.1,
>     old_building_factor [cite: manual:p3] = 1.15,
>     business_charge [cite: manual:p3] = 40,
>     equipment_rate [cite: manual:p3] = 0.02,
>     visitor_charge [cite: manual:p3] = 25,
>     item_rate [cite: manual:p4] = 0.012,
>     bicycle_rate [cite: manual:p4] = 0.03
> }
>
> Records = {
>     Policy = {
>         inputs {
>             postal_code: Zip [ask: "What is the ZIP code of the home you rent?", format: "postal-code", group: "Home"],
>             zip_county: County [filled_by: "zip_lookup(postal_code).county"],
>             county: County from self.zip_county [ask: "Which county is the home in?", group: "Home"],
>             year_built: Num {precision: int} [ask: "What year was the building built?", help: "The lease or the landlord states the year.", format: "year", group: "Home"]
>                 where self in [1800, 2100],
>             home_business: Bool [ask: "Do you run a business from the home?", help: "Count paid work you do at home for your own customers.", group: "Business"],
>             business_property: Num {precision: int} when self.home_business [ask: "What is the business equipment worth?", format: "money", unit: "USD", group: "Business"]
>                 where self in [0, 20000],
>             customers_visit: Bool when self.home_business [ask: "Do customers visit the home?", group: "Business"],
>             contents_limit: Num {precision: 1000} [ask: "How much contents coverage do you want?", choice, shape: "limit", api_default: "30000", unit: "USD", group: "Coverage"]
>                 where self in [20000, 100000],
>             items: List(Item) keyed by item_id [ask: "Which valuables do you want to schedule?", help: "Add each item worth $1,000 or more.", group: "Valuables", called: "the {kind}|item {#}", complete: "Any other valuables?"]
>                 where len(self) in [0, 5]
>         },
>         step premium {
>             county_factor: Num = if self.county = @County.maricopa then urban_county_factor else 1,
>             age_factor: Num = if self.year_built < 1980 then old_building_factor else 1,
>             contents_premium: Premium [cite: manual:p2] =
>                 round(self.contents_limit / 1000 * rate_per_1000 * self.county_factor * self.age_factor, 2, half_up),
>             business_premium: Premium [cite: manual:p3] = if self.home_business then business_charge else 0,
>             equipment_premium: Premium [cite: manual:p3] =
>                 if self.home_business then round(self.business_property * equipment_rate, 2, half_up) else 0,
>             visitor_premium: Premium [cite: manual:p3] =
>                 if self.home_business and self.customers_visit then visitor_charge else 0,
>             item_premium: Premium [cite: manual:p4] = sum(self.items as i, i.charge),
>             premium: Premium [cite: manual:p5] =
>                 round(base_premium + self.contents_premium + self.business_premium + self.equipment_premium + self.visitor_premium + self.item_premium, 0, half_up)
>         }
>     },
>     Item = {
>         inputs {
>             item_id: Num {precision: int} where self in [1, 99],
>             kind: ItemKind [ask: "What kind of item is item {#}?"],
>             value: Money [ask: "What is {this} worth?", format: "money", unit: "USD"]
>                 where self in [1000, 25000]
>         },
>         step item {
>             rate: Rate = if self.kind = @ItemKind.bicycle then bicycle_rate else item_rate,
>             charge: Premium [cite: manual:p4] = round(self.value * self.rate, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

Check it, then package it into a bundle:

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

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS questions/rater.rsl (strict)
cost premium: 8 + 3*|Policy.items| (each list of size 1: 11, 3: 17, 10: 38)
WROTE bundle: questions.zip
```

The bundle holds the input contract as `program/input-schema.json`.

## Read the input contract

`Rater.input_schema` returns the same contract as a dictionary. It is the JSON Schema of one request, and each annotation is a key that starts with `x-`:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rater = Rater.load("questions.zip")
policy = rater.input_schema["properties"]["Policy"]
fields = policy["properties"]
print(json.dumps(fields["home_business"], indent=2))
# {
#   "type": "boolean",
#   "x-ask": "Do you run a business from the home?",
#   "x-group": "Business",
#   "x-help": "Count paid work you do at home for your own customers."
# }
```

The follow-up carries its condition, and its type admits `null`:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(json.dumps(fields["business_property"], indent=2))
# {
#   "anyOf": [
#     {
#       "maximum": 20000.0,
#       "minimum": 0.0,
#       "type": "integer"
#     },
#     {
#       "type": "null"
#     }
#   ],
#   "format": "money",
#   "x-ask": "What is the business equipment worth?",
#   "x-group": "Business",
#   "x-rsl-when": "self.home_business",
#   "x-unit": "USD"
# }
```

The contract requires three answers. The follow-ups, the choice, the list and the inputs that only feed a suggestion are optional:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
policy["required"]
# ['county', 'year_built', 'home_business']
```

The choice carries its default and its shape:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(json.dumps(fields["contents_limit"], indent=2))
# {
#   "default": 30000,
#   "maximum": 100000.0,
#   "minimum": 20000.0,
#   "multipleOf": 1000,
#   "type": "number",
#   "x-ask": "How much contents coverage do you want?",
#   "x-choice": true,
#   "x-group": "liability",
#   "x-round": "up",
#   "x-shape": "limit",
#   "x-unit": "USD"
# }
```

`x-round` comes from the shape. For a limit that the rater leaves unsold, a host prices the next limit up.
`x-group` comes from the shape as well: a `limit` sits in the `liability` part of a coverage screen.

The list carries its key, its item name and its closing question:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(json.dumps({key: value for key, value in fields["items"].items() if key.startswith("x-")}, indent=2))
# {
#   "x-ask": "Which valuables do you want to schedule?",
#   "x-complete": "Any other valuables?",
#   "x-group": "Valuables",
#   "x-help": "Add each item worth $1,000 or more.",
#   "x-keyed-by": "item_id",
#   "x-label": "the {kind}|item {#}"
# }
```

The filled input names its source, and the suggested input names what the suggestion reads:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(json.dumps(fields["zip_county"]["x-filled-by"]))
# {"field": "county", "from": "postal_code", "source": "zip_lookup"}
print(json.dumps(fields["county"]["x-from"]))
# {"reads": ["zip_county"], "text": "self.zip_county"}
```

[Input contract](https://docs.canary.effectiveai.app/rsl/reference/input-contract.md) describes every key.

## Rate with the follow-ups left out

This request answers the three required questions and leaves out everything else:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
answers = {"county": "pima", "year_built": 1975, "home_business": False}
rater.rate(answers).to_pandas()[["status", "premium"]]
#       status premium
# 0  succeeded     275
```

The rater reads both follow-ups as absent, and each calculation takes its `else` value of 0. It takes the default contents limit of \$30,000 and rates an empty list. The premium is \$275: the \$120 base premium plus 30 × 4.50 × 1.15, rounded to whole dollars.

Where the condition is false, the rater ignores a follow-up that the request gives:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
ignored = answers | {"business_property": 5000, "customers_visit": True}
rater.rate(ignored).to_pandas()[["status", "premium"]]
#       status premium
# 0  succeeded     275
```

Where the condition is true, the rater requires the follow-ups:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
owed = {"county": "pima", "year_built": 1975, "home_business": True}
refused = rater.rate(owed)
refused.to_pandas()[["status", "reason_code"]]
#            status      reason_code
# 0  invalid_inputs  INPUT-FOLLOW-UP
```

The refusal lists each follow-up that the request owes:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
for violation in refused.rows[0].error.details["violations"]:
    print(violation["code"], violation["path"])
# INPUT-FOLLOW-UP Policy.business_property
# INPUT-FOLLOW-UP Policy.customers_visit
```

With both answers, the rater adds the business premiums:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
complete = owed | {"business_property": 5000, "customers_visit": True}
rater.rate(complete).to_pandas()[["status", "premium"]]
#       status premium
# 0  succeeded     440
```

[Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) describes the refusal that a rater returns.

## Next steps

* [Plan the next question](https://docs.canary.effectiveai.app/rsl/how-to/plan-the-next-question.md) asks these questions one at a time, in order.
* [Rate with missing answers](https://docs.canary.effectiveai.app/rsl/how-to/rate-with-missing-answers.md) rates a request while a person is still answering.
* [Input contract](https://docs.canary.effectiveai.app/rsl/reference/input-contract.md) lists every key of the contract.
* [Names](https://docs.canary.effectiveai.app/rsl/reference/names.md) lists every annotation and where it applies.

`rslc examples get questions` saves the finished rater of this tutorial to a folder.


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