Python for Actuarial Students · Course 2
Lesson 8 · Part B

Capstone: An End-to-End Rating Engine & Report

Operational database dumps in; exposure, loss ratios, spatial territories, and rate indications out — as an executive Excel report and a Reveal.js deck.

The briefWhat you’re building

The head of motor pricing wants a quarterly rating review that anyone on the team can reproduce with one command:

  1. Ingest the six-table operational dump and validate it
  2. Compute exact earned exposure and earned premium to the evaluation date
  3. Enrich every vehicle with a spatial territory
  4. Calculate segment experience and credibility-weighted rate indications
  5. Report: an executive Excel workbook and a Reveal.js slide deck

Uses every lesson

Cleaning (1) · exposure (2) · joins (3) · metrics (4) · charts (5) · spatial (6) · Excel (7) · pipelines (8A)

Why it matters

This is what a pricing team’s quarterly process looks like when it’s done well: fast, reproducible, reviewable.

Companion notebook: lesson-08b-capstone.ipynb — worked examples and self-checking exercises.

Part 1

Requirements

Inputs, stages, and outputs.

SpecificationInputs and outputs

Inputs

  • data/motor/*.csv — the six-table schema (Lesson 3)
  • data/geo/nc_geo.gpkg — counties, places, coastline (Lesson 6)
  • config.toml — evaluation date, territory rule, age bands, indication parameters

Command

uv run rating-pipeline --config config.toml

Outputs (in output/)

abt.parquetthe enriched vehicle-level ABT
rating_review.xlsxSummary (KPI cards), Territory, Segments (territory × age band with indications, loss ratios above target highlighted)
rating_review.htmla Reveal.js deck: title, KPIs, territory chart and table, largest proposed changes
figures/*.pngcharts used by the deck
run logrow counts, control totals, file paths

CalculationThe indication, step by step

def indicate(seg, target_loss_ratio, annual_trend, trend_years,
             full_credibility_claims, max_change):
    out = seg.copy()
    out["trended_lr"] = (out["loss_ratio"]
                         * (1 + annual_trend) ** trend_years)
    out["indicated_change"] = (out["trended_lr"]
                               / target_loss_ratio - 1)
    out["credibility"] = np.minimum(
        1, np.sqrt(out["claims"] / full_credibility_claims))
    out["proposed_change"] = (out["credibility"]
                              * out["indicated_change"]
                             ).clip(-max_change, max_change)
    return out

segments = indicate(experience(abt, ["territory", "age_band"]),
                    **cfg["indication"])
  1. Trend the experience loss ratio 1.5 years forward at 4% a year
  2. Indicate: how far above or below the 68% target is it?
  3. Credibility: Z = min(1, √(claims ÷ 1,082)) — thin segments move less
  4. Cap at ±15% so no customer sees a shock

Identical logic to the Lesson 7 Excel panel — and the two independent implementations agree: +7.8% premium-weighted proposed change.

Part 2

Generating the Slide Deck

HTML from a template — the same Reveal.js you’ve watched all course.

Jinja2Templates: HTML with holes in it

<!-- templates/deck.html.j2 -->
<section>
  <h2>Portfolio at a glance</h2>
  <div class="kpi"><b>{{ "{:.1%}".format(kpis.loss_ratio) }}</b>
    loss ratio</div>
</section>
<section>
  <h2>Loss ratio by territory</h2>
  <img src="figures/territory_lr.png">
  <table>
  {% for t in territory %}
    <tr><td>{{ t.territory }}</td>
        <td class="{{ 'over' if t.loss_ratio > params.target_loss_ratio }}">
          {{ "{:.1%}".format(t.loss_ratio) }}</td></tr>
  {% endfor %}
  </table>
</section>
from jinja2 import (Environment, PackageLoader,
                    select_autoescape)

env = Environment(loader=PackageLoader("rating_pipeline"),
                  autoescape=select_autoescape())
html = env.get_template("deck.html.j2").render(
    kpis=kpis,
    territory=territory.to_dict("records"),
    params=params)
Path("output/rating_review.html").write_text(html)
  • {{ … }} inserts a value; {% for %} / {% if %} control structure
  • Pass plain Python data: to_dict("records") gives a list of row dicts
  • autoescape stops text in the data being read as HTML

The deck is regenerated with the numbers every run — no copy-paste into PowerPoint, ever again.

ResultThe generated deck

Two slides from the generated Reveal.js deck: 'Portfolio at a glance' with KPI boxes for 20,101 car years, 3,354 claims, 74.6% loss ratio and +7.8% proposed change; and 'Loss ratio by territory' with a bar chart (Coastal 80%, Rural inland 65%, Urban inland 78%) against a 68% target line, above a table of car years, frequency and loss ratio

Two slides from a finished pipeline’s output/rating_review.html.

Answer keyYour pipeline should reproduce these

Control totals
Policy terms · vehicles22,185 · 23,993
Car years20,100.95
Claims · incurred3,354 · 24,669,862
Portfolio loss ratio74.6%
Premium-weighted proposed change+7.8%
TerritoryVehiclesCar yearsFreq.LR
Coastal4,8344,070.00.190780.5%
Rural inland7,5016,284.00.144064.9%
Urban inland11,6589,746.90.171678.3%

Largest proposals: Urban inland 35–49, Coastal 75+, Urban inland 17–24 and 25–34 at the +15% cap; Coastal 50–64 at +12.4%.

Small differences in the fourth significant figure usually mean a different exposure convention or age-band edge. Larger ones mean a join changed the grain — check your control totals.

Capstone deliverableWhat to hand in

1 · The repository

  • A uv package with the four stage modules and a CLI entry point
  • config.toml holding every assumption
  • README.md: what it does, how to run it, what it produces
  • output/ git-ignored; uv.lock committed

2 · Tests

  • Exposure (leap year, cancellation, in force at evaluation)
  • Metrics are ratios of sums; indication capping and credibility
  • A data-quality test: a duplicated key raises DataQualityError

3 · The outputs

  • rating_review.xlsx styled to the corporate palette
  • rating_review.html generated from a Jinja2 template, with at least one chart
  • A run log from a clean run

4 · A one-page memo

  • Where the current rates are inadequate, and by how much
  • What the territory analysis changes
  • The limitations: incurred not ultimate, no IBNR, thin segments

Definition of done: a fresh clone, uv sync, uv run rating-pipeline, uv run pytest — all green, no manual steps.

AssessmentHow it will be marked

CriterionWeightWhat “excellent” looks like
Correctness30%Reproduces the answer key; every control total reconciles
Architecture20%Clean stages, I/O at the edges, all parameters in config, one-command run
Data quality & tests15%Key and grain checks that stop bad runs; meaningful, fast tests
Reporting20%Excel and slides a pricing committee could use unedited; headline titles; formatted numbers
Judgment15%Memo is honest about credibility and limitations; recommendations follow from the evidence

Stretch goalsIf you finish early

Honor the evaluation date

Set date = 2024-12-31 in the config. Exclude policies incepting and claims reported after it, and cap every open claim’s development at that date. What changes?

Messy input

Point the pipeline at claims_raw.csv (Lesson 1) and add a cleaning stage with a rejects report in the Excel workbook.

A map in the deck

Add the Lesson 6 county choropleth to the generated slides, masked for credibility.

The first stretch goal is the most important in practice: a pipeline that quietly uses data from after its evaluation date produces results nobody could have known at the time — a serious error in reserving and pricing work.

Course 2 · RecapEight lessons, one pipeline

1 · pandas

Clean a messy extract; log what you drop

2 · Dates

Exact exposure; CY, PY, AY

3 · Joins

Keys, grain, and an ABT that reconciles

4 · Metrics

Frequency, severity, pure premium, LR

5 · EDA

Charts that make a point

6 · Spatial

Territories from real geography

7 · Excel

Live, tested workbooks by code

8 · Pipelines

One command, every quarter

You can now take real, messy, multi-table data all the way to a decision-ready report.

Python for Actuarial Students
Course 2 complete · Next: Course 3

Predictive Modeling & Modern Actuarial Machine Learning

From one-way tables to GLMs, gradient boosting, and interpretable risk models — on a larger motor book and a health-claims book, built with the skills you just practiced.