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

# Rate your first book

> Read a book of households, rate two programs, explain a premium, and compare premiums on a common basis.

In this tutorial, you rate six households with a synthetic carrier, explain one premium, and add a
second carrier. You finish with a matched premium exhibit and explicit rating exceptions.

```mermaid theme={null}
flowchart LR
  read["1. Read the book"] --> prepare["2. Write prepare"]
  prepare --> rate["3. Rate the book"]
  rate --> explain["4. Explain a premium"]
  explain --> market["5. Add a second carrier"]
  market --> compare["6. Compare on one basis"]
```

## 1. Read the book

Read the synthetic book from its CSV files:

[Install Relativities and download the example data](/relativities/setup) before running this page.

```python theme={null}
from pathlib import Path

from relativities import Book, Market, Program, Rater, ppa

book = Book.read_csv("relativities-examples/ppa/book", schema=ppa.SCHEMA)
book
# Book(6 households, 6 submissions, 7 drivers, 9 vehicles)
book.drivers[["submission_id", "driver_id", "birth_date", "first_licensed_date"]].head(3)
#   submission_id driver_id birth_date first_licensed_date
# 0            S1        D1 1986-01-15          2006-01-15
# 1            S2        D2 1981-01-15          2001-01-15
# 2            S2        D3 2007-01-15          2024-01-15
```

The book holds facts as reported, such as birth dates. Each submission is one household's request
to price.

## 2. Write prepare

Carrier A's rater takes each driver's age, and each vehicle's mileage and collision deductible.
Write a `prepare` function that reads the whole book and returns each submission's rater input,
keyed by `submission_id`:

```python theme={null}
VEHICLE = ["submission_id", "vehicle_id"]


def years(start, on):
    """Whole years from each start date to each on date."""
    before = on.dt.strftime("%m%d") < start.dt.strftime("%m%d")
    return on.dt.year - start.dt.year - before.astype(int)


def on_effective_date(book):
    """Each driver with their submission's effective date."""
    return book.drivers.merge(book.submissions[["submission_id", "effective_date"]])


def nest(book, **lists):
    """Each submission's input: one list of records per keyword, in book order."""
    grouped = {
        name: {sid: rows.drop(columns="submission_id").to_dict("records") for sid, rows in frame.groupby("submission_id", sort=False)}
        for name, frame in lists.items()
    }
    return {sid: {name: grouped[name].get(sid, []) for name in lists} for sid in book.submissions["submission_id"]}


def carrier_a(book):
    drivers = on_effective_date(book)
    drivers["age"] = years(drivers["birth_date"], drivers["effective_date"])
    coverages = book.vehicle_coverages
    collision = coverages[coverages["coverage_id"] == "collision"][[*VEHICLE, "deductible"]]
    vehicles = book.vehicles.merge(collision, on=VEHICLE, how="left")
    vehicles["collision_deductible"] = vehicles["deductible"].fillna(0).astype(int)
    return nest(
        book,
        drivers=drivers[["submission_id", "age"]],
        vehicles=vehicles[["submission_id", "annual_mileage", "collision_deductible"]],
    )


carrier_a(book)["S1"]
# {'drivers': [{'age': 40}], 'vehicles': [{'annual_mileage': 12000, 'collision_deductible': 500}]}
```

`prepare` is plain pandas. Its input for each submission has the shape of the rater's `inputs`.

## 3. Rate the book

Pair the rater with `prepare` in a program, and rate the book. `schema` is the book the program
accepts:

```python theme={null}
carrier_a = Program(
    Rater.load(Path("relativities-examples/ppa/carrier-a.zip")), carrier_a, schema=ppa.SCHEMA
)
rated = carrier_a.rate(book)
rated[["submission_id", "status", "reason_code", "premium"]]
#   submission_id          status  reason_code  premium
# 0            S1       succeeded          NaN    450.0
# 1            S2       succeeded          NaN    825.0
# 2            S3       succeeded          NaN    375.0
# 3            S4  invalid_inputs  INPUT-RANGE      NaN
# 4            S5       succeeded          NaN    390.0
# 5            S6       succeeded          NaN   1290.0
```

Each submission keeps its row. Carrier A's premium is for a six-month term. S4's vehicle exceeds
the rater's mileage limit, so S4 has the status `invalid_inputs` and the premium `NaN`.

`carrier_a.schema` lists each table and column the program accepts. Read the drivers' dates:

```python theme={null}
schema = carrier_a.schema.to_pandas()
schema.query("table == 'drivers' and type == 'date'")[["column", "type", "required"]]
#                    column  type  required
# 11             birth_date  date     False
# 14    first_licensed_date  date     False
# 15  defensive_course_date  date     False
```

