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_platformpackage 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
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
| Module | Lines | From |
|---|---|---|
dashboard.py | 239 | Lesson 5 |
api.py | 185 | Lesson 4 |
model.py | 171 | Course 3 + 8A |
rating.py | 141 | Lessons 1, 4 |
db.py | 127 | Lesson 6 |
cli.py | 89 | all |
schemas.py | 83 | Lessons 1, 4 |
portfolio.py | 72 | Lesson 5 |
manual.py, settings.py, monitoring.py, … | 176 | 1, 7, 8A |
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) andmanifest.jsonare 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
| Endpoint | Does |
|---|---|
POST /quotes | validate, price, store, return (201) |
POST /quotes/batch | up to 1,000 in one transaction, one model call |
GET /quotes/{id} | a stored quote (404 if none) |
GET /health | status, model version, database |
GET /model | the manifest |
GET /monitoring/drift | PSI 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
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"}, ...]}
| Check | Result |
|---|---|
| Tests | 48 passed, 96% coverage |
| ruff, mypy --strict | clean |
| Images | API 521 MB, portal 807 MB, non-root |
| Workflow | actionlint clean; smoke test passes locally |
| Start to healthy | 2.2 s |
Hand-inWhat “done” means
| Area | Evidence |
|---|---|
| Correctness | boundaries, contract, transactions, model integrity; ≥ 90% |
| Model | manifest with holdout A/E and Gini; refuses tampering |
| Service | 201 / 401 / 404 / 422; OpenAPI page; vectorized batch |
| Data | quotes in PostgreSQL with the model version; UTC timestamps |
| Portal | proposal numbers match the answer key; AppTest tests |
| Operations | one-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 · Gini | 1.0143 · 0.2753 |
| Training rows · claims | 84,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, $45k | LIAB 4,648.59 · total 7,008.88 |
| Quote requests: valid · rejected | 963 · 37 |
| Total premium, 963 quotes | $1,577,722.41 |
| Book loaded | 18,249 vehicles |
| Current loss ratio · margin | 77.7% · −2.7% |
| Proposal: premium · loss ratio | +9.2% · 71.2% |
| Proposal: vehicles up > 15% | 10,902 |
| Driver-age PSI, new business | 0.0061 |
| After the campaign | 0.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
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.