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

# Measure a rate change

> Edit a rater's rating table exactly, build the proposed rater, and rate a book with both raters to see who pays more.

Measure how a proposed table change affects premiums and competitive position on the same risks.
This example raises comprehensive factors by 5% and separates coverage-level changes from changes in the total premium:

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

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

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

rater = Rater.load(Path("relativities-examples/table-rater.zip"))
factors = rater.tables["VehicleAgeFactor"]
comprehensive = factors["coverage"] == "comprehensive"
factors.loc[comprehensive, "factor"] = factors.loc[comprehensive, "factor"] * Decimal("1.05")
proposed = rater.with_tables({"VehicleAgeFactor": factors}, path="proposed.zip")

cars = [{"effective_year": 2026, "vehicles": [{"model_year": year}]} for year in (2024, 2021, 2010)]
current = rater.rate(cars).to_pandas()["premium"]
changed = proposed.rate(cars).to_pandas()["premium"]
(changed - current).tolist()  # [26.0, 20.0, 14.0]
```

One car in each vehicle age band pays 5% more for comprehensive: $26, $20 and $14. The synthetic
rater bands each car by age, as new, mid or old. It multiplies a base rate of $400 by a factor from
`VehicleAgeFactor`, for liability and for comprehensive.

## Read a rating table

List the rater's tables, and read one as a DataFrame:

```python theme={null}
list(rater.tables)  # ['VehicleAgeFactor']
rater.tables["VehicleAgeFactor"].head(3)
#   age_band       coverage factor
# 0      new      liability   1.00
# 1      new  comprehensive   1.30
# 2      mid      liability   0.95
type(factors.loc[0, "factor"])  # decimal.Decimal
```

Each read returns a new DataFrame, so edits leave the rater the same. Keys are text exactly as
filed, and factors are exact `Decimal` values.

## Build the proposed rater

Pass the edited tables to `with_tables`. RSLC checks each edited table before it exports the
new bundle:

* A table with a gap in a key that a lookup reaches raises `BundleError`, which names the key.
* A `float` raises `TypeError`. Give each rate as a `Decimal`.

```python theme={null}
proposed.bundle_id != rater.bundle_id  # True
```

`with_tables` writes a new bundle with its own `bundle_id`, here to `proposed.zip`. To keep the
new bundle in memory only, omit `path`. [Bundles and exact rating](/relativities/explanation/bundles-and-exact-rating)
describes what the new bundle holds.

## Rate a book with both raters

Write one `prepare` function, and give each rater a [`Program`](/relativities/reference/relativities#program)
with that function:

```mermaid theme={null}
flowchart LR
  book["Book"] --> current["Program: current rater"]
  book --> next["Program: proposed rater, same prepare"]
  current --> frame["One row for each submission and program"]
  next --> frame
```

This rater's inputs are the effective year and each vehicle's model year. Add model years to the
synthetic book's vehicles:

```python theme={null}
def prepare(book):
    years = book.submissions.set_index("submission_id")["effective_date"].dt.year
    vehicles = book.vehicles.groupby("submission_id", sort=False)["model_year"]
    return {
        submission_id: {
            "effective_year": years[submission_id],
            "vehicles": [{"model_year": year} for year in vehicles.get_group(submission_id)],
        }
        for submission_id in book.submissions["submission_id"]
    }


