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

# Planner

> The public surface of the question planner: the rslc plan commands, the journey file, the Journey class, and each field of a plan.

The planner turns the missing answers of one or more raters into question cards.
It reads two things from each rater: its [input contract](https://docs.canary.effectiveai.app/rsl/reference/input-contract.md) and its [result](https://docs.canary.effectiveai.app/rsl/reference/results.md) for the answers so far.
`rslc plan` and `rslc.journey.Journey` run the planner on your machine. [Plan the next question](https://docs.canary.effectiveai.app/rsl/how-to/plan-the-next-question.md) gives the steps.

The examples use two raters from the synthetic carrier Example Mutual: the shared renters example and a smaller rater, `basic/rater.rsl`.

> [accordion] Complete example: basic/rater.rsl
>
> ```rsl basic/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Renters Basic",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p2] = 6,
>     dog_surcharge [cite: manual:p3] = 40
> }
>
> Records = {
>     Policy = {
>         inputs {
>             contents: Num {precision: int} [ask: "What are your belongings worth?", group: "Home"]
>                 where self in [10000, 50000] [code: "CONTENTS-LIMIT", message: "The basic program covers belongings up to $50,000.", cite: manual:p2],
>             has_dog: Bool [ask: "Do you have a dog?", group: "Household"]
>         },
>         step rating [cite: manual:p2] {
>             dog_charge: Num = if self.has_dog then dog_surcharge else 0,
>             premium: Premium [cite: manual:p2] =
>                 round(self.contents / 1000 * rate_per_1000 + self.dog_charge, 0, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc examples get renters
```

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

## Commands

Each command names a journey file. A command that changes the journey writes the file back, then prints the next card.

| Command | Effect on the journey file | Prints |
| - | - | - |
| `rslc plan start JOURNEY --rater ID=FOLDER` | writes a new file for the raters | the first card |
| `rslc plan answer JOURNEY PATH VALUE` | saves the JSON value at the path | the next card |
| `rslc plan dismiss JOURNEY DOCUMENT` | adds the document to the documents offered | the next card |
| `rslc plan next JOURNEY` | keeps the answers | the current card |
| `rslc plan skip JOURNEY PATH` | adds the path to the cards put off | the next card |
| `rslc plan search JOURNEY QUERY` | leaves the file as it is | the answers that match |

[Commands](https://docs.canary.effectiveai.app/rsl/reference/commands.md) lists each option. Each command takes `--json`.

### `start`

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan start journey.json --rater renters=renters --rater basic=basic --title "renters=Renters" --title "basic=Renters Basic" --section Insurance --section Home
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
yesno Policy.insured_now: Do you have a renters policy now?
  section: Insurance
  asked by: Renters
  still settled if skipped: 1
ready: none
```

| Option | Meaning |
| - | - |
| `--rater ID=FOLDER` | one rater: the ID the plan reports, and the folder that holds `rater.rsl`; repeat for each rater |
| `--title ID=NAME` | the name a card shows for the rater; the default is the ID |
| `--section NAME` | the order of the sections; repeat for each section |

`start` replaces a file at the same path. The journey begins with one empty record for each root record of the first rater.

### `answer`

`VALUE` is JSON: `30000`, `true`, `"metro"` or `null`. `answer` also takes a skipped path off the cards put off.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan answer journey.json Policy.insured_now true
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
document document:declarations_page:
  section: Home
  help: declarations page
ready: none
```

The next card here is a document card. A document counts as offered once a command prints it.

### `dismiss`

`DOCUMENT` is the kind of a document card, such as `declarations_page`. The planner offers each document once.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan dismiss journey.json declarations_page
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
number Policy.contents: What are your belongings worth?
  section: Home
  help: Count furniture, clothing and electronics.
  min: 10000
  max: 100000
ready: none
```

### `next`

`next` prints the current card again and keeps the journey as it is.

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan next journey.json
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
number Policy.contents: What are your belongings worth?
  section: Home
  help: Count furniture, clothing and electronics.
  min: 10000
  max: 100000
ready: none
```

### `skip`

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan skip journey.json Policy.contents
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
choice Policy.territory: Where is the home?
  section: Home
  asked by: Renters
  still settled if skipped: 1
  - "metro": Metro
  - "suburban": Suburban
  - "rural": Rural
ready: none
```

A skipped card comes back after the other cards, with the line `(skipped before)`.

### `search`

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan search journey.json fire --path Policy.construction
```

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

| Option | Meaning |
| - | - |
| `--path PATH` | the path of the card |
| `--list NAME` | the `list` of a `search` card |
| `--limit COUNT` | the largest count of answers; the default is 20 |

Each line holds the JSON value of an answer, then its label. A query that matches zero answers prints `no match`.

### Card text

A card prints as these lines. A line appears only when the card has the field:

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
KIND PATH: QUESTION
  section: SECTION
  help: HELP
  error: MESSAGE
  format: FORMAT
  pattern: PATTERN
  key: FIELD
  list: NAME
  asked by: TITLE, TITLE
  why TITLE: REASON
  min: NUMBER
  max: NUMBER
  - VALUE: LABEL
  (skipped before)
ready: ID, ID
```

* A document card prints `document:` and its kind as the path, and an empty question.
* `asked by` appears when some open rater leaves the input out of its contract.
* `ready` lists each settled rater, or `none`.
* When the plan has zero open cards, the command prints `done: every rater is settled` and the `ready` line.

### JSON output and exit status

With `--json`, a command prints one compact JSON object with sorted keys: `"ok": true` and the fields of [the plan](#the-plan).
`rslc plan search --json` prints `"ok": true` and `results`, a list of objects with `value` and `label`.
A command that fails prints `"ok": false` and an `error` object with a `message` and a `type`.

| Exit status | Meaning |
| - | - |
| 0 | the command ran |
| 1 | the journey file or a rater is missing |
| 2 | the command line is wrong, such as a `VALUE` that fails to parse as JSON |

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc plan next missing.json
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc: error: journey file not found: missing.json
```

## The journey file

The journey file is JSON. `rslc plan` keeps the whole state of a journey in it:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "raters": {
    "renters": "renters",
    "basic": "basic"
  },
  "titles": {
    "renters": "Renters",
    "basic": "Renters Basic"
  },
  "sections": ["Insurance", "Home"],
  "facts": {
    "Policy": {
      "insured_now": true
    }
  },
  "skipped": ["Policy.contents"],
  "after": "Policy.insured_now",
  "offered": ["declarations_page"]
}
```

| Field | Value |
| - | - |
| `raters` | the folder of each rater, by ID, in the order of `--rater` |
| `titles` | the title of each rater, by ID |
| `sections` | the section order |
| `facts` | each answer so far, as one request |
| `skipped` | the paths put off, in the order of the skips |
| `after` | the path answered last, or `null` |
| `offered` | the document kinds offered so far |

The facts are one request for every rater. Each rater receives only the inputs that its contract declares.

## `rslc.journey`

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

import rslc
from rslc.journey import Journey, answer

journey = Journey(
    {"renters": rslc.PreparedRater("renters"), "basic": rslc.PreparedRater("basic")},
    titles={"renters": "Renters", "basic": "Renters Basic"},
    sections=["Insurance", "Home"],
)
```

`Journey(raters, *, titles=None, sections=(), decoders=None)` holds a set of raters:

| Argument | Value |
| - | - |
| `raters` | a mapping from an ID to a `rslc.PreparedRater`, in the order the plan reports them |
| `titles` | a mapping from an ID to the name that a card shows |
| `sections` | the section order |
| `decoders` | a mapping from a source name to a function; `decode` calls it with a typed answer |

| Attribute | Value |
| - | - |
| `raters` | the raters, by ID |
| `sections` | the section order, as a list |
| `decoders` | the decoders, by source name |
| `contracts` | the input contract of each rater, with its title as `x-title` |

| Method | Returns |
| - | - |
| `plan(facts, *, skipped=(), after=None, offered=(), as_of=None, fills=(), confirmed=())` | [the plan](#the-plan) for the facts, as a `dict` |
| `evaluations(facts)` | the [result](https://docs.canary.effectiveai.app/rsl/reference/results.md#results-of-evaluate) of each rater, by ID |
| `programs(facts)` | a list with the `id`, `contract` and `evaluation` of each rater |
| `search(facts, query, *, path=None, list_id=None, limit=None)` | the answers of a card that match `query`, best match first |
| `decode(facts, path, typed)` | a copy of the facts with the inputs that a decoder fills from `typed` |

The arguments of `plan`:

| Argument | Value |
| - | - |
| `facts` | each answer so far, as one request, such as `{"Policy": {"contents": 30000}}` |
| `skipped` | the paths put off, in order |
| `after` | the path answered last; the planner stays in its section |
| `offered` | the document kinds offered so far |
| `as_of` | a date as `YYYY-MM-DD`: the `{look_back}` of a question and the `today` of a date bound |
| `fills` | what your program fills from an answer: a list of [fills](#fills) |
| `confirmed` | the paths of the [choices](#choices) that the person has confirmed |

The module has two functions:

| Function | Returns |
| - | - |
| `answer(facts, path, value)` | a copy of the facts with the value at the path; it creates the records on the way |
| `vin_decoder(path)` | a decoder for the source `vin_decode`, over a vPIC vehicle database in SQLite form |

`decode` fills an input that the rater declares with `filled_by`. It fills the input only when the facts lack an answer and the contract admits the decoded value.

## The plan

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
facts = {"Policy": {"contents": 30000}}
step = journey.plan(facts, after="Policy.contents")
print(sorted(step))
# ['changes', 'choices', 'issues', 'names', 'next', 'open', 'progress', 'questions', 'ready', 'screens', 'standings', 'task']
```

| Field | Value |
| - | - |
| `task` | the one thing the person does next: a [task](#task) |
| `next` | the [card](#card) to show, or `null` when `questions` is empty |
| `questions` | each open card, in asking order; `next` is the first |
| `screens` | the open cards in groups that a host shows together: a list of [screens](#screen) |
| `ready` | the ID of each settled rater |
| `progress` | how much is left: see [readiness](#readiness) |
| `open` | each open input once, with the raters that need it: see [readiness](#readiness) |
| `choices` | each choice input, such as a deductible: a list of [choices](#choices) |
| `changes` | each rater whose status differs from the status before: a list of [changes](#changes) |
| `issues` | what is wrong with the facts as given: a list of [issues](#issues) |
| `names` | the label of each labelled list item, by its path, such as `Policy.vehicles[0]` |
| `standings` | where each rater stands, for every task: a list of [standings](#readiness) |
| `implied` | the answers that the facts imply: a list of [implied answers](#implied-answers) |
| `priced_on` | the date that each rater prices for, by ID: the answer to its `priced_on` input |

`implied` and `priced_on` appear only when they hold an entry.

A settled rater has outcomes or a final refusal. `Journey.evaluations` returns which.
A plan with a `null` card can still hold a `failed` rater, so read `standings` before you show a result.

### Task

`task` has a `kind` and the fields of that kind:

| `kind` | When | Fields |
| - | - | - |
| `answer_screen` | at least one open card is still in turn | `screen`: the first [screen](#screen) |
| `confirm_choices` | each open card is put off, and a choice lacks a confirmation or has a difference | `unconfirmed`, `defaulted`, `differences` |
| `review_results` | each open card is put off, and each choice is confirmed and free of differences | `programs`: the `standings` of the plan |

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
done = {"Policy": {"insured_now": False, "contents": 30000, "territory": "rural", "has_dog": False}}
print(journey.plan(done)["task"])
# {'kind': 'confirm_choices', 'unconfirmed': ['Policy.deductible'], 'defaulted': ['Policy.deductible'], 'differences': []}
print(journey.plan(done, confirmed=["Policy.deductible"])["task"])
# {'kind': 'review_results', 'programs': [{'id': 'renters', 'state': {'kind': 'priced', 'pending': []}, 'status': 'priced', 'provisional': False}, {'id': 'basic', 'state': {'kind': 'priced', 'pending': []}, 'status': 'priced', 'provisional': False}]}
```

| Field of `confirm_choices` | Value |
| - | - |
| `unconfirmed` | the paths of the choices that lack a confirmation |
| `defaulted` | among them, the paths whose value is the rater's default |
| `differences` | each choice that some rater prices at another value than the person asked for |

| Field of a difference | Value |
| - | - |
| `path` | the path of the choice |
| `asked` | the value the person asked for |
| `programs` | the IDs of the raters that sell other values |
| `options` | the nearest values those raters sell, lowest first |
| `suggested` | the value those raters price until the person picks one |
| `error` | for a choice that those raters refuse, the message |
| `code` | for a choice that those raters refuse, the code |
| `refused` | the value that the planner priced those raters at |

A rater that refuses the value of a choice is a difference too, for a confirmed choice as well. Its `options` are the other values that the rater sells, and `suggested` stays out:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
refused = journey.plan({"Policy": {**done["Policy"], "deductible": 300}}, confirmed=["Policy.deductible"])
print(refused["task"]["differences"])
# [{'path': 'Policy.deductible', 'asked': 300, 'programs': ['renters'], 'options': [250, 1000], 'error': 'deductible is not one of its declared values', 'code': 'INPUT-ENUM', 'refused': 500}]
```

`rslc plan` passes an empty `confirmed`. For a rater with a choice, its last task is `confirm_choices`.

### Card

A `choice` card that one of two raters asks:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(json.dumps(step["next"], indent=2))
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "kind": "choice",
  "path": "Policy.territory",
  "section": "Home",
  "screen": "Policy",
  "text": "Where is the home?",
  "why": {
    "asked_by": ["Renters"],
    "blocks": ["Renters"],
    "reasons": {},
    "still_settled": 1
  },
  "choices": [
    {
      "value": "metro",
      "label": "Metro"
    },
    {
      "value": "suburban",
      "label": "Suburban"
    },
    {
      "value": "rural",
      "label": "Rural"
    }
  ],
  "more": true
}
```

A `number` card, after the person has declined the document:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
with_policy = {"Policy": {"insured_now": True}}
card = journey.plan(with_policy, after="Policy.insured_now", offered=["declarations_page"])["next"]
print(json.dumps(card, indent=2))
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "kind": "number",
  "path": "Policy.contents",
  "section": "Home",
  "screen": "Policy",
  "text": "What are your belongings worth?",
  "why": {
    "asked_by": "all",
    "blocks": ["Renters", "Renters Basic"],
    "reasons": {},
    "still_settled": null
  },
  "help": "Count furniture, clothing and electronics.",
  "evidence": [
    {
      "kind": "declarations_page",
      "label": "declarations page"
    }
  ],
  "min": 10000,
  "max": 100000,
  "whole": true
}
```

Each card has `kind`, `path`, `screen` and `text`. Every other field appears only when it applies.

| Field | Value |
| - | - |
| `kind` | how a host takes the answer: one of the [kinds](#card-kinds) |
| `path` | where the answer goes in the facts; for a document, `document:` and its kind |
| `section` | the section of the card, from the `group` annotation |
| `screen` | the path of the record the card belongs to, such as `Policy`; empty for a document |
| `screen_name` | the label of the list item that the screen shows |
| `text` | the question, from the `ask` annotation, with its placeholders filled |
| `short` | a short label for the answer in a summary, from the `short` annotation |
| `template` | the question with `{this}` left for the host to fill |
| `this` | the list item that `{this}` names: its `path` and the planner's `label` |
| `help` | the help text, from the `help` annotation; for a document, its label |
| `help_template` | the help text with `{this}` left for the host to fill |
| `evidence` | the documents that print the answer, from `found_on`: a list of `kind` and `label` |
| `why` | who asks the question: see the next table |
| `because` | for a follow-up input, the answer that made it apply: its `path` and the answer as `shown` |
| `related` | for an input that refines another, that other answer: its `path` and the answer as `shown` |
| `waits_on` | for an input that refines an unanswered input, the path of that input |
| `choices` | the answers of a `choice` or `multi` card: a list of `value`, `label` and, for an explained value, `help` |
| `more` | `true` when an answer outside `choices` is valid; the host takes it as typed |
| `list` | the name of the list that a `search` card searches; for a `more` card, the path of its list |
| `format` | the format of the answer: `vin`, `postal-code`, `money`, `year`, `country`, `subdivision` or `date` |
| `precision` | for a `date`, `month` when the person picks a month; the answer is the first day of that month |
| `picker` | how a host shows the answers of a `search` card, from the `picker` annotation |
| `add_label` | for a `list` or `more` card, the words of the button that adds an item |
| `item_name` | for a `list` or `more` card, the label of a name that the host keeps for each item |
| `pattern` | the pattern that a `code` answer matches |
| `min`, `max` | the smallest and the largest answer; for a `list`, the fewest and the most items |
| `whole` | `true` when a `number` takes whole numbers only |
| `key` | for a `list`, the field that names each item |
| `nullable` | `true` when `null` is an answer too |
| `you` | for a `list`, the `field` and `value` that mark the item of the person who answers |
| `revisit` | `true` when the person skipped the card before |
| `error` | the message of the refusal, when a rater refused the answer given |
| `error_code` | the code of that refusal |
| `refused_by` | the IDs of the raters that refused the answer given |
| `document` | the kind of the document, for a `document` card |
| `answers` | for a `document` card, the count of open questions that the document answers |
| `items` | for a `more` card, the items of the list: a list of `path` and `name` |
| `suggested` | a suggested answer: `value`, `label`, `from` (the path it comes from), `name` and, for a decoder, `source` |
| `checklist` | the name of the checklist that the card is a row of |
| `statement` | the words of a checklist row |
| `none_answer` | an answer that sets a `Bool` input to `false`: its `label` and `gate`, the path of that input |

A card takes its wording and its limits from the first rater of the journey that declares the input.

The `min` and `max` of a `date` card also apply the `earliest` and `latest` annotations of the input. A bound of `today` reads `as_of`.

The fields of `why`:

| Field | Value |
| - | - |
| `asked_by` | `"all"` when each open rater declares the input; otherwise the titles of those that do |
| `blocks` | the titles of the raters whose result waits on the answer |
| `reasons` | the reason of each rater that gives one, by title, from the `why` annotation |
| `still_settled` | the count of other titles, open or priced, whose result ignores the answer; `null` for zero |

Raters with one title count once in `still_settled`. The planner ends each reason with a full stop.

### Card kinds

| `kind` | The answer |
| - | - |
| `yesno` | `true` or `false` |
| `choice` | one `value` of `choices`; a card lists 12 choices at most |
| `search` | one value of a longer list; `rslc plan search` finds it |
| `multi` | a list of values of `choices` |
| `list` | a list of records, one object for each item, each with its `key` field |
| `number` | a number between `min` and `max` |
| `date` | a date as text, `YYYY-MM-DD` |
| `code` | text that matches `pattern` |
| `include` | for an optional record: `{}` to include it, `null` to leave it out |
| `document` | an offer to read a document; the host saves the answers that it reads |
| `more` | whether a list is complete: `true` at the card's path closes the list |

The planner offers a document when the person has it and it prints an open answer:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
card = journey.plan(with_policy, after="Policy.insured_now")["next"]
print(card)
# {'kind': 'document', 'path': 'document:declarations_page', 'section': 'Home', 'screen': '', 'text': '', 'help': 'declarations page', 'document': 'declarations_page', 'answers': 1}
```

### Screen

A screen holds the cards that a host shows together:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
for screen in step["screens"]:
    print(screen["id"], screen["kind"], [card["path"] for card in screen["cards"]])
# Policy item ['Policy.territory', 'Policy.construction', 'Policy.insured_now', 'Policy.has_dog']
```

| Field | Value |
| - | - |
| `id` | the path of the record for an `item` screen; the planner's own ID for the other kinds |
| `kind` | one of the screen kinds below |
| `name` | the label of the list item, for an `item` screen |
| `cards` | the cards of the screen, in asking order |

| `kind` | Holds |
| - | - |
| `item` | the questions of one record |
| `list` | the question that adds the items of a list |
| `checklist` | the `Bool` rows of one named checklist |
| `assign` | one input on each item of a list that names an item of another list |
| `document` | the documents on offer |
| `rows` | one question asked of each item of a list |

### Choices

A choice is something the person buys, such as a deductible. It carries a default, so the planner lists it under `choices` and leaves it out of the cards:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
choice = step["choices"][0]
print({key: value for key, value in choice.items() if key != "steps"})
# {'path': 'Policy.deductible', 'section': 'extras', 'text': 'Which deductible do you want?', 'kind': 'choice', 'nullable': False, 'current': None, 'group': 'extras', 'label': 'Deductible'}
print(choice["steps"][0])
# {'value': 250, 'label': '250', 'programs': ['Renters'], 'program_ids': ['renters'], 'common': True}
```

| Field | Value |
| - | - |
| `path` | where the choice goes in the facts |
| `section` | the section of the choice: its `group` |
| `text` | the question |
| `help` | the help text |
| `kind` | how a host takes the answer: a card kind |
| `steps` | each value that some rater sells, in order |
| `nullable` | `true` when some rater takes `null`, for a person who declines the choice |
| `current` | the value in the facts, or `null` |
| `shape` | the shape of the choice, from the `shape` annotation, such as `deductible` |
| `code` | the shared code of what the choice sells, from the `coverage` annotation |
| `parts` | for a pair of limits, the names of its two fields, in order |
| `group` | the part of a coverage screen: `policy`, `liability`, `car` or `extras` |
| `label` | a short name for the choice: its `short` annotation, or its name in words |

Each step has `value`, `label`, `programs` (the titles of the raters that sell it), `program_ids` and `common`.
`common` is `true` when each rater that declares the choice sells the step.

The compiler sets the group of a choice. [Input contract](https://docs.canary.effectiveai.app/rsl/reference/input-contract.md#choice-keys) gives the rule.
The choices arrive by section, then by the count of titles that declare them, largest first.
A choice outside a list then comes before a choice of a list item.
A choice that a condition opens comes after the choices that its condition reads.

### Readiness

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
skipped = {"Policy": {"contents": 30000, "territory": "rural", "has_dog": False}}
step = journey.plan(skipped, skipped=["Policy.insured_now"], confirmed=["Policy.deductible"])
print(step["ready"])
# ['basic']
print(step["progress"])
# {'left': 1, 'programs': [{'id': 'renters', 'left': 0}], 'counted': True}
print(step["open"])
# [{'path': 'Policy.insured_now', 'programs': ['renters'], 'deferred': True}]
print(step["standings"])
# [{'id': 'renters', 'state': {'kind': 'needs', 'items': [{'path': 'Policy.insured_now', 'kind': 'answer', 'deferred': True}]}, 'status': 'open', 'provisional': False, 'waiting_on': ['Policy.insured_now'], 'deferred': ['Policy.insured_now']}, {'id': 'basic', 'state': {'kind': 'priced', 'pending': []}, 'status': 'priced', 'provisional': False}]
```

| Field of `progress` | Value |
| - | - |
| `left` | the count of open cards for all raters, with the cards put off |
| `programs` | for each open rater, its `id` and `left`, the count of its cards still in turn |
| `counted` | `false` while a list that sets the counts is still open; a host then hides them |

| Field of an `open` entry | Value |
| - | - |
| `path` | the path of the open input |
| `programs` | the IDs of the raters that need it |
| `deferred` | `true` when the person put the card off |

`standings` holds one standing for each rater, in every plan. The `programs` of a `review_results` task is the same list.

| Field of a standing | Value |
| - | - |
| `id` | the ID of the rater |
| `state` | the one [state](#state) of the rater; the fields below repeat parts of it |
| `status` | `priced`, `declined`, `invalid`, `open` or `failed` |
| `provisional` | `true` while the `pending` of a `priced` state holds a path |
| `waiting_on` | the path of each item of a `needs` state |
| `deferred` | among them, the paths that the person put off |

| `status` | The rater |
| - | - |
| `priced` | is settled with outcomes, with each of its choices confirmed |
| `declined` | is settled with a refusal for a business rule |
| `invalid` | needs a value in place of one that it refused |
| `open` | needs answers or choices, and has refused zero values |
| `failed` | lacks a result, and the planner has zero cards or choices for it |

### State

Each rater is in one state. Every state except `priced` names the way forward:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
for standing in journey.plan({"Policy": {"contents": 5000}})["standings"]:
    print(standing["id"], standing["state"])
# renters {'kind': 'needs', 'items': [{'path': 'Policy.contents', 'kind': 'answer', 'deferred': False, 'error': {'code': 'INPUT-RANGE', 'message': 'contents is below declared minimum 10000'}}]}
# basic {'kind': 'declined', 'reason': {'code': 'CONTENTS-LIMIT', 'message': 'The basic program covers belongings up to $50,000.'}, 'change': ['Policy.contents']}
```

| `kind` | The rater | Fields |
| - | - | - |
| `priced` | is settled with outcomes, with each of its choices confirmed | `pending`: the paths of the open cards of its lists that carry `complete` |
| `needs` | waits on the person | `items`: one item or more, the answers first, then the choices |
| `declined` | is settled with a refusal for a business rule | `reason`, and `change`: the paths of the answers that the refusal names |
| `failed` | lacks a result, with zero cards or choices for it | `reason` |

A settled rater with a choice that lacks a confirmation is in `needs`, with one `choice` item for each such path.
So a rater moves from `priced` back to `needs` only when an earlier answer changes.

`pending` covers a list of the root record that has a `complete` annotation and lacks `inline`. Its path is the `more` card of the list, or the `list` card.
A price with a path in `pending` can still change with the next item of that list.

| Field of an item | Value |
| - | - |
| `path` | the path of a card in `questions`, or of an entry of `choices` |
| `kind` | `answer` for a card; `choice` for an entry of `choices` |
| `deferred` | `true` when the person put the card off |
| `error` | a reason, when the rater refused the value given at the path |

A reason has a `code` and a `message`. The `code` is the code of the refusal, or `null` for a refusal that lacks one.
A `failed` state has the code `NOTHING_TO_ASK`: the rater lacks a result, and the planner has zero cards or choices to offer for it.
A refused key of a list item is one such case, and the host corrects the key.

### Implied answers

Some answers follow from other answers. The planner plans with them and lists each one under `implied`, with its `path` and `value`:

* A list item that lacks its key takes the next whole number after the largest key of the list.
* An answered follow-up that has a `none_answer` sets its unanswered `Bool` input to `true`.

`Journey.plan` evaluates each rater on the facts as given. Write each implied answer into the facts with `answer`, then plan again.
Until then, a rater that waits on an implied `Bool` input has the state `failed`.
`rslc plan` keeps the journey file to the answers of its commands, so run `rslc plan answer` for an implied `Bool` input.

### Changes

A change reports a rater whose status differs from an earlier status that the host supplies.
`rslc plan` and `Journey.plan` supply zero earlier statuses, so their `changes` is an empty list.

| Field | Value |
| - | - |
| `program` | the ID of the rater |
| `from` | its status before |
| `to` | its status now |
| `because` | for a refusal, the path of the refused answer |
| `reason` | for a refusal, its message |
| `code` | for a refusal, its code, such as `CONTENTS-LIMIT` |

### Issues

An issue names one answer in the facts that every rater rejects:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
wrong = {"Policy": {"contents": 5000, "territory": "coastal", "pool": True}}
for issue in journey.plan(wrong)["issues"]:
    print(issue)
# {'path': 'Policy.contents', 'code': 'OUT_OF_RANGE', 'expected': {'min': 10000, 'max': 100000}}
# {'path': 'Policy.pool', 'code': 'UNKNOWN_PATH', 'candidates': []}
# {'path': 'Policy.territory', 'code': 'NOT_A_CHOICE', 'expected': {'choices': [{'value': 'metro', 'label': 'Metro'}, {'value': 'suburban', 'label': 'Suburban'}, {'value': 'rural', 'label': 'Rural'}]}}
```

| Field | Value |
| - | - |
| `path` | the path of the answer |
| `code` | one of the codes below |
| `expected` | what a valid answer looks like, by code |
| `candidates` | declared paths near an unknown path, or the stored values that an answer may mean |

| `code` | The answer | `expected` |
| - | - | - |
| `UNKNOWN_PATH` | sits at a path that each contract leaves out | |
| `WRONG_TYPE` | has a type that each rater rejects | `kind`, `format` |
| `NOT_A_CHOICE` | is outside the choices of each rater | `choices` |
| `BAD_FORMAT` | fails the pattern or format of each rater | `pattern`, `format`, `example` |
| `OUT_OF_RANGE` | is outside the range of each rater | `min`, `max`: the widest range |
| `TOO_MANY` | is a list longer than each rater takes | `max` |

An answer that one rater takes raises zero issues. A rater with a narrower rule refuses on its own.

### Fills

A fill tells the planner that your program fills inputs from one answer. The planner then asks that question first.

| Field | Value |
| - | - |
| `source` | your name for where the values come from |
| `from` | the path of the answer, with `[*]` for each item of a list |
| `fills` | the names of the inputs of the same record that the answer fills |

A rater also declares a fill itself, with the `filled_by` annotation. `Journey.decode` then fills the input.

### Search results

`Journey.search` and `rslc plan search --json` return a list of answers, each with `value` and `label`:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
print(journey.search(facts, "f", path="Policy.construction"))
# [{'value': 'fire_resistive', 'label': 'Fire resistive'}, {'value': 'frame', 'label': 'Frame'}]
```

* The search ignores case and reads punctuation as spaces.
* Each word of the query matches the start of a word of a label.
* The search also matches the other names of an answer, from the `aliases` column of an enum file.
* A label equal to the query comes first, then another name equal to the query.
* A label that starts with the query follows, then the other matches of a label, then the other matches of another name.
* The result holds 20 answers at most, or `limit`.
* For a card with a `list`, the search reads the keys of the table from the rater.

## How the planner orders cards

* Raters share an input when they declare it at the same path. One card serves each rater that needs it.
* Sections follow the section order of the journey, then the order in which the contracts first name them.
* The planner stays in the section of `after` while that section has cards in turn.
* A question that fills other inputs comes before those inputs.
* A follow-up question comes after the answer that makes it apply.
* A card put off comes last. A card that only a waiting rater asks moves behind the cards of the other raters.
* The planner offers a document once, on entering a section, when the person has it and it prints an open answer.
* A refused answer returns as the next card, with the refusal message as `error` and its code as `error_code`.
* The key of a list item stays out of the cards. The planner numbers the items: see [implied answers](#implied-answers).
* In a list with a `you` annotation, the item of the person who answers comes before the other items.

## Related

* [Missing answers](https://docs.canary.effectiveai.app/rsl/explanation/missing-answers.md) explains how a rater decides which inputs still matter.
* [Ask the questions a rater needs](https://docs.canary.effectiveai.app/rsl/tutorial/ask-questions.md) writes the annotations that a card shows.


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