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

# Model a commercial book

> Define a workers’ compensation book, rate two versions of a payroll tariff, and calibrate the portfolio mix.

This guide models workers’ compensation rated on payroll by state and class. Its three policies, schema and tariff are synthetic. A property book follows the same steps with a location and building schema.

## Define the schema and book

Each policy reports payroll by state and class code. The `classes` key includes the state, so policy W1 reports class `8810` in two states:

```python theme={null}
from decimal import Decimal as D
import pandas as pd
from relativities import Book, Column, Inputs, Market, Program, Rater, Schema, Table
from relativities.analysis import PremiumAnalysis, PremiumTerms, search_lever

SCHEMA = Schema("wc_example", tables={
    "states": Table(("submission_id", "state"), required=True),
    "classes": Table(("submission_id", "state", "class_code"),
        {"payroll": Column("number", unit="USD")}, required=True, references=("states",)),
})
classes = pd.DataFrame({"submission_id": ["W1", "W1", "W2", "W3"], "state": ["CA", "NY", "CA", "CA"],
    "class_code": ["8810", "8810", "5403", "5403"], "payroll": [100000, 200000, 100000, None]})
book = Book({
    "submissions": pd.DataFrame({"submission_id": ["W1", "W2", "W3"], "policy_id": ["W1", "W2", "W3"],
        "industry": ["office", "construction", "construction"]}),
    "states": classes[["submission_id", "state"]], "classes": classes,
}, schema=SCHEMA)
```

`references` makes each class row name a state row of its own submission. Policy W3 leaves its payroll blank. [Create a book](https://docs.canary.effectiveai.app/relativities/how-to/create-a-book.md) describes the schema rules.

## Save the toy tariff

The tariff charges a rate per \$100 of payroll for each class code. Save the rater and its rate table:

```rsl wc/rater.rsl theme={null}
spec { rsl: "0.4", carrier: "Example Mutual", state: CA, product: "Toy payroll tariff", effective_date: 2026-01-01 }
Sources = { manual: Source = { artifact: artifact("00000000-0000-4000-8000-000000000000"), kind: pdf } }
Types = { ClassCode = Code(pattern: "[0-9]{4}") }
Tables = { ClassRate [cite: manual:p1] : table dims (class_code: ClassCode) -> { rate: Rate } }
Records = {
    Policy = { inputs { classes: List(Class) },
        premium: Premium [cite: manual:p2] = round(sum(self.classes as c, c.premium), 2, half_up) },
    Class = { inputs { class_code: Key(ClassRate), payroll: Num {precision: 0.01} where self in [0, _] },
        premium: Num = self.payroll / 100 * lookup!(ClassRate, self.class_code).rate }
}
RatingOutcomes = { premium: Policy.premium }
```

```csv wc/tables/ClassRate.csv theme={null}
class_code,rate
8810,1.00
5403,3.00
```

The toy rates apply in every state. Real programs add state rates, experience modification and premium discounts.

## Rate two versions

Build the rater, then derive an alternative with new class rates:

```python theme={null}
def prepare(book):
    return Inputs(classes=book.classes)

current = Program(Rater.build("wc/rater.rsl"), prepare, schema=SCHEMA)

def with_rates(rates):
    table = pd.DataFrame({"class_code": list(rates), "rate": [D(r) for r in rates.values()]})
    return current.with_rater(current.rater.with_tables({"ClassRate": table}))

alternative = with_rates({"8810": "0.80", "5403": "3.30"})
results = Market({"current": current, "alternative": alternative}).rate(book)
```

`prepare` passes each policy’s class rows as the rater’s `classes` list. The current rates charge W1 and W2 $3,000 each. The alternative charges W1 $2,400 and W2 \$3,300. Both versions return W3 as `invalid_inputs`, because its payroll is blank.

## Compare premium per payroll

Total each policy’s payroll onto its submission, and name it as the exposure base:

```python theme={null}
totals = book.classes.groupby("submission_id").payroll.sum(min_count=1)
book = book.with_tables(submissions=lambda t: t.assign(payroll=t.submission_id.map(totals)))
analysis = PremiumAnalysis.from_market(
    results, book, baseline="current", candidate="alternative", premium="premium",
    terms=lambda row: PremiumTerms("USD", "annual_standard_premium", {"coverage": "statutory"}),
    segments={"industry": "industry"}, exposure="payroll", exposure_unit="USD payroll",
)
summary = analysis.summarize()[0]
summary.portfolio_change_percent, summary.candidate_premium_per_exposure * 100
# (Decimal('-5.00'), Decimal('1.42500'))
```

Both versions share one coverage basis, so `terms` returns the same `PremiumTerms` for every quote. The alternative lowers premium by 5% on the two rated policies. It charges $1.43 per $100 of their payroll, against \$1.50 under the current rates. W3 stays in the population as unrated.

## Calibrate the portfolio mix

Reweight the policies to match target policy counts by industry:

```python theme={null}
calibration = analysis.calibrate({"industry": {"office": 1, "construction": 3}})
calibration.weights, calibration.analysis.summarize()[0].portfolio_change_percent
# ((Decimal('1'), Decimal('1.5'), Decimal('1.5')), Decimal('-2.00'))
```

In this construction-heavy mix, the decrease narrows to 2%. `calibration.targets` reports each target’s source count, achieved count and residual. One attribute is post-stratified exactly; several attributes are raked. The calibrated analysis reuses every quote. These targets are illustrative; base real targets on market or exposure evidence.

## Find a matching rate

`search_lever` searches one rating parameter until a program meets a premium target. Here it finds the class `5403` rate that matches the alternative’s premium:

```python theme={null}
lever = search_lever(lambda rate: with_rates({"8810": 1, "5403": rate}),
                     reference=alternative, book=book, low=2, high=4, quantum=D("0.01"))
lever.converged, lever.value
# (True, Decimal('2.70'))
```

With class `8810` at 1.00, a `5403` rate of 2.70 matches the alternative’s \$5,700 on the two rated policies. Each step builds one rater and rates the whole book. Pair a suggested rate with the program’s indicated rate need before you act on it.

## Next step

[Analyze supplied quotes](https://docs.canary.effectiveai.app/relativities/how-to/analyze-supplied-quotes.md) to count rated policies, show the distribution of changes and rank several programs.


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