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

# Price a deductible change

> Price a collision deductible of 1,000 against 500 with each carrier, pair each vehicle's two premiums, and report each pair that has a gap.

This page prices a collision deductible of $1,000 against $500 with each carrier.

[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>

Write a scenario that raises each $500 collision deductible to $1,000, and rate both scenarios.
A scenario is a function from a book to a changed book. `Step` gives it its parameters. To put
both carriers on a twelve-month term, double carrier A's six-month premium:

```python theme={null}
def raise_deductible(book, before, after):
    coverages = book.vehicle_coverages
    chosen = coverages["coverage_id"].eq("collision") & coverages["deductible"].eq(before)
    coverages.loc[chosen, "deductible"] = after
    return book.with_tables(vehicle_coverages=coverages)


higher = Step(raise_deductible, before=500, after=1_000)
study = Study(book, scenarios={"higher_deductible": higher})
results = market.rate(study)
results["annual"] = results["premium"].mul(2).fillna(results["annual_premium"])
annual = results.pivot(index=["submission_id", "program"], columns="scenario_id", values="annual")
(annual["baseline"] - annual["higher_deductible"]).unstack()
# program            a    b
# submission_id
# S1             150.0  0.0
# S2             150.0  0.0
# S3               0.0  NaN
# S4               NaN  NaN
# S5             150.0  NaN
# S6             150.0  0.0
```

Carrier A saves $150 a year for each submission with a vehicle at a $500 deductible. S3's vehicle
starts at $1,000, so S3 saves $0. Carrier B's synthetic rater accepts years licensed, miles and a
comprehensive flag, so its premium stays the same.

An unchanged premium here reflects the modeled inputs. Confirm the real carrier's deductible rules before drawing a product conclusion.

```mermaid theme={null}
flowchart LR
  book["Book: collision at 500"] --> base["baseline"]
  book --> high["higher_deductible: collision at 1,000"]
  base --> pair["Pair by submission, vehicle and program"]
  high --> pair
  pair --> saving["Saving: baseline minus higher_deductible"]
```

[Books and studies](/relativities/explanation/books-and-studies) describes scenarios.

## Choose the vehicles to compare

Select the vehicles that carry collision at a \$500 deductible in the book:

```python theme={null}
coverages = book.vehicle_coverages
at_500 = coverages["coverage_id"].eq("collision") & coverages["deductible"].eq(500)
affected = coverages.loc[at_500, ["submission_id", "vehicle_id"]]
affected
#    submission_id vehicle_id
# 1             S1         V1
# 4             S2         V2
# 10            S5         V6
# 16            S6         V9
```

Name this population in the exhibit: "vehicles with collision at a \$500 deductible".

## Pair each vehicle's collision premiums

Find each vehicle's position in its submission. Read its collision premium from that entry of
carrier A's `vehicles` outcome:

```python theme={null}
positions = book.vehicles.assign(position=lambda frame: frame.groupby("submission_id").cumcount())
pairs = results.merge(affected.merge(positions[["submission_id", "vehicle_id", "position"]]))
pairs["collision"] = [
    vehicles[position]["premium"]["collision"] if isinstance(vehicles, list) else float("nan")
    for vehicles, position in zip(pairs["vehicles"], pairs["position"])
]
keys = ["submission_id", "vehicle_id", "program"]
premium = pairs.pivot(index=keys, columns="scenario_id", values="collision")
status = pairs.pivot(index=keys, columns="scenario_id", values="status")
comparable = status.eq("succeeded").all(axis=1) & premium.notna().all(axis=1)
premium["saving"] = premium["baseline"] - premium["higher_deductible"]
premium[comparable]
# scenario_id                       baseline  higher_deductible  saving
# submission_id vehicle_id program
# S1            V1         a           150.0               75.0    75.0
# S2            V2         a           150.0               75.0    75.0
# S5            V6         a           150.0               75.0    75.0
# S6            V9         a           150.0               75.0    75.0
```

A pair is comparable when both scenarios succeed and both have a collision premium. Both premiums
of a pair come from one carrier, so they share a term. Carrier A saves \$75 on each vehicle for a
six-month term.

## List the pairs to report as exceptions

Show the baseline row of each pair outside `comparable`:

```python theme={null}
baseline = pairs.query("scenario_id == 'baseline'").set_index(keys)
baseline.loc[~comparable, ["status", "reason_code", "collision"]]
#                                           status     reason_code  collision
# submission_id vehicle_id program
# S1            V1         b             succeeded             NaN        NaN
# S2            V2         b             succeeded             NaN        NaN
# S5            V6         b        mapping_failed  PREPARE_FAILED        NaN
# S6            V9         b             succeeded             NaN        NaN
```

Carrier B returns one account premium, so each of its pairs has `NaN` as its collision premium.
S5's `reason` names D6's blank first licensed date. Report these rows with the exhibit, and label
the saving as carrier A's saving.

## Check which carriers receive a deductible

Search each rater's `inputs` for a deductible field:

```python theme={null}
for name in ("a", "b"):
    paths = market.programs[name].rater.inputs["path"]
    print(name, paths[paths.str.contains("deductible")].tolist())
# a ['vehicles[].collision_deductible']
# b []
```

Carrier B's list is empty. In the exhibit, replace carrier B's \$0 with the label "deductible
outside the modeled inputs".

<Note>
  To check a carrier that rates a fixed set of deductibles, compare the study's deductible with that
  carrier's inputs from `program.explain`.
</Note>

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