The bottleneck is almost never the math — RAF scoring per member is cheap. The real pain is owning the model tables, plus a job that can't resume when it dies at 3 a.m. If you're scoring against CMS-HCC, the practical answer is to push the population through a managed Batch RAF API and keep your code as an orchestrator. That API is a 3-step async job, not a "POST a file, get JSON back" call — get that shape right and the rest is easy.
1. Submit, poll, download (3 steps)
Base URL https://www.vbcriskanalytics.com/raf-batch-api. Every request needs two headers — ApiKey: <your-batch-key> and an empty X-CSRF-TOKEN: (it's not Bearer).
# Step 1: submit as multipart/form-data -> returns a raf_batch_id
curl -X POST 'https://www.vbcriskanalytics.com/raf-batch-api/getPreProspectScore' \
-H 'accept: */*' -H "ApiKey: $RAF_BATCH_API_KEY" -H 'X-CSRF-TOKEN: ' \
-F 'risk_model=CMS-HCC-V28 Continuing Enrollee' \
-F 'risk_factor=Community NonDual Aged' \
-F 'file=@input_pre_prospective.csv;type=text/csv'
# -> {"code":201,"raf_batch_id":3400,"status":"Queued",
# "check_status_url":".../raf-batch-api/check-status/3400"}
# Step 2: poll until Completed (Queued 201 -> Running 202 -> Completed 200)
curl '.../raf-batch-api/check-status/3400' -H "ApiKey: $RAF_BATCH_API_KEY" -H 'X-CSRF-TOKEN: '
# Step 3: get the signed download URL
curl '.../raf-batch-api/download/3400' -H "ApiKey: $RAF_BATCH_API_KEY" -H 'X-CSRF-TOKEN: '
# -> {"download_url":"https://...s3.amazonaws.com/...zip?X-Amz-...","status":"Completed"}
# Step 4: GET that S3 URL directly — NO ApiKey header, it EXPIRES in ~120s.
# You get a .zip with a timestamped .xlsx of scored members.
Submit endpoints map to score types: /getPreProspectScore, /getPostProspectScore, /getPostCncntScore.
2. Get the CSV schema right — one row per (member, diagnosis)
This trips people up: it's not one row per member. Rows are grouped by ID:
ID,Gender,Age,ICD-10 CM Code,Flag
1,Male,65,E1122,Last_Year
1,Male,65,J449,Current_Year
2,Male,84,E1142,Last_Year
Gender is Male/Female, Age ≤ 125, the dot in the ICD-10 code is optional, and valid Flag values depend on score type (Pre-Prospective Last_Year/Current_Year; Post-Prospective New/Billed/Missed; Post-Concurrent No_Changes/Deletion/Addition plus a Modification_To column).
3. For throughput, fan out jobs and poll asynchronously
Split the population into CSV chunks, submit them all, then poll each to Completed:
import os, time, requests
BASE = "https://www.vbcriskanalytics.com/raf-batch-api"
H = {"ApiKey": os.environ["RAF_BATCH_API_KEY"], "X-CSRF-TOKEN": ""}
def submit(path):
with open(path, "rb") as f:
r = requests.post(f"{BASE}/getPreProspectScore", headers=H,
data={"risk_model": "CMS-HCC-V28 Continuing Enrollee",
"risk_factor": "Community NonDual Aged"},
files={"file": ("input.csv", f, "text/csv")})
return r.json()["raf_batch_id"]
jobs = [submit(p) for p in chunk_paths] # submit all chunks
for jid in jobs: # then poll each
while requests.get(f"{BASE}/check-status/{jid}", headers=H).json()["status"] != "Completed":
time.sleep(5)
url = requests.get(f"{BASE}/download/{jid}", headers=H).json()["download_url"]
open(f"scored_{jid}.zip", "wb").write(requests.get(url).content) # signed URL, no ApiKey
4. Make it idempotent
The API returns a durable raf_batch_id and a check-status URL, so persist those per chunk. A re-run can reattach to in-flight jobs instead of resubmitting them — a 3 a.m. failure becomes a cheap retry, not a full restart, and it doesn't waste your available hits quota.
A couple of gotchas
-
Validate before you submit so you don't burn quota on bad rows. The API returns specific codes:
418invalid gender,419age > 125,420columns missing,427only CSV allowed,429invalid flag,425/426invalid risk model/factor,430license/limit. -
Store the
risk_model/risk_factoryou submitted with each output so historical scores stay reproducible for audits.
Wrapping up
If you'd rather not maintain the model tables and hierarchies yourself, there's a good writeup on a managed approach to batch RAF scoring for large member populations, and the batch RAF API that handles the overnight throughput.
VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.
Top comments (0)