A program checks each book against its schema before `prepare` runs, and gives `prepare` only
the schema's columns.

## 4. Explain a premium

Explain S2's premium of \$825:

```python theme={null}
explanation = carrier_a.explain(book, "S2")
explanation.trace
#            kind                              name value
# 0         input                    drivers[0].age    45
# 1         input                    drivers[1].age    19
# 2         input        vehicles[0].annual_mileage  6000
# 3         input  vehicles[0].collision_deductible   500
# 4         input        vehicles[1].annual_mileage  9000
# 5         input  vehicles[1].collision_deductible     0
# 6   calculation        vehicles[0].mileage_factor   0.8
# 7   calculation     vehicles[0].premium.liability   300
# 8   calculation     vehicles[0].premium.collision   150
# 9   calculation        vehicles[1].mileage_factor     1
# 10  calculation     vehicles[1].premium.liability   375
# 11  calculation     vehicles[1].premium.collision     0
# 12  calculation               young_driver_factor  1.25
# 13  calculation                           premium   825
```

The input rows show what the rater received. The calculation rows show each step to the premium.
The 19-year-old driver brings a young driver factor of 1.25. `explanation.json_in` is the input
that `prepare` built, and `explanation.book` holds S2's facts.

## 5. Add a second carrier

Carrier B's rater takes each driver's years licensed, each vehicle's miles, and a comprehensive
flag. Write its `prepare`, and rate the book with both carriers in a market:

```python theme={null}
def carrier_b(book):
    drivers = on_effective_date(book)
    drivers["years_licensed"] = years(drivers["first_licensed_date"], drivers["effective_date"])
    coverages = book.vehicle_coverages
    comprehensive = coverages[coverages["coverage_id"] == "comprehensive"][VEHICLE]
    autos = book.vehicles.merge(comprehensive.assign(comprehensive=True), on=VEHICLE, how="left")
    autos["comprehensive"] = autos["comprehensive"].eq(True)
    autos = autos.rename(columns={"annual_mileage": "miles"})
    return nest(
        book,
        operators=drivers[["submission_id", "years_licensed"]],
        autos=autos[["submission_id", "miles", "comprehensive"]],
    )


carrier_b = Program(
    Rater.load(Path("relativities-examples/ppa/carrier-b.zip")), carrier_b, schema=ppa.SCHEMA
)
market = Market({"a": carrier_a, "b": carrier_b})
market.rate(book)[["submission_id", "program", "status", "premium", "annual_premium"]].head(6)
#   submission_id program     status  premium  annual_premium
# 0            S1       a  succeeded    450.0             NaN
# 1            S1       b  succeeded      NaN           570.0
# 2            S2       a  succeeded    825.0             NaN
# 3            S2       b  succeeded      NaN          1176.0
# 4            S3       a  succeeded    375.0             NaN
# 5            S3       b    refused      NaN             NaN
```

The market adds a `program` column. Each carrier's outcome keeps the name its rater gives it:
`premium` for six months from carrier A, and `annual_premium` for twelve months from carrier B.

## 6. Compare on one basis

Select liability only, then annualize carrier A's six-month premium for this illustrative comparison:

```python theme={null}
coverages = book.vehicle_coverages
liability = book.with_tables(vehicle_coverages=coverages.query("coverage_id == 'liability'"))
comparison = market.rate(liability)
comparison["annual"] = comparison["premium"].mul(2).where(
    comparison["program"] == "a", comparison["annual_premium"]
)
comparison.pivot(index="submission_id", columns="program", values="annual")
# program             a       b
# submission_id
# S1              600.0   480.0
# S2             1350.0  1176.0
# S3              600.0     NaN
# S4                NaN     NaN
# S5              480.0     NaN
# S6             1680.0  1380.0
```

Carrier B has the lower premium on all three matched submissions. Three other submissions have at least one unrated program.
Retain their statuses and reasons with the exhibit. A calculated premium is a rating result, and underwriting appetite needs its own evidence.

For a market study, align limits, deductibles, fees, effective dates, and term conversion too.
[Compare carriers](/relativities/how-to/compare-carriers) adds mapping checks and a segment summary with explicit population counts.

```python theme={null}
market.programs["a"].close()
market.programs["b"].close()
```

## What you did

You prepared shared facts, wrote `prepare` for two raters, explained a premium, and compared a matched population. To go further:

* [Compare carriers](/relativities/how-to/compare-carriers) on one basis.
* [Price a deductible change](/relativities/how-to/price-a-deductible-change) with a study.
* Read [Books and studies](/relativities/explanation/books-and-studies) and
  [Programs and schemas](/relativities/explanation/programs-and-schemas).


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