book = Book.read_csv("relativities-examples/ppa/book", schema=ppa.SCHEMA)
vehicles = book.vehicles
vehicles["model_year"] = [2025, 2020, 2012, 2024, 2008, 2019, 2023, 2015, 2010]
book = book.with_tables(vehicles=vehicles)
```

Rate the book with a [`Market`](/relativities/reference/relativities#market), and compare the two programs:

```python theme={null}
today = Program(rater, prepare, schema=ppa.SCHEMA)
market = Market({"current": today, "proposed": today.with_rater(proposed)})
results = market.rate(book)
premium = results.pivot(index="submission_id", columns="program", values="premium")
premium["change"] = premium["proposed"] - premium["current"]
premium["change_pct"] = (100 * premium["change"] / premium["current"]).round(2)
premium
# program        current  proposed  change  change_pct
# submission_id
# S1               920.0     946.0    26.0        2.83
# S2              1420.0    1454.0    34.0        2.39
# S3               920.0     946.0    26.0        2.83
# S4               640.0     654.0    14.0        2.19
# S5               780.0     800.0    20.0        2.56
# S6              2200.0    2254.0    54.0        2.45
```

`with_rater` keeps `prepare` and the schema, and replaces the rater. This rater charges comprehensive on each
vehicle, so each submission pays 2.19% to 2.83% more. S4, with a 2008 car, moves least.

## Summarize by segment

Join `submissions` for the `household_id`, then `households` for the region, and group the change:

```python theme={null}
segments = premium.join(book.submissions.set_index("submission_id")["household_id"])
segments = segments.merge(book.households, on="household_id")
segments.groupby("region")["change_pct"].agg(["count", "mean", "max"])
#           count      mean   max
# region
# metro         3  2.593333  2.83
# rural         2  2.510000  2.83
# suburban      1  2.450000  2.45
```

To measure the change on several versions of a book, rate a study with scenarios or draws. See
[Price a deductible change](/relativities/how-to/price-a-deductible-change) and
[Fill unknown facts](/relativities/how-to/fill-unknown-facts).

## Compare current and proposed positions against a competitor

Add competing programs to the same market and keep their inputs, coverage scope, and premium term aligned.
For this synthetic example, create a competing plan with comprehensive factors 2% above the current factors:

```python theme={null}
competitor_factors = rater.tables["VehicleAgeFactor"]
competitor_factors.loc[comprehensive, "factor"] *= Decimal("1.02")
competitor_rater = rater.with_tables({"VehicleAgeFactor": competitor_factors})
comparison_market = Market(
    {
        "current": today,
        "proposed": today.with_rater(proposed),
        "competitor": today.with_rater(competitor_rater),
    }
)
comparison = comparison_market.rate(book)
comparison.groupby(["program", "status"]).size()
# program     status
# competitor  succeeded    6
# current     succeeded    6
# proposed    succeeded    6
# dtype: int64
```

This competing plan is an illustrative benchmark derived from synthetic tables. For a market study, load the actual competing program and validate its mapping.

Compare all three programs on the same matched population:

```python theme={null}
position = comparison.pivot(index="submission_id", columns="program", values="premium")
matched = position.dropna(subset=["current", "proposed", "competitor"]).copy()
matched["current_gap"] = matched["current"] - matched["competitor"]
matched["proposed_gap"] = matched["proposed"] - matched["competitor"]
matched[["current_gap", "proposed_gap"]]
# program        current_gap  proposed_gap
# submission_id
# S1                   -10.4          15.6
# S2                   -13.6          20.4
# S3                   -10.4          15.6
# S4                    -5.6           8.4
# S5                    -8.0          12.0
# S6                   -21.6          32.4
```

The current plan costs less than this benchmark on all six submissions. The proposed plan costs more on all six.
A positive gap means our plan's premium exceeds the competitor's on the stated basis.
Retain each unmatched submission and its status separately when applying this method to your own book.

## Interpret the impact

For the aggregate premium impact, compare summed current and proposed premiums on the same population:

```python theme={null}
round(100 * (premium["proposed"].sum() / premium["current"].sum() - 1), 2)
# 2.53
```

The aggregate change is 2.53%. The segment exhibit above reports the arithmetic mean of submission-level percentage changes; label those statistics separately.
Apply consistent exposure weights when the study requires them.

Premium movement measures price impact. Claims and expense evidence are needed to assess profitability; quote and bind evidence are needed to assess conversion.
Retain the input book, bundle IDs, edited factors, comparison basis, and analysis code so reviewers can reproduce the exhibit.

```python theme={null}
competitor_rater.close()
```

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