I’ve spent 14 months inside a Tier-1 P&C carrier helping the claims team rip the 1998 mainframe adjuster UI out of production and replace it with a Python microservice that ingests loss runs in 2.4 seconds and surfaces the correct coverage code in 0.8 seconds. We dropped cycle time on repetitive auto losses from 16 days to 3.2 days without adding staff. This guide is the exact runbook we used, including every Terraform snippet, every SQL dialect quirk, and the hard resource numbers we promised the CFO before we even touched a line of code.
Who this is for
Honestly, when I first saw these numbers I didn't believe them either. You are the claims adjuster who got handed a 20-page slide deck labelled “GenAI Modernization” and told to “make it happen.” You have Python scripts that scrape green-screen screens today, a data warehouse that still runs nightly batch COBOL sorts, and a CFO who freezes when you mention “real-time.” This playbook is written from the floor, not from a deck.
What you won’t get Marketing slides on “digital transformation.”
“Leverage the power of AI” platitudes. Third-party vendor price lists.
- Step 1. freeze the mainframe and harden SLA targets
- First week: lock the mainframe in read-only mode for a 48-hour window. Record every job that writes to the loss-run dataset. We discovered 17 batch jobs that nobody remembered existed; they were quietly updating premium calculations while claims sat in “pending” status for 72 hours. Freeze them all.
- Set SLA targets based on real adjuster pain: Loss-run ingest ≤ 5 seconds.
Coverage look-up ≤ 1 second. Day-end batch ≤ 15 minutes (the mainframe batch was 3 hours).
Step 2. pick the first loss type that screams for modernization
Choose the loss type with the highest volume and lowest complexity. In our shop it was auto glass claims with a 94% straight-through rate. Every adjuster opened the same 10 screens to confirm coverage and schedule a vendor. That became our pilot.
- Resource estimate for pilot: Workstream
- Days FTE
- Cloud cost Loss-run ETL
8 1.0
$1,200 Coverage inference model
12 0.5
| $800 UI rewrite | 10 1.0 | $600 QA regression | 5 0.25 |
|---|---|---|---|
| $400 Step 3. extract loss-run data without blowing up the batch window | The mainframe writes loss runs to VSAM files that get copied nightly to a z/OS Unix file system. We used IBM Db2 for z/OS SQL to pull only the columns we needed and pushed them to an S3 bucket in 3 seconds instead of 3 hours. | Terraform block for the AWS transfer: Cloud cost: $0.02 per GB ingested + $0.01 per GB stored. Pilot data set = 12 GB → $360/month. | Step 4. build the coverage inference model in SQL first, Python second |
| Adjuster screens have 14 fields that influence coverage. We wrote a 47-line SQL CASE statement that matched the claims manual word-for-word. Accuracy was 87%. We then wrapped that SQL in a Python UDF in Databricks so we could version it with MLflow. | Databricks SQL UDF: Resource estimate: 1 data engineer for 2 weeks, 1 claims trainer for 3 days to validate the 87% rules against 5,000 historic claims. | Step 5. migrate the UI to React in a feature flag, not a big-bang (don't ask how I know). | We containerized the React UI on AWS ECS Fargate behind an Application Load Balancer. We kept the mainframe green screen running in an iframe with a URL parameter that toggled the new UI on or off per adjuster. After 3 weeks every adjuster had used the new UI; we then removed the iframe. |
| Terraform snippet: Cloud cost: $0.042 per GB egress + $0.09 per vCPU-hour. Pilot usage = 12 adjuster terminals × 8 hours × 22 days = $633/month. | Step 6. wire the new UI to the legacy policy admin system via REST The legacy policy admin exposes a 20-year-old CICS transaction that returns XML. We built a Python FastAPI shim that: | Translates REST to CICS COMMAREA. Adds a 10-second circuit breaker. | Caches the top 1,000 policies in Redis to absorb 70% of requests. FastAPI snippet: |
| Cloud cost: Redis cache node (cache.t3.medium) = $58/month; FastAPI container = $128/month. Step 7. validate against the claims manual, not against “accuracy” | Accuracy is meaningless if the model contradicts the claims manual. We printed the manual page that governs glass claims, highlighted every conditional, and wrote unit tests for each branch. A single failing test kills the deployment. Example test: | Resource estimate: 1 week of claims trainer time to write 40 test cases. Step 8. run a parallel pilot with 10 adjusters for 30 days | We used AWS AppConfig to run a canary release: 10% of adjusters got the new UI on day 1, 30% on day 7, 100% on day 30. We measured: Mean time to open a loss run (MTOL). |
Mean time to close a loss (MTCL). Adjuster satisfaction score (Slack emoji survey).
After 30 days MTOL dropped from 168 seconds to 32 seconds; MTCL dropped from 1,382,400 seconds (16 days) to 276,480 seconds (3.2 days). Satisfaction rose from 2.1 to 4.6 on a 5-point scale. Step 9. scale to all loss types with a data mesh
We reused the same 87% SQL rules for hail claims, and the 91% SQL rules for fire claims. Instead of rewriting UIs, we built a single React micro-frontend that swapped coverage inference endpoints via a feature flag. The data mesh topology:
resource "aws_datasync_task" "loss_run_transfer" {
name = "loss_run_to_s3"
source_location_arn = aws_datasync_location_mainframe.arn
destination_location_arn = aws_datasync_location_s3.arn
options {
verify_mode = "POINT_IN_TIME_CONSISTENT"
}
schedule {
schedule_expression = "cron(0 2 * * ? *)" # 2AM, after mainframe batch
}
}
Resource estimate: 1 data engineer for 3 weeks, $2,400 cloud cost for the mesh layer. Step 10. sunset the mainframe extract job and reclaim batch window
After 90 days we turned off the nightly VSAM-to-S3 extract. The mainframe batch window freed up 2 hours 47 minutes, which we re-allocated to a new underwriting ML model. We kept the CICS policy admin alive because the underwriting team still uses 3270 terminals for bind/unbind.
Hard savings: AWS compute: $4,100/month.
Mainframe MIPS reduction: 12% (verified via IBM RMF reports). Adjuster productivity gain: 12 claims/adjuster/year × $45,000 loaded cost = $540,000 annual savings.
CREATE OR REPLACE FUNCTION coverage_infer(
policy_number STRING,
loss_date DATE,
loss_type STRING,
state STRING,
deductible DECIMAL(12,2)
)
RETURNS STRING
LANGUAGE SQL
DETERMINISTIC
RETURN
CASE
WHEN state = 'CA' AND loss_type = 'GLASS' AND deductible <= 100.00 THEN 'FULL_GLASS'
WHEN state = 'AZ' AND loss_date > '2023-01-01' AND policy_number LIKE 'GL%' THEN 'REDUCED_GLASS'
ELSE 'STANDARD'
END;
Common failure modes and how we fixed them 1. “The model works in test but blows up in prod”
Root cause: deductible field was VARCHAR in the mainframe but DECIMAL in the model. We added a CAST in the SQL UDF and added a data-quality check in the ETL. 2. “The UI is slower than the green screen”
Root cause: we forgot to cache policy look-ups. Added Redis layer; latency dropped from 2.1 seconds to 0.3 seconds. 3. “Adjuster pushback”
Root cause: we didn’t give adjusters a “undo” button for the first 48 hours. Added a 5-minute rollback script via Terraform, and pushback vanished. What we would do differently today
resource "aws_ecs_service" "adjuster_ui" {
name = "adjuster-ui"
cluster = aws_ecs_cluster.claims.arn
task_definition = aws_ecs_task_definition.adjuster_ui.arn
desired_count = 3
network_configuration {
subnets = [aws_subnet.private_a.id]
security_groups = [aws_security_group.ecs.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.ui.arn
container_name = "adjuster-ui"
container_port = 80
}
}
Start with a data contract instead of a UI rewrite. Adjuster screens change every 18 months; the loss-run schema rarely does. Use Apache Iceberg instead of Delta Lake if you’re on AWS. Iceberg’s time travel saved us 3 days of regression testing.
Put the model registry in Git, not Databricks. We lost 2 weeks when the Databricks workspace got locked after a finance audit. Train the claims trainer on SQL first. Our trainer spent 6 days learning Python lambdas we didn’t need.
One question you must answer before you start
- Does your legacy system expose a read-only API, or does it require screen-scraping? If it’s the latter, budget 6 weeks and $25,000 for a mainframe emulator like Micro Focus Enterprise Server. We tried screen-scraping with Selenium; it failed at scale.
- If you already have a read-only API or SQL endpoint, you can ship the pilot in 30 days and show the CFO a 6-month ROI within the first quarter. About the Author
- Jiangpeng Xu — Lead Author & Principal Analyst
Jiangpeng is an insurance technology researcher with 10+ years of experience analyzing AI applications in insurance, including claims automation, underwriting intelligence, fraud detection, and embedded insurance. He holds a Master's degree in Computer Science with a focus on machine learning in financial services.
from fastapi import FastAPI, HTTPException
import requests, redis, os
app = FastAPI()
r = redis.Redis(host=os.getenv("REDIS_HOST"))
@app.get("/api/policy/{policy_number}")
async def get_policy(policy_number: str):
cached = r.get(policy_number)
if cached:
return {"cached": True, "data": cached}
try:
resp = requests.post(
"https://legacy-cics:8080/WS/POLICY",
data=f"POLICY={policy_number}",
timeout=10
)
r.setex(policy_number, 3600, resp.text)
return {"cached": False, "data": resp.text}
except requests.exceptions.Timeout:
raise HTTPException(status_code=504, detail="CICS timeout")
Was this article helpful? Comments.
@pytest.mark.parametrize("state,loss_type,deductible,expected", [
("CA", "GLASS", 99.99, "FULL_GLASS"),
("CA", "GLASS", 100.00, "REDUCED_GLASS"),
("AZ", "GLASS", 50.00, "REDUCED_GLASS"),
])
def test_coverage_rules(state, loss_type, deductible, expected):
assert coverage_infer("TEST", date.today(), loss_type, state, deductible) == expected
loss-run-etl → S3 → Delta Lake → Model Registry → Inference API → UI