> ## Documentation Index
> Fetch the complete documentation index at: https://docs.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Programs and schemas

> Why a program pairs a rater with a schema and a prepare function, what it guarantees, and how a market lines programs up.

Each carrier's rater defines its own input schema and premium outputs. A book stores household and vehicle facts in a shared schema.
A program pairs a rater with the `Schema` of the book it accepts and a `prepare` function that builds the rater's inputs from that book.

```mermaid theme={null}
flowchart LR
  book["Book or one version of a study"] --> schema["Schema: checked, then cut to its columns"]
  schema --> prepare["prepare: a chunk of the book"]
  prepare --> json_in["json_in: one per submission"]
  json_in --> rater["Rater: its own inputs"]
  rater --> row["Row: status and the rater's outcomes"]
```

## The schema is the program's shape

`program.schema` is a [`Schema`](/relativities/reference/relativities#schema), the same type as
`book.schema`. It lists each table and column the program accepts, with its type and whether it
is required. A line's schema, such as `ppa.SCHEMA`, accepts the line's core columns. To read a
column of your own, declare it with `with_columns`:

```python theme={null}
from relativities import Column, ppa

SCORED = ppa.SCHEMA.with_columns(drivers={"credit_score": Column("int", required=True)})
```

Read the schema in pandas with `program.schema.to_pandas()`, or print it.

## prepare holds each carrier-specific rule

`prepare` is a function from a book to `{submission_id: json_in}`. Each `json_in` has the shape
of `rater.inputs`, and it depends only on that submission's rows. `rate` gives `prepare` the book
in chunks, and a failing submission is left out of the book, so this rule keeps each input the
same however the book is split. Relativities converts its values to JSON: dates become ISO text, numpy
numbers become Python numbers, a missing field is left out, and a missing list element stays as `None`.

The book stays the same for each carrier, and a change to one carrier touches its own `prepare`.
For a carrier's own estimate of an empty fact, write a new `prepare` that fills the fact and calls
the old one. The book keeps the fact as reported, so one carrier's estimate stays out of another
carrier's input.

## What a program guarantees

* **The schema is enforced.** `rate` and `explain` check the book against the program's schema. A
  missing required column, or a value of the wrong type, raises `BookError` before `prepare` runs.
* **The schema is all `prepare` reads.** `prepare` gets a copy of the book with only the schema's
  tables and columns. A column outside the schema raises `KeyError` in `prepare`, so
  `program.schema` lists every column the program can depend on.
* **One bad submission fails only itself.** When `prepare` raises, Relativities halves the book
  until it finds each submission that `prepare` fails on alone. Those rows get the status
  `mapping_failed` and the reason code `PREPARE_FAILED`, with the function's name and the
  exception as the reason. `prepare` then runs again on the rest of the book, and the other rows
  rate. A clean book runs `prepare` once.
* **A submission that `prepare` leaves out stays in the results.** Its status is
  `mapping_failed`, with the reason code `NOT_PREPARED`.
* **Explain matches rate.** `explain` runs `prepare` on the chunk that `rate` gives it, so its
  `json_in` is the exact input rated and its `row` is the row `rate` gives. Its `book` holds the
  submission's facts, with the schema's columns.

Keep failed mappings visible in your analysis and investigate the underlying data or `prepare` logic.

## Result columns carry the rater's own names

`rate` returns the rater's `RatingOutcomes` as columns, with the names the rater gives them. The
rater defines what it returns, so the result shows exactly that. A total is the carrier's own
total, and a premium carries the carrier's own term.

In a market, two programs share a column when their raters give an outcome the same name. Carrier
A's six-month `premium` and carrier B's twelve-month `annual_premium` stay in separate columns.
The choice of a comparison basis, such as an annual premium, stays with you, in pandas.

## Each layer adds its key

| You rate | With | Rows are keyed by |
| - | - | - |
| Book | Program | `submission_id` |
| Study | Program | `submission_id`, `scenario_id`, `draw_id` |
| Book | Market | `submission_id`, `program` |
| Study | Market | `submission_id`, `scenario_id`, `draw_id`, `program` |

Analysis code for one program works on a market's table too: it groups by one more column.

## One rating path

`program.rate` and `market.rate` use the same path as `rater.rate`: parallel native batches. The
unit of work is one program and one chunk of up to 10,000 submissions from one version of the
book. Units run on two threads, and a study builds one version at a time, so memory holds a few
chunks, plus the result rows.

Each input depends only on its own submission's rows, so a chunk gives the same rows as the whole
book. `explain` runs `prepare` on the chunk that holds the submission, then rates the one
submission again with each calculation kept. Rating is deterministic, so the explanation matches
the rated row. `market.explain` gives each program's explanation side by side.

## Mapping quality is part of rating quality

A correctly implemented algorithm still needs the right inputs. Vehicle symbols, territory assignments, discounts, and coverage selections can change a comparison materially.
`explain` exposes the inputs passed to RSL and its calculations. Review the Python `prepare` function to establish each mapping's source and assumptions.
Use [Validate a program](/relativities/how-to/validate-a-program) to reconcile independent cases before analyzing the full book.


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