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

# Explain a premium

> List the inputs and calculations behind one premium, and read two carriers' steps side by side to explain why their premiums differ.

This page lists the inputs and calculations behind one premium, and explains why two carriers
price one submission differently.

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

<Accordion title="Set up the book and the two carriers">
  The book is the synthetic six-household book in `relativities-examples/ppa/book`. Carrier A
  rates liability and collision per vehicle for a six-month term. Carrier B rates an account for a
  twelve-month term and returns one premium. [Compare carriers](/relativities/how-to/compare-carriers) shows
  how to check what each carrier receives.

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

  import pandas as pd

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

  book = Book.read_csv("relativities-examples/ppa/book", schema=ppa.SCHEMA)


  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"]],
      )


  def carrier_b(book):
      drivers = on_effective_date(book)
      blank = drivers.loc[drivers["first_licensed_date"].isna(), "driver_id"]
      if len(blank):
          raise ValueError(f"driver {blank.iloc[0]} has no first_licensed_date")
      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"]],
      )


  market = Market(
      {
          "a": Program(Rater.load(Path("relativities-examples/ppa/carrier-a.zip")), carrier_a, schema=ppa.SCHEMA),
          "b": Program(Rater.load(Path("relativities-examples/ppa/carrier-b.zip")), carrier_b, schema=ppa.SCHEMA),
      }
  )
  ```
</Accordion>

Explain submission S2 with carrier A's program:

```python theme={null}
explanation = market.programs["a"].explain(book, "S2")
explanation
# Explanation(submission 'S2': succeeded; 14 trace rows)
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
```

Each `input` row is a value the program's `prepare` built, in the carrier's own terms. Each
`calculation` row is a value the rater calculated, in calculation order. The last row is the
premium: \$825 for a six-month term.

To trace an input back to the book, compare `json_in` with `book`. `explanation.json_in` is the
exact input the rater received. `explanation.book` holds S2's facts, with the columns of the
program's schema:

```python theme={null}
explanation.json_in["drivers"]
# [{'age': 45}, {'age': 19}]
explanation.book.drivers[["driver_id", "birth_date"]]
#   driver_id birth_date
# 0        D2 1981-01-15
# 1        D3 2007-01-15
```

`explanation.row` is S2's row of `program.rate`.

## Explain one rater input

To explain an input that you build by hand, such as a filing's rating example, call
`rater.explain` with that input:

```python theme={null}
rater_a = market.programs["a"].rater
rater_a.explain({"drivers": [{"age": 19}], "vehicles": [{"annual_mileage": 12_000, "collision_deductible": 500}]})
#           kind                              name  value
# 0        input                    drivers[0].age     19
# 1        input        vehicles[0].annual_mileage  12000
# 2        input  vehicles[0].collision_deductible    500
# 3  calculation        vehicles[0].mileage_factor      1
# 4  calculation     vehicles[0].premium.liability    375
# 5  calculation     vehicles[0].premium.collision    150
# 6  calculation               young_driver_factor   1.25
# 7  calculation                           premium    525
```

[`Rater.explain`](/relativities/reference/relativities#rater) rates that one input and returns its trace.
For several inputs, pass `row_id` to choose one.

## Read two carriers' steps side by side

Explain the same submission with carrier B's program:

```python theme={null}
market.programs["b"].explain(book, "S2").trace
#           kind                         name  value
# 0        input  operators[0].years_licensed     25
# 1        input  operators[1].years_licensed      2
# 2        input               autos[0].miles   6000
# 3        input       autos[0].comprehensive  False
# 4        input               autos[1].miles   9000
# 5        input       autos[1].comprehensive  False
# 6  calculation                autos[0].base    420
# 7  calculation                autos[1].base    420
# 8  calculation            experience_factor    1.4
# 9  calculation               annual_premium   1176
```

* Carrier A applies a 1.25 young driver factor to liability, because driver D3 is 19.
* Carrier B applies a 1.4 experience factor to the account, because D3 has two years licensed.
* Carrier A adds \$150 of collision on V2 for a six-month term, and carrier B's only coverage input is a comprehensive flag.

## Put both premiums on one term

Rate the book, and double carrier A's six-month premium:

```python theme={null}
results = market.rate(book)
results["annual"] = results["premium"].mul(2).fillna(results["annual_premium"])
results.query("submission_id == 'S2'")[["program", "premium", "annual_premium", "annual"]]
#   program  premium  annual_premium  annual
# 2       a    825.0             NaN  1650.0
# 3       b      NaN          1176.0  1176.0
```

On the annualized basis, carrier A's premium for H2 exceeds carrier B's by $474. Collision on V2
accounts for $300 of that gap.

This comparison includes different coverages. Use [Compare carriers](/relativities/how-to/compare-carriers) to align the coverage basis before interpreting the gap as a price difference.

## Measure what one driver adds

Add a scenario that keeps only driver D2 on S2, and rate both scenarios:

```python theme={null}
def without_driver(book, driver_id):
    drivers = book.drivers
    return book.with_tables(drivers=drivers[drivers["driver_id"] != driver_id])


study = Study(book, scenarios={"only_d2": Step(without_driver, driver_id="D3")})
compared = market.rate(study).query("submission_id == 'S2'")
compared["annual"] = compared["premium"].mul(2).fillna(compared["annual_premium"])
added = compared.pivot(index="program", columns="scenario_id", values="annual")
added["d3_adds"] = added["baseline"] - added["only_d2"]
added
# scenario_id  baseline  only_d2  d3_adds
# program
# a              1650.0   1380.0    270.0
# b              1176.0    840.0    336.0
```

D3 adds $270 a year with carrier A and $336 a year with carrier B. D3 makes the gap \$66 smaller.
To see the steps of one scenario, pass `scenario_id` to `program.explain`.

## Split the gap

Take out one difference at a time: collision first, then D3.

```mermaid theme={null}
flowchart LR
  gap["Gap: 474 a year"] --> collision["Collision, carrier A only: 300"]
  gap --> liability["Liability for D2 alone: 1,080 against 840, so 240"]
  gap --> driver["D3 surcharge: 270 with A, 336 with B, so minus 66"]
```

The split depends on the order. State the order with the exhibit.

## Close the raters

```python theme={null}
for program in market.programs.values():
    program.close()
```


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