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

Capstone: An Enterprise Actuarial Rating Platform

The Course 3 champion model behind a FastAPI service, a PostgreSQL database, a Streamlit underwriting portal, containers, and a CI/CD pipeline: an end-to-end, tested, deployable decision-support system.

Lesson 8B · OverviewThe deliverable

  • A rating_platform package with one module per concern
  • The Course 3 champion GLM, validated on 2025, served with a verified manifest
  • A FastAPI service that prices and logs every quote in PostgreSQL
  • The Executive Underwriting Portal, pricing uploads through the API
  • Images, Compose, and CI: tested, built, and started with one command
3–4 h
Build, in stages
1 h
Test, containerize, document

Companion notebook: lesson-08b-capstone.ipynb — prototype each stage with self-checking exercises, then move it into the package.

The package1,283 lines of code, 537 lines of tests

rating_platform/
├── pyproject.toml   # extras: portal, train, postgres
├── uv.lock
├── Dockerfile       # targets: api, portal
├── compose.yaml     # db, init, api, portal
├── .dockerignore  .env.example
├── .github/workflows/ci.yml
├── models/champion/  # model.joblib + manifest.json
├── src/rating_platform/
│   ├── settings.py   schemas.py   manual.py
│   ├── model.py      rating.py    money.py
│   ├── db.py         api.py       client.py
│   ├── portfolio.py  monitoring.py
│   ├── dashboard.py  cli.py
│   └── manuals/motor_2025.toml
└── tests/              # 48 tests, fixtures in tests/data
ModuleLinesFrom
dashboard.py239Lesson 5
api.py185Lesson 4
model.py171Course 3 + 8A
rating.py141Lessons 1, 4
db.py127Lesson 6
cli.py89all
schemas.py83Lessons 1, 4
portfolio.py72Lesson 5
manual.py, settings.py, monitoring.py, …1761, 7, 8A
Build

Five Stages

Each one working and tested before the next.

Stage 1 · ModelTrain, validate, save with a manifest

$ uv run rating-platform train
saved glm-dcbe5b5dbb to models/champion
  holdout_year: 2025
  holdout_actual_to_expected: 1.0143
  holdout_gini: 0.2753
  trained_rows: 84346
  trained_car_years: 72,780.5092
  trained_claims: 4565
  • Fit on 2021–2024; check A/E and Gini on 2025; refit on all five years
  • The same numbers as Course 3’s champion: the same data, the same model, now in a package
  • model.joblib (a few KB) and manifest.json are committed: the artifact the API serves is reviewed like code

Training reads Parquet (the train extra); the API image doesn’t train, so it doesn’t carry pyarrow.

Stage 2 · Rating & dataThe engine and the book

def build_engine(manual, model, liability_basis,
                 trend_factor=1.0, target_loss_ratio=0.68):
    if liability_basis == "model":
        liability = ModelLiability(model, trend_factor,
                                   target_loss_ratio)
    else:
        liability = ManualLiability(manual)
    return RatingEngine({"LIAB": liability,
                         "COLL": ManualCollision(manual),
                         "COMP": ManualComprehensive(manual)},
                        liability_basis)
$ uv run rating-platform init-db
loaded 18,249 vehicles into sqlite
(expected losses from glm-dcbe5b5dbb)

Liability from the model (pure premium × 1.041.5 ÷ 0.68), collision and comprehensive from the manual; RP_LIABILITY_BASIS=manual switches back without a code change. init-db scores the 2025 book with the champion and loads it in one transaction.

Stage 3 · ServiceThe API, with the quote log

EndpointDoes
POST /quotesvalidate, price, store, return (201)
POST /quotes/batchup to 1,000 in one transaction, one model call
GET /quotes/{id}a stored quote (404 if none)
GET /healthstatus, model version, database
GET /modelthe manifest
GET /monitoring/driftPSI of recent quotes vs the book
quote requests: 963 valid, 37 rejected before
                reaching the API
batch of 963: 0.20 s, total premium 1,577,722.41
  • Writes and reads of quotes need the API key
  • Every quote row stores the request, the premiums, and the model version

Stage 4 · PortalDecisions on the live book

  • Current rates: projected loss ratio 77.7%, margin −2.7%
  • The rate review proposal: +9.2% premium, 71.2%, margin +3.8%, and 10,902 customers up 25%
  • Saved scenarios, shareable preset links, batch uploads priced through the API
The Executive Underwriting Portal showing the rate review proposal: premium 21.70 million, loss ratio 71.2 percent, margin 3.8 percent

Stage 5 · ShipContainers and the pipeline

$ docker compose up --build -d
$ curl -s localhost:8000/health
{"status":"ok","model_version":"glm-dcbe5b5dbb",
 "database":"postgresql"}
$ curl -s -H "X-API-Key: $KEY" \
    "localhost:8000/monitoring/drift?last=963"
{"quotes": 963, "reference_vehicles": 18249,
 "features": [
  {"feature": "driver_age", "psi": 0.0061,
   "status": "stable"}, ...]}
CheckResult
Tests48 passed, 96% coverage
ruff, mypy --strictclean
ImagesAPI 521 MB, portal 807 MB, non-root
Workflowactionlint clean; smoke test passes locally
Start to healthy2.2 s

Hand-inWhat “done” means

AreaEvidence
Correctnessboundaries, contract, transactions, model integrity; ≥ 90%
Modelmanifest with holdout A/E and Gini; refuses tampering
Service201 / 401 / 404 / 422; OpenAPI page; vectorized batch
Dataquotes in PostgreSQL with the model version; UTC timestamps
Portalproposal numbers match the answer key; AppTest tests
Operationsone-command start; no secrets in images or Git; drift endpoint; CI (checks → images → smoke test) green

Add a one-page README for a reviewer: what it does, how to run it, how the model was validated, what you monitor.

Extensions: PostgreSQL in CI; rate limits; a registry with version tags; a shadow-scored challenger.

Capstone · answer keyNumbers your platform should reproduce

Holdout A/E · Gini1.0143 · 0.2753
Training rows · claims84,346 · 4,565
Base risk quote (Sedan, 45, 12,000 mi, $30k, $500s)LIAB 726.73 · COLL 576.85 · COMP 158.35
22, Sports, urban, 16,000 mi, $45kLIAB 4,648.59 · total 7,008.88
Quote requests: valid · rejected963 · 37
Total premium, 963 quotes$1,577,722.41
Book loaded18,249 vehicles
Current loss ratio · margin77.7% · −2.7%
Proposal: premium · loss ratio+9.2% · 71.2%
Proposal: vehicles up > 15%10,902
Driver-age PSI, new business0.0061
After the campaign0.6742 (investigate)

Course 4 recapWhat you can now do

  • Turn a script into a typed, validated package with clear design
  • Prove it works with tests, properties, linters, and CI
  • Simulate millions of scenarios, reproducibly and fast
  • Serve a model through a documented, secured API
  • Put live portfolio decisions in front of the business
  • Store and query data with integrity, at scale
  • Ship it all in containers with secrets kept out
  • Deliver changes through a pipeline, and monitor what comes back
Python for Actuarial Students
Course 4 complete · Next: Course 5 (proposed)

Advanced Actuarial Analytics: Reserving, Uncertainty & Responsible AI

Claims data at scale, stochastic reserving, experience studies, Bayesian credibility, extreme values and capital, deep learning, and the governance that makes advanced models usable.