<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VBC Risk Analytics</title>
    <description>The latest articles on DEV Community by VBC Risk Analytics (@vbc_risk_analytics).</description>
    <link>https://dev.to/vbc_risk_analytics</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3862747%2F76a5f1ff-1c8f-4dbe-97a6-e1e8dcf4a599.png</url>
      <title>DEV Community: VBC Risk Analytics</title>
      <link>https://dev.to/vbc_risk_analytics</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vbc_risk_analytics"/>
    <language>en</language>
    <item>
      <title>CDI Is the Missing Link in HCC Risk Capture (and It's a Data Problem)</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:38:04 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/cdi-is-the-missing-link-in-hcc-risk-capture-and-its-a-data-problem-n6c</link>
      <guid>https://dev.to/vbc_risk_analytics/cdi-is-the-missing-link-in-hcc-risk-capture-and-its-a-data-problem-n6c</guid>
      <description>&lt;p&gt;Most engineering teams building risk adjustment tooling treat clinical documentation as a black box: notes go in, codes come out, RAF scores get calculated. But if your HCC (Hierarchical Condition Category) capture rate is low, the problem usually isn't your scoring engine — it's upstream, in the documentation itself. This is where Clinical Documentation Improvement (CDI) becomes a data-engineering concern, not just a clinical one.&lt;br&gt;
﻿&lt;/p&gt;
&lt;h2&gt;
  
  
  The capture gap, in concrete terms
&lt;/h2&gt;

&lt;p&gt;﻿&lt;br&gt;
A member can have a chronic condition that absolutely belongs in the CMS-HCC V28 model, but if the provider's note for the year doesn't document that the condition was evaluated and addressed, it never becomes a codable diagnosis, and it never contributes to the RAF (Risk Adjustment Factor) score. The condition is real. The data is missing. Multiply that across a panel and you have a systematic understatement of risk.&lt;br&gt;
﻿&lt;br&gt;
CDI exists to close that gap at the source. From a systems perspective, that means surfacing the right prompt to the right clinician at the right moment — which is squarely an engineering problem.&lt;br&gt;
﻿&lt;/p&gt;
&lt;h2&gt;
  
  
  Where developers come in
&lt;/h2&gt;

&lt;p&gt;﻿&lt;br&gt;
A modern CDI workflow is a pipeline:&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clinical note (NLP)  -&amp;gt;  candidate conditions
   -&amp;gt;  cross-check vs claims history + problem list
   -&amp;gt;  documentation gap?  -&amp;gt;  provider query/prompt
   -&amp;gt;  resolved diagnosis  -&amp;gt;  HCC mapping (V28)  -&amp;gt;  RAF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
Each stage is testable. A few things to get right:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Evidence linkage.&lt;/strong&gt; Every suggested condition should carry a pointer to the evidence (the lab value, prior claim, or note span) that triggered it. A prompt with no evidence is noise, and clinicians tune out noise fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suppress the already-documented.&lt;/strong&gt; If the condition is already captured for the year, don't prompt. Deduping against the current-year encounter set is essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchy awareness.&lt;/strong&gt; In the HCC model, hierarchies mean a more severe condition supersedes a less severe one in the same family. Your suggestion logic should respect that so you're not prompting for a code that wouldn't add value.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Synthetic example
&lt;/h2&gt;

&lt;p&gt;Consider a synthetic member with a documented history of CKD and a recent eGFR of 28. If the current-year note mentions kidney disease only in passing without staging, the CDI engine flags a documentation gap and suggests a clarification. The clinician confirms the stage, the diagnosis is documented, and the appropriate HCC is captured. No new clinical reality was invented — the documentation simply caught up to it. (All values here are synthetic; no PHI.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The compliance guardrail
&lt;/h2&gt;

&lt;p&gt;CDI is not a code-maximization tool. Every prompt has to be clinically defensible and every captured diagnosis must reflect a condition actually evaluated and addressed at the visit. Build your prompts to be neutral and evidence-based, not leading, so the resulting documentation holds up under a RADV (Risk Adjustment Data Validation) audit. If your engine nudges clinicians toward unsupported codes, you're building audit liability, not value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring whether it works
&lt;/h2&gt;

&lt;p&gt;Track the boring metrics: query response rate, query agreement rate, recapture rate of chronic conditions year over year, and the delta between suspected and confirmed conditions. If your prompts have a low agreement rate, they're probably low-quality or poorly timed — fix the evidence linkage before you add more rules.&lt;/p&gt;

&lt;p&gt;For the clinical playbook side of this — how CDI specialists actually structure queries and partner with providers — see the resources maintained by &lt;a href="https://www.vbcriskanalytics.com/cdi-resources?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p063" rel="noopener noreferrer"&gt;VBC Risk Analytics&lt;/a&gt;. The full featured article walks through the operating model end to end: &lt;a href="https://www.vbcriskanalytics.com/blogs/cdi-missing-link-hcc-risk-capture?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p063" rel="noopener noreferrer"&gt;CDI: The Missing Link to Improve HCC Risk Capture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Done right, CDI is the difference between a scoring engine that's technically correct and a risk picture that's actually complete.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cdi</category>
      <category>hcc</category>
      <category>healthtech</category>
      <category>riskadjustment</category>
    </item>
    <item>
      <title>V24 vs V28: A Side-by-Side for Engineers</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Mon, 10 Aug 2026 17:28:38 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/v24-vs-v28-a-side-by-side-for-engineers-1162</link>
      <guid>https://dev.to/vbc_risk_analytics/v24-vs-v28-a-side-by-side-for-engineers-1162</guid>
      <description>&lt;p&gt;You're not comparing two clinical philosophies — you're comparing two coefficient/crosswalk artifacts that must both run during the blend. Here's the diff that matters in code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;V24&lt;/th&gt;
&lt;th&gt;V28&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HCC IDs&lt;/td&gt;
&lt;td&gt;one numbering&lt;/td&gt;
&lt;td&gt;renumbered/reorganized — &lt;strong&gt;not&lt;/strong&gt; 1:1 with V24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payable categories&lt;/td&gt;
&lt;td&gt;one set&lt;/td&gt;
&lt;td&gt;revised; some V24-payable conditions drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coefficients&lt;/td&gt;
&lt;td&gt;V24 table&lt;/td&gt;
&lt;td&gt;re-estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status during transition&lt;/td&gt;
&lt;td&gt;weighted with V28&lt;/td&gt;
&lt;td&gt;weighted with V24 (blend)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Implementation implications
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No shared HCC enum.&lt;/strong&gt; Tag every HCC value with its model version or you will eventually compare V24 #18 to V28 #18 and they are not the same thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two crosswalks + two coefficient tables&lt;/strong&gt;, both keyed by payment year. Treat them as versioned data, not config constants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blend at the RAF layer&lt;/strong&gt;, not the HCC layer: &lt;code&gt;blended = w24*RAF_v24 + w28*RAF_v28&lt;/code&gt;, weights from the payment-year Rate Announcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Golden tests per version.&lt;/strong&gt; A fixed member set with expected RAF under each model; run on every coefficient-table update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A side-by-side of the category remap, plus a calculator that outputs the same member under both models, is here: &lt;a href="https://www.rafscorecalculator.com/hcc-v28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p27" rel="noopener noreferrer"&gt;HCC V28&lt;/a&gt; — useful as the reference for those golden tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The non-code part
&lt;/h2&gt;

&lt;p&gt;All of this serves one goal: the &lt;em&gt;accurate&lt;/em&gt; RAF for the documented patient under the correct payment-year blend. Engineering correctness here isn't pedantry — a silently wrong model version mis-funds real patients at scale. Cross-check your output against &lt;a href="https://www.rafscorecalculator.com/hcc-v28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p27" rel="noopener noreferrer"&gt;the V28 model&lt;/a&gt; before trusting it downstream.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not coding, billing, or clinical advice. Confirm coefficients/weights against the current CMS Rate Announcement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written by Chin Ramamoorthi&lt;/strong&gt; — CEO, VBC Risk Analytics. He has 20+ years across provider- and payer-side healthcare IT, including leading V24-to-V28 transition work on both the payer and provider side.&lt;br&gt;
&lt;strong&gt;Reviewed by the VBC Risk Analytics Risk Adjustment &amp;amp; Coding Team.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Last updated:&lt;/strong&gt; &lt;strong&gt;August 2026&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>data</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How to Efficiently Compute RAF Scores for 100,000+ Members in a Nightly Batch Job</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Fri, 31 Jul 2026 18:06:11 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/how-to-efficiently-compute-raf-scores-for-100000-members-in-a-nightly-batch-job-27ck</link>
      <guid>https://dev.to/vbc_risk_analytics/how-to-efficiently-compute-raf-scores-for-100000-members-in-a-nightly-batch-job-27ck</guid>
      <description>&lt;p&gt;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 &lt;strong&gt;3-step async job&lt;/strong&gt;, not a "POST a file, get JSON back" call — get that shape right and the rest is easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Submit, poll, download (3 steps)
&lt;/h2&gt;

&lt;p&gt;Base URL &lt;code&gt;https://www.vbcriskanalytics.com/raf-batch-api&lt;/code&gt;. Every request needs two headers — &lt;code&gt;ApiKey: &amp;lt;your-batch-key&amp;gt;&lt;/code&gt; and an empty &lt;code&gt;X-CSRF-TOKEN:&lt;/code&gt; (it's &lt;strong&gt;not&lt;/strong&gt; Bearer).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: submit as multipart/form-data -&amp;gt; returns a raf_batch_id&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://www.vbcriskanalytics.com/raf-batch-api/getPreProspectScore'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: */*'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-CSRF-TOKEN: '&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'risk_model=CMS-HCC-V28 Continuing Enrollee'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'risk_factor=Community NonDual Aged'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'file=@input_pre_prospective.csv;type=text/csv'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; {"code":201,"raf_batch_id":3400,"status":"Queued",&lt;/span&gt;
&lt;span class="c"&gt;#     "check_status_url":".../raf-batch-api/check-status/3400"}&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: poll until Completed (Queued 201 -&amp;gt; Running 202 -&amp;gt; Completed 200)&lt;/span&gt;
curl &lt;span class="s1"&gt;'.../raf-batch-api/check-status/3400'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-CSRF-TOKEN: '&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: get the signed download URL&lt;/span&gt;
curl &lt;span class="s1"&gt;'.../raf-batch-api/download/3400'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-CSRF-TOKEN: '&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; {"download_url":"https://...s3.amazonaws.com/...zip?X-Amz-...","status":"Completed"}&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: GET that S3 URL directly — NO ApiKey header, it EXPIRES in ~120s.&lt;/span&gt;
&lt;span class="c"&gt;#          You get a .zip with a timestamped .xlsx of scored members.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Submit endpoints map to score types: &lt;code&gt;/getPreProspectScore&lt;/code&gt;, &lt;code&gt;/getPostProspectScore&lt;/code&gt;, &lt;code&gt;/getPostCncntScore&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Get the CSV schema right — one row per (member, diagnosis)
&lt;/h2&gt;

&lt;p&gt;This trips people up: it's &lt;strong&gt;not&lt;/strong&gt; one row per member. Rows are grouped by &lt;code&gt;ID&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Gender&lt;/code&gt; is &lt;code&gt;Male&lt;/code&gt;/&lt;code&gt;Female&lt;/code&gt;, &lt;code&gt;Age&lt;/code&gt; ≤ 125, the dot in the ICD-10 code is optional, and valid &lt;code&gt;Flag&lt;/code&gt; values depend on score type (Pre-Prospective &lt;code&gt;Last_Year&lt;/code&gt;/&lt;code&gt;Current_Year&lt;/code&gt;; Post-Prospective &lt;code&gt;New&lt;/code&gt;/&lt;code&gt;Billed&lt;/code&gt;/&lt;code&gt;Missed&lt;/code&gt;; Post-Concurrent &lt;code&gt;No_Changes&lt;/code&gt;/&lt;code&gt;Deletion&lt;/code&gt;/&lt;code&gt;Addition&lt;/code&gt; plus a &lt;code&gt;Modification_To&lt;/code&gt; column).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. For throughput, fan out jobs and poll asynchronously
&lt;/h2&gt;

&lt;p&gt;Split the population into CSV chunks, submit them all, then poll each to &lt;code&gt;Completed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.vbcriskanalytics.com/raf-batch-api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ApiKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-CSRF-TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/getPreProspectScore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;risk_model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS-HCC-V28 Continuing Enrollee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;risk_factor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Community NonDual Aged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf_batch_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk_paths&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# submit all chunks
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;jid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                 &lt;span class="c1"&gt;# then poll each
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/check-status/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;jid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/download/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;jid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;download_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scored_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;jid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# signed URL, no ApiKey
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Make it idempotent
&lt;/h2&gt;

&lt;p&gt;The API returns a durable &lt;code&gt;raf_batch_id&lt;/code&gt; and a &lt;code&gt;check-status&lt;/code&gt; 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 &lt;code&gt;available hits&lt;/code&gt; quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate before you submit&lt;/strong&gt; so you don't burn quota on bad rows. The API returns specific codes: &lt;code&gt;418&lt;/code&gt; invalid gender, &lt;code&gt;419&lt;/code&gt; age &amp;gt; 125, &lt;code&gt;420&lt;/code&gt; columns missing, &lt;code&gt;427&lt;/code&gt; only CSV allowed, &lt;code&gt;429&lt;/code&gt; invalid flag, &lt;code&gt;425&lt;/code&gt;/&lt;code&gt;426&lt;/code&gt; invalid risk model/factor, &lt;code&gt;430&lt;/code&gt; license/limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store the &lt;code&gt;risk_model&lt;/code&gt;/&lt;code&gt;risk_factor&lt;/code&gt;&lt;/strong&gt; you submitted with each output so historical scores stay reproducible for audits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you'd rather not maintain the model tables and hierarchies yourself, there's a good writeup on a managed approach to &lt;a href="https://www.vbcriskanalytics.com/blogs/batch-raf-scoring?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p055" rel="noopener noreferrer"&gt;batch RAF scoring for large member populations&lt;/a&gt;, and the &lt;a href="https://www.vbcriskanalytics.com/raf-batch?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p055" rel="noopener noreferrer"&gt;batch RAF API&lt;/a&gt; that handles the overnight throughput.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Engineering a Defensible Suspect-Condition Pipeline (Identify Validate Capture)</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Sat, 18 Jul 2026 02:56:28 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/engineering-a-defensible-suspect-condition-pipeline-identify-validate-capture-1a39</link>
      <guid>https://dev.to/vbc_risk_analytics/engineering-a-defensible-suspect-condition-pipeline-identify-validate-capture-1a39</guid>
      <description>&lt;p&gt;Suspect-condition workflows are deceptively simple to prototype and surprisingly hard to make &lt;em&gt;defensible&lt;/em&gt;. Anyone can flag "this member might have HCC X." Building a system whose output survives a RADV audit is a different problem. This is a walkthrough of the three stages and the engineering decisions that matter at each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Identify
&lt;/h2&gt;

&lt;p&gt;Identification is pattern detection over a member's clinical record — labs, medications, prior diagnoses, utilization. Model it as a set of rules or features that emit candidate HCCs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;identify_suspects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;suspects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a1c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;9.0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insulin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;suspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HCC38&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trigger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a1c&amp;gt;=9 + insulin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;egfr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;egfr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;suspects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HCC326&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trigger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;egfr&amp;lt;30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;suspects&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The temptation is to maximize recall here — flag everything. Resist it. Every unvalidated suspect you generate is downstream work and downstream risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Validate (the stage that actually matters)
&lt;/h2&gt;

&lt;p&gt;Validation attaches &lt;em&gt;evidence&lt;/em&gt; to each suspect and scores its defensibility. This is the difference between a documentation opportunity and an audit liability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collect_evidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# labs, rx, prior dx
&lt;/span&gt;    &lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;
    &lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;score_evidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defensible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;suspect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;suspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design rule: a suspect with an empty &lt;code&gt;evidence&lt;/code&gt; array should never reach a coder. Make that a hard gate, not a soft warning. Under CMS-HCC V28 and current audit posture, a captured-but-unsupported diagnosis can be extrapolated across a contract into a real clawback — so "defensible by default" is the right engineering stance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Capture
&lt;/h2&gt;

&lt;p&gt;Capture routes validated suspects to the right human with the evidence inline, so the clinician or coder can confirm and document efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-50921"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hcc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HCC38"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a1c:9.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rx:insulin_glargine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prior_dx:E11.65"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirm_and_document"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Track two metrics relentlessly: &lt;strong&gt;validated-suspect capture rate&lt;/strong&gt; and &lt;strong&gt;audit-fail rate of captured codes.&lt;/strong&gt; They tell you whether the pipeline produces durable revenue or just volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't reinvent the model layer
&lt;/h2&gt;

&lt;p&gt;The mapping from clinical signals to HCCs — and the V28 weighting behind it — is version-sensitive and audit-relevant. Grounding the pipeline on a maintained engine for &lt;a href="https://www.vbcriskanalytics.com/risk-adjustment-analytics?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p039" rel="noopener noreferrer"&gt;suspect conditions risk adjustment&lt;/a&gt; saves you from owning a copy of the model tables that drifts out of date.&lt;/p&gt;

&lt;p&gt;The non-code framing of the same workflow is in &lt;a href="https://www.vbcriskanalytics.com/blogs/suspect-conditions-risk-adjustment?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p039" rel="noopener noreferrer"&gt;Suspect Conditions: Identify, Validate, Capture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>dataengineering</category>
      <category>python</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building a Population Health Risk Stratification Pipeline for MA Plans</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Wed, 15 Jul 2026 15:57:58 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/building-a-population-health-risk-stratification-pipeline-for-ma-plans-15j7</link>
      <guid>https://dev.to/vbc_risk_analytics/building-a-population-health-risk-stratification-pipeline-for-ma-plans-15j7</guid>
      <description>&lt;p&gt;Risk stratification sounds like a data-science buzzword until you have to build the thing. For a Medicare Advantage plan, it's a concrete pipeline: take a population of members, score each one's clinical and financial risk, and rank them so care management and documentation teams know who to touch first. Here's how I'd architect it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Population health risk stratification = scoring + segmentation. You compute a per-member risk signal, then bucket members into tiers (e.g., rising-risk, high-risk, catastrophic) so finite resources go where they move outcomes and revenue most.&lt;/p&gt;

&lt;p&gt;The mistake teams make is treating it as a single ML model. In practice you want a layered signal: a stable, explainable base (RAF + chronic conditions) plus optional predictive overlays. Explainability matters because care managers won't act on a black-box score, and auditors won't accept one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build the member feature record
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-77310"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hccs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HCC37_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HCC85"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HCC18"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"raf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.842&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gaps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a1c_overdue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no_pcp_visit_180d"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"utilization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ed_visits_12m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inpatient_12m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RAF here is your defensible, model-grounded risk anchor under CMS-HCC V28. Everything else is supplemental signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Score and tier
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;risk_tier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utilization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ed_visits_12m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; \
         &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utilization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inpatient_12m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;util&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;catastrophic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rising&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the weights transparent and tunable. The point isn't a perfect model; it's a defensible, reproducible ranking your operational teams trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Make "rising-risk" actionable
&lt;/h2&gt;

&lt;p&gt;The tier that quietly drives the most ROI is &lt;em&gt;rising-risk&lt;/em&gt; — members trending toward high cost who still have open documentation and care gaps. Surface their specific gaps (overdue labs, undocumented chronic conditions) so outreach has a concrete target instead of a vague "high risk" label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Close the loop with documentation
&lt;/h2&gt;

&lt;p&gt;Stratification that doesn't feed back into documentation is half a system. When a rising-risk member has a suspected-but-undocumented chronic condition, that's both a care opportunity and a RAF-accuracy opportunity. The pipeline should emit those as work items.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't rebuild the risk model from scratch
&lt;/h2&gt;

&lt;p&gt;You can hand-roll feature engineering, but the HCC mapping and V28 weighting logic is fiddly, version-sensitive, and audit-relevant. It's worth grounding the pipeline on a maintained &lt;a href="https://www.vbcriskanalytics.com/risk-adjustment-analytics?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p036" rel="noopener noreferrer"&gt;VBC Risk Analytics population health risk stratification engine&lt;/a&gt; rather than maintaining your own copy of the model tables.&lt;/p&gt;

&lt;p&gt;For the non-engineering framing of how MA plans use stratification operationally, see &lt;a href="https://www.vbcriskanalytics.com/blogs/population-health-risk-assessment?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p036" rel="noopener noreferrer"&gt;Population Health Risk Stratification for MA Plans&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>dataengineering</category>
      <category>datascience</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Designing an API-First Value-Based Care Analytics Stack for MA Payers</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Fri, 10 Jul 2026 01:02:57 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/designing-an-api-first-value-based-care-analytics-stack-for-ma-payers-54ij</link>
      <guid>https://dev.to/vbc_risk_analytics/designing-an-api-first-value-based-care-analytics-stack-for-ma-payers-54ij</guid>
      <description>&lt;p&gt;If you build software for Medicare Advantage (MA) plans, "analytics" usually arrives as a vague requirement and leaves as a pile of nightly batch jobs and a BI dashboard nobody trusts. This post is about treating value-based care analytics as an &lt;em&gt;engineering&lt;/em&gt; problem: data contracts, idempotent scoring, and auditability baked into the API surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The domain in one paragraph
&lt;/h2&gt;

&lt;p&gt;Each member has diagnoses (ICD-10-CM codes) that map to Hierarchical Condition Categories (HCCs). HCCs, plus demographics, produce a Risk Adjustment Factor (RAF) under the CMS-HCC V28 model. RAF drives the plan's payment. So the analytics platform's job is to take coded encounters in, and emit defensible RAF and gap insights out — reproducibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Model the inputs as contracts, not files
&lt;/h2&gt;

&lt;p&gt;The single biggest source of bad analytics is loose ingestion. Define a strict schema for encounters and enforce it at the edge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-100245"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dx_codes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"E11.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I50.32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"N18.4"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider_npi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0000000000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reject malformed records loudly instead of silently dropping them. A diagnosis that never made it into the pipeline is a RAF dollar that quietly disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make RAF scoring a pure function
&lt;/h2&gt;

&lt;p&gt;RAF computation should be deterministic: given the same member inputs and the same model version, you get the same score. Pin the model version explicitly.&lt;/p&gt;

&lt;p&gt;You can build this yourself, but the hierarchy and coefficient logic is exactly what a scoring API exists to own. Calling one keeps your platform a pure pass-through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;


&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS-HCC-V28 Continuing Enrollee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_raf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://restapi.npidataservices.com/raf/api/v1/getScore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ApiKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAF_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# custom header, NOT Authorization: Bearer
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;factor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Community NonDual Aged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gender&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gender&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;            &lt;span class="c1"&gt;# "MALE" | "FEMALE"
&lt;/span&gt;            &lt;span class="c1"&gt;# ICD-10-CM WITHOUT dots: "E11.9" -&amp;gt; "E119"
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HCC_Codes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dx_codes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Grand Total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAF_Score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two engineering points matter here. First, the hierarchy ("trumping" within an HCC family, where a more severe condition suppresses milder ones) is applied server-side — don't reimplement it, or you'll overstate the score by summing raw weights. Second, store the &lt;code&gt;model&lt;/code&gt; string with every result; when you re-run history you must reproduce the score that was live at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat suspecting as evidence-linked, not magic
&lt;/h2&gt;

&lt;p&gt;A "suspect" condition should carry its supporting evidence (labs, meds, prior dx) as structured fields, so downstream consumers can rank by defensibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-100245"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suspect_hcc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HCC38"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a1c&amp;gt;9.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rx:insulin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prior_dx:E11.65"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suspects without an evidence array are a code smell — and an audit liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build audit simulation as a first-class endpoint
&lt;/h2&gt;

&lt;p&gt;The most underrated feature: let the plan sample its own population and model RADV extrapolation exposure before CMS does. It's just sampling + documentation scoring + an extrapolation formula over synthetic data, but exposing it as an API turns "are we defensible?" into a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why API-first wins
&lt;/h2&gt;

&lt;p&gt;Dashboards are a presentation layer; the durable asset is a clean, versioned API that any tool can consume. If you want the product-level view of what MA payers actually need from this stack in 2026, &lt;a href="https://www.vbcriskanalytics.com/blogs/value-based-care-analytics-payers?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p033" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; lays it out, and the broader &lt;a href="https://www.vbcriskanalytics.com/risk-adjustment-analytics-payers?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p033" rel="noopener noreferrer"&gt;risk adjustment analytics platform for payers&lt;/a&gt; shows how the pieces compose end to end.&lt;/p&gt;

&lt;p&gt;Build the contracts first. The dashboards get easy after that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>api</category>
      <category>dataengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How ICD-10 Codes Map to an HCC / RAF Score (and Why the Same Code Set Yields Different Scores)</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Wed, 08 Jul 2026 17:22:17 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/how-icd-10-codes-map-to-an-hcc-raf-score-and-why-the-same-code-set-yields-different-scores-25l2</link>
      <guid>https://dev.to/vbc_risk_analytics/how-icd-10-codes-map-to-an-hcc-raf-score-and-why-the-same-code-set-yields-different-scores-25l2</guid>
      <description>&lt;p&gt;If you're building anything that touches Medicare Advantage risk adjustment, sooner or later you hit this question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"How do ICD-10 codes map to an HCC / RAF score?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The short answer that trips most people up: &lt;strong&gt;ICD-10-CM codes don't carry a RAF weight individually.&lt;/strong&gt; There's a pipeline between the diagnosis code and the number, and skipping a step gives you a wrong score. Here's the whole chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. ICD-10 → HCC
&lt;/h3&gt;

&lt;p&gt;Each qualifying diagnosis maps into a &lt;em&gt;Hierarchical Condition Category&lt;/em&gt; (HCC) — a clinically grouped bucket. Many ICD-10 codes collapse into a single HCC, and &lt;strong&gt;not every ICD-10 code maps to a payable HCC at all.&lt;/strong&gt; The crosswalk is many-to-one, and it's lossy by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Apply the hierarchies
&lt;/h3&gt;

&lt;p&gt;Within a disease family, only the most severe HCC is kept. A more severe HCC "traps" the milder related HCCs so the model doesn't pay twice for the same underlying problem.&lt;/p&gt;

&lt;p&gt;The critical part for implementers: &lt;strong&gt;do this before you sum anything.&lt;/strong&gt; If you sum coefficients first and trump later, you'll double-count.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sum the coefficients
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAF = demographic_factor
    + Σ (coefficient for each surviving HCC)
    + interaction_terms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demographic factor comes from age/sex (and enrollment characteristics). Interaction terms are extra weight for specific disease combinations — they aren't just additive HCCs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Mind the model version
&lt;/h3&gt;

&lt;p&gt;This is the one that silently breaks reconciliations. &lt;strong&gt;CMS-HCC V24 and V28 use different HCC maps &lt;em&gt;and&lt;/em&gt; different coefficients.&lt;/strong&gt; The exact same set of ICD-10 codes produces a different RAF depending on the model year you run it against. If two systems disagree on a member's score, "which model version?" is the first question to ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Need Programmatically
&lt;/h2&gt;

&lt;p&gt;To compute this yourself, you need four artifacts — all keyed to the correct payment year:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A current &lt;strong&gt;ICD-10 → HCC crosswalk&lt;/strong&gt; for the target model version&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;hierarchy (trumping) table&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;coefficient table&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;interaction list&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get the payment year wrong on any one of these and the score is wrong. These tables change annually with the CMS Rate Announcement, so a cached copy from two years ago is a bug waiting to happen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sanity-Checking Your Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I'm the CEO of VBC Risk Analytics and we build rafscorecalculator.com, so I have a financial interest in what follows. With that caveat out of the way — if you want to verify your own implementation, the &lt;a href="https://www.rafscorecalculator.com/hcc-raf?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=icd10-hcc" rel="noopener noreferrer"&gt;RAF Score Calculator&lt;/a&gt; walks the ICD-10 → HCC → RAF chain with worked examples and computes a member under &lt;strong&gt;both V24 and V28&lt;/strong&gt;. It's handy as a reference oracle for unit tests when you're validating your own crosswalk logic.&lt;/p&gt;




&lt;p&gt;This is a general explanation of the mechanics, not coding or billing advice. HCC and coefficient specs change every payment year — always validate against the current CMS Rate Announcement rather than a table you cached last cycle.&lt;/p&gt;

&lt;p&gt;If you're implementing this and hit an edge case with hierarchies or interaction terms, drop it in the comments.&lt;/p&gt;

</description>
      <category>healthcare</category>
      <category>api</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Automating RAF Scoring in Real Time: An Architecture Walkthrough</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:38:37 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/automating-raf-scoring-in-real-time-an-architecture-walkthrough-8ci</link>
      <guid>https://dev.to/vbc_risk_analytics/automating-raf-scoring-in-real-time-an-architecture-walkthrough-8ci</guid>
      <description>&lt;p&gt;If you've ever built a batch RAF job, you know the awkward truth: by the time the score lands, the encounter that produced it is long over. The interesting engineering challenge is moving that calculation from a nightly batch to a real-time, event-driven service. Here's how I think about the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're computing
&lt;/h2&gt;

&lt;p&gt;The Risk Adjustment Factor (RAF) is built from demographic factors plus clinical conditions expressed as Hierarchical Condition Categories (HCCs), each weighted by a coefficient under CMS-HCC V28. The math itself is simple addition. The hard part is doing it &lt;em&gt;as data arrives&lt;/em&gt;, deterministically, with an audit trail.&lt;/p&gt;

&lt;p&gt;For the conceptual background on why real-time scoring improves accuracy, this writeup on &lt;a href="https://www.vbcriskanalytics.com/blogs/raf-score-automation?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p027" rel="noopener noreferrer"&gt;how RAF score automation works&lt;/a&gt; is a good companion read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event-driven, not batch
&lt;/h2&gt;

&lt;p&gt;The shift is from "scan everything nightly" to "recompute the affected member when their data changes."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new_diagnosis_event ──&amp;gt; map ICD-10 -&amp;gt; HCC ──&amp;gt; recompute member RAF ──&amp;gt; emit score event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new confirmed diagnosis, a corrected code, or a model-year change triggers a recompute of just that member, not the whole population.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;


&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS-HCC-V28 Continuing Enrollee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# pinned, never implicit
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_diagnosis_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;member_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://restapi.npidataservices.com/raf/api/v1/getScore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ApiKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAF_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# custom header, NOT Bearer
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;factor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Community NonDual Aged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gender&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;# "MALE" | "FEMALE"
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HCC_Codes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;icd10_no_dots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# e.g. ["E119", "I509"]
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                            &lt;span class="c1"&gt;# itemized, additive components
&lt;/span&gt;    &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf.updated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;member_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Grand Total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAF_Score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;breakdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;               &lt;span class="c1"&gt;# Demographic + Diagnosis + interactions
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Determinism is the whole ballgame
&lt;/h2&gt;

&lt;p&gt;Real-time scoring is only useful if it's reproducible. Two requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin the model version.&lt;/strong&gt; A score computed today must be reproducible later. Pass &lt;code&gt;model&lt;/code&gt; explicitly (e.g. &lt;code&gt;"CMS-HCC-V28 Continuing Enrollee"&lt;/code&gt;); never let the crosswalk float implicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Itemize the output.&lt;/strong&gt; A bare RAF is undebuggable and indefensible. The &lt;code&gt;getScore&lt;/code&gt; response is already itemized into additive components — &lt;code&gt;Demographic&lt;/code&gt;, per-HCC &lt;code&gt;Diagnosis&lt;/code&gt;, &lt;code&gt;Disease Interaction&lt;/code&gt;, and a &lt;code&gt;Total&lt;/code&gt; block — each carrying both a &lt;code&gt;RAF_Score&lt;/code&gt; (coefficient) and an &lt;code&gt;MA_Payment&lt;/code&gt; (dollars). That structure is exactly what you need when a RADV (Risk Adjustment Data Validation) audit asks how a number was derived.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_usage_log_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;439229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Demographic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Age and Gender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3453.58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.332&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Diagnosis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HCC 226"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3744.84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.36&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HCC 38"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1726.79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.166&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HCC 328"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1321.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.127&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HCC Count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;520.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Disease Interaction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"DIABETES_HF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1165.06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.112&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HF_KIDNEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1830.81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.176&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Grand Total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19826.87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.906&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"MA_Adjusted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;17485.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.681&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Normalized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MA_Payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;18581.88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"RAF_Score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.786&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score_cnt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Synthetic.) One parsing gotcha: some fields — like &lt;code&gt;HCC 328&lt;/code&gt;'s &lt;code&gt;MA_Payment&lt;/code&gt; above — come back as a quoted string (&lt;code&gt;"1321.10"&lt;/code&gt;), so coerce to a number defensively rather than assuming JSON floats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency and ordering
&lt;/h2&gt;

&lt;p&gt;Events arrive out of order and get redelivered. Two defenses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent recompute.&lt;/strong&gt; Recomputing from the member's current state (not by incrementally mutating a score) means a duplicate event is harmless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version your member state.&lt;/strong&gt; Tag each recompute with the input version so a late-arriving stale event can be safely ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Batch still has a place: backfill and roster-wide reruns
&lt;/h2&gt;

&lt;p&gt;Real-time handles the steady-state stream, but you still need a batch path for the initial population load and for re-scoring everyone after a model-year change. That runs against a separate batch API as a 3-step job (base &lt;code&gt;https://www.vbcriskanalytics.com/raf-batch-api&lt;/code&gt;, auth &lt;code&gt;ApiKey: &amp;lt;key&amp;gt;&lt;/code&gt; plus an empty &lt;code&gt;X-CSRF-TOKEN:&lt;/code&gt; header on every call):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) submit a CSV of (member, diagnosis) rows -&amp;gt; returns a job id&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://www.vbcriskanalytics.com/raf-batch-api/getPreProspectScore &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-CSRF-TOKEN: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"risk_model=CMS-HCC-V28 Continuing Enrollee"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"risk_factor=Community NonDual Aged"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@members.csv"&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; {"code":201,"raf_batch_id":3400,"status":"Queued","check_status_url":"..."}&lt;/span&gt;


&lt;span class="c"&gt;# 2) poll status: Queued -&amp;gt; Running -&amp;gt; Completed (Completed returns a download_url)&lt;/span&gt;
curl https://www.vbcriskanalytics.com/raf-batch-api/check-status/3400 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-CSRF-TOKEN: "&lt;/span&gt;


&lt;span class="c"&gt;# 3) download the result (short-lived ~120s signed S3 .zip wrapping an .xlsx)&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://www.vbcriskanalytics.com/raf-batch-api/download/3400 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_BATCH_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-CSRF-TOKEN: "&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; results.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSV is one row per &lt;code&gt;(member, diagnosis)&lt;/code&gt; with columns &lt;code&gt;ID,Gender,Age,ICD-10 CM Code,Flag&lt;/code&gt;, where &lt;code&gt;Flag&lt;/code&gt; (the Pre-Prospective Flag) is &lt;code&gt;Last_Year&lt;/code&gt; or &lt;code&gt;Current_Year&lt;/code&gt;. Treat the download URL as ephemeral — it expires in about two minutes, so fetch it immediately once status is &lt;code&gt;Completed&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let automation launder bad data
&lt;/h2&gt;

&lt;p&gt;This is the failure mode worth stating plainly: automation amplifies whatever logic you give it. If a mapping is wrong or a diagnosis is unsupported, real-time scoring just produces wrong numbers faster and at scale. Build the accuracy into the rules — unsupported-HCC checks, specificity flags — &lt;em&gt;upstream&lt;/em&gt; of the scorer, not as an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic fixtures only.&lt;/strong&gt; Generate illustrative members that exercise each HCC family and interaction term. Never test against live records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Golden tests.&lt;/strong&gt; Pin known inputs to known outputs per model version, so a coefficient change can't silently alter historical scores.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;An event-driven, deterministic RAF service puts an accurate, explainable score where it can influence care and documentation in the moment — and makes audit defense a query rather than a scramble. The full conceptual treatment of accuracy gains lives in the companion article above; this post is the architecture behind it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>healthtech</category>
      <category>api</category>
      <category>python</category>
      <category>vbc</category>
    </item>
    <item>
      <title>HCC V28 Transition Timeline &amp; Phase-In Impact</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Wed, 01 Jul 2026 18:12:56 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/hcc-v28-transition-timeline-phase-in-impact-29en</link>
      <guid>https://dev.to/vbc_risk_analytics/hcc-v28-transition-timeline-phase-in-impact-29en</guid>
      <description>&lt;p&gt;V28 didn't replace V24 overnight. CMS blends the two models over multiple payment years, which means for the duration of the transition your RAF is a &lt;em&gt;weighted mix&lt;/em&gt; of two models. If your pipeline treats the switch as a flag flip, your numbers are wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blend, conceptually
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;blended_RAF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w_v28&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;RAF_v28&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w_v24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;RAF_v24&lt;/span&gt;
&lt;span class="c1"&gt;# w_v28 increases each payment year until V28 is 100%
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact weights per payment year are set in the CMS Rate Announcement.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Re-read it for the actual year — do not hard-code last year's split.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What that does to your data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YoY RAF deltas are contaminated.&lt;/strong&gt; Part of any year-over-year change is just &lt;code&gt;w_v28&lt;/code&gt; increasing. Decompose every trend into &lt;em&gt;model shift&lt;/em&gt; vs. &lt;em&gt;true population/documentation shift&lt;/em&gt;, or your dashboards mislead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two engines, not one.&lt;/strong&gt; You must compute both &lt;code&gt;RAF_v24&lt;/code&gt; and &lt;code&gt;RAF_v28&lt;/code&gt; for every member through the entire blend, then weight. Retire V24 only when its weight reaches zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category drift.&lt;/strong&gt; Some conditions that paid under V24 don't under V28; documentation that was sufficient before can under-capture now. Re-baseline historical members under the V28 model to see your real exposure. A category-by-category view of &lt;a href="https://www.rafscorecalculator.com/compare-raf-score-version-24-vs-version-28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p17" rel="noopener noreferrer"&gt;the V24-vs-V28 differences&lt;/a&gt; helps you spot which families to watch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minimal ops checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Load both coefficient/crosswalk sets, keyed by payment year.&lt;/li&gt;
&lt;li&gt;Compute V24 + V28 per member; apply the year's blend weights.&lt;/li&gt;
&lt;li&gt;Report model-shift and true-shift as separate series.&lt;/li&gt;
&lt;li&gt;Re-score history under V28 for a clean baseline.&lt;/li&gt;
&lt;li&gt;Re-verify weights and category counts against the current CMS Rate Announcement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A side-by-side of the remap — plus a tool that computes a member under both models, so you can see the blend's effect on a real case — is here: &lt;a href="https://www.rafscorecalculator.com/hcc-v28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p17" rel="noopener noreferrer"&gt;HCC V28&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The point of all this is accuracy under the correct blended model for the payment year — not chasing a higher number.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not coding, billing, or clinical advice. Phase-in weights change by payment year — confirm against the current CMS Rate Announcement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written by Chin Ramamoorthi&lt;/strong&gt; — CEO, VBC Risk Analytics. He has 20+ years across provider- and payer-side healthcare IT, including leading V24-to-V28 transition work on both the payer and provider side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewed by the VBC Risk Analytics Risk Adjustment &amp;amp; Coding Team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last updated:&lt;/strong&gt; July 2026&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>Risk Stratification as a Data Pipeline: Turning RAF Into a Worklist</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Thu, 25 Jun 2026 15:08:09 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/risk-stratification-as-a-data-pipeline-turning-raf-into-a-worklist-49k1</link>
      <guid>https://dev.to/vbc_risk_analytics/risk-stratification-as-a-data-pipeline-turning-raf-into-a-worklist-49k1</guid>
      <description>&lt;p&gt;Risk stratification sounds like a clinical strategy, but for the engineers who build it, it's a ranking pipeline. You take a population, score each member's expected need, sort, and hand a care team a prioritized list. This post is about how that pipeline is actually wired in a Medicare Advantage context.&lt;br&gt;
﻿&lt;/p&gt;
&lt;h2&gt;
  
  
  The goal, stated as a function
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;stratify(population) -&amp;gt; ranked_list_of_members_by_expected_need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
﻿The hard parts are choosing the score, making it explainable, and refreshing it on a cadence that's useful to humans.&lt;br&gt;
﻿&lt;/p&gt;
&lt;h2&gt;
  
  
  The inputs
&lt;/h2&gt;

&lt;p&gt;A useful stratification model blends several signals. In Medicare Advantage, the natural backbone is the same data that drives funding accuracy:&lt;/p&gt;

&lt;p&gt;﻿- &lt;strong&gt;RAF (Risk Adjustment Factor)&lt;/strong&gt; — the normalized expected-cost score built from demographics and HCCs (CMS-HCC V28).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HCC composition&lt;/strong&gt; — &lt;em&gt;which&lt;/em&gt; conditions, not just the total. Two members with the same RAF can need very different interventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilization signals&lt;/strong&gt; — recent admissions, ED visits, polypharmacy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gaps&lt;/strong&gt; — suspected-but-undocumented conditions (a stratification model that ignores gaps under-ranks the sickest, least-documented members).
﻿
This is exactly the approach walked through in this &lt;a href="https://www.vbcriskanalytics.com/case-study/medicare-patient-risk-stratification?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p021" rel="noopener noreferrer"&gt;medicare advantage risk adjustment&lt;/a&gt; case study, where risk data became a concrete outreach list.
﻿
## A minimal scoring sketch
﻿

&lt;code&gt;python
def member_score(m):
"""Synthetic, illustrative weighting only."""
score = m.raf * W_RAF
score += m.recent_admissions * W_ADMIT
score += len(m.open_gaps) * W_GAP
score += m.polypharmacy_flag * W_RX
return score
﻿
ranked = sorted(population, key=member_score, reverse=True)
&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The weights are policy decisions, not engineering ones — surface them in config, don't bury them in code. Your clinical leadership will want to tune them, and your auditors will want to see them.&lt;br&gt;
﻿&lt;/p&gt;
&lt;h2&gt;
  
  
  Explainability is not optional
&lt;/h2&gt;

&lt;p&gt;﻿A stratification score that a care manager can't interrogate is a score they won't trust. For every ranked member, emit the contribution breakdown:&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-10293"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"drivers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"raf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recent_admissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"open_gaps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"polypharmacy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
Now "why is this member #1?" has a one-glance answer.&lt;br&gt;
﻿&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh cadence and stability
&lt;/h2&gt;

&lt;p&gt;﻿- &lt;strong&gt;Recompute on a schedule&lt;/strong&gt; (often monthly) and snapshot each run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watch for churn.&lt;/strong&gt; If members thrash in and out of the top tier week to week, your weights are too sensitive — care teams need stability to actually act.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track outcomes back to the score.&lt;/strong&gt; The point isn't the ranking; it's whether outreach to high-ranked members changed anything.
﻿&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Engineering guardrails
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic data only&lt;/strong&gt; in dev and test. Generate illustrative populations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version the model.&lt;/strong&gt; A stratification run should be reproducible later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate scoring from action.&lt;/strong&gt; The pipeline ranks; humans decide. Don't auto-trigger interventions off a raw score.
﻿&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;﻿Done well, stratification connects the backward-looking RAF to forward-looking care: instead of reconciling last year's risk, you're deciding who to reach this month. For the full data-side walkthrough that complements this pipeline view, see the &lt;a href="https://www.vbcriskanalytics.com/blogs/risk-stratification-medicare-advantage?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p021" rel="noopener noreferrer"&gt;Medicare Advantage risk stratification guide&lt;/a&gt;.&lt;br&gt;
﻿&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>medicareadvantage</category>
      <category>riskstratification</category>
      <category>hcc</category>
      <category>healthcaredata</category>
    </item>
    <item>
      <title>HCC V28 Explained: What Actually Changed from V24</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:56:35 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/hcc-v28-explained-what-actually-changed-from-v24-4g1h</link>
      <guid>https://dev.to/vbc_risk_analytics/hcc-v28-explained-what-actually-changed-from-v24-4g1h</guid>
      <description>&lt;p&gt;If you maintain anything that touches risk adjustment, the CMS-HCC model version is effectively a breaking dependency upgrade — and the industry is mid-migration from V24 to V28. Here's the changelog view.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The HCC map was &lt;strong&gt;reorganized and renumbered&lt;/strong&gt; — V24 HCC numbers do not line up with V28.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;count of payment HCCs changed&lt;/strong&gt; and a number of categories were &lt;strong&gt;constrained or removed&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coefficients were re-estimated&lt;/strong&gt;, so the same member can produce a different RAF under V28 vs V24.&lt;/li&gt;
&lt;li&gt;CMS is &lt;strong&gt;phasing V28 in over multiple payment years&lt;/strong&gt; — both models are live simultaneously during the blend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this breaks in practice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hard-coded HCC numbers.&lt;/strong&gt; Any mapping table keyed on V24 HCC IDs is wrong under V28. Treat the model version as an explicit input through your whole pipeline, not a constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trend comparisons.&lt;/strong&gt; Year-over-year RAF deltas during the phase-in are partly &lt;em&gt;model artifact&lt;/em&gt;, not real population change. If you don't separate "blend shift" from "true shift," your dashboards lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditions that used to pay.&lt;/strong&gt; Some diagnoses that mapped to a paying category under V24 don't under V28. Documentation behavior that was fine before can quietly under-capture now.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The migration checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Load &lt;strong&gt;both&lt;/strong&gt; coefficient sets; compute V24 and V28 in parallel during the blend years.&lt;/li&gt;
&lt;li&gt;Recompute historical members under V28 to get a clean apples-to-apples baseline.&lt;/li&gt;
&lt;li&gt;Flag members whose RAF moves materially between models — those are where documentation and care-gap workflows need attention. If you want a structured look at &lt;a href="https://www.rafscorecalculator.com/compare-raf-score-version-24-vs-version-28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p05" rel="noopener noreferrer"&gt;how V24 and V28 compare&lt;/a&gt; before you build the diff logic, that breakdown is a useful reference.&lt;/li&gt;
&lt;li&gt;Re-verify phase-in percentages and category counts against the &lt;strong&gt;current CMS Rate Announcement&lt;/strong&gt; for the actual payment year — don't trust last year's numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A side-by-side of the category remap and the phase-in schedule, plus a tool that computes a member under both models, is here: &lt;a href="https://www.rafscorecalculator.com/hcc-v28?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rsc-lb-2026&amp;amp;utm_content=p05" rel="noopener noreferrer"&gt;HCC V28&lt;/a&gt;. It's the fastest way to see, concretely, how a real member shifts under the V28 model before you trust your own implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's worth getting right
&lt;/h2&gt;

&lt;p&gt;This isn't a cosmetic version bump. RAF drives Medicare Advantage funding; a quietly wrong model version means systematically wrong risk scores for real patients. Accuracy under the correct model — not score inflation — is the entire point.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not coding, billing, or clinical advice. CMS-HCC specifications change by payment year — confirm against the current CMS Rate Announcement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written by Chin Ramamoorthi&lt;/strong&gt; — CEO, VBC Risk Analytics. He has 20+ years across provider- and payer-side healthcare IT, including leading V24-to-V28 transition work on both the payer and provider side.&lt;br&gt;
&lt;strong&gt;Reviewed by the VBC Risk Analytics Risk Adjustment &amp;amp; Coding Team.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Last updated:&lt;/strong&gt; June 2026&lt;/p&gt;

</description>
      <category>hccv28</category>
      <category>riskadjustment</category>
      <category>medicareadvantage</category>
      <category>healthcareit</category>
    </item>
    <item>
      <title>Building an HCC Gap Analysis Pipeline (a developer's view of risk capture)</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Mon, 08 Jun 2026 08:18:40 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/building-an-hcc-gap-analysis-pipeline-a-developers-view-of-risk-capture-8ak</link>
      <guid>https://dev.to/vbc_risk_analytics/building-an-hcc-gap-analysis-pipeline-a-developers-view-of-risk-capture-8ak</guid>
      <description>&lt;p&gt;If you write software for a Medicare Advantage plan, "HCC gap analysis" eventually lands on your desk as a data problem disguised as a clinical one. The clinical team says "we're leaving risk on the table." What they need from you is a pipeline that finds, ranks, and tracks the gaps. Here's how I think about building one.&lt;br&gt;
﻿&lt;br&gt;
&lt;strong&gt;The mental model&lt;/strong&gt;&lt;br&gt;
﻿&lt;br&gt;
Start with definitions, because the acronyms compound fast:&lt;br&gt;
﻿&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HCC&lt;/strong&gt; — Hierarchical Condition Category. The risk bucket a diagnosis rolls up into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAF&lt;/strong&gt; — Risk Adjustment Factor. The score built from demographics plus HCC coefficients (CMS-HCC V28 is the current model).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "gap"&lt;/strong&gt; — a condition that is clinically supported somewhere in the data but is &lt;em&gt;not&lt;/em&gt; captured as a coded, current-year HCC.
﻿
Gap analysis is fundamentally a set-difference problem: &lt;code&gt;suspected_hccs - documented_hccs&lt;/code&gt;, weighted by the RAF impact of each missing HCC.
﻿
&lt;strong&gt;Step 1: Build the two sets&lt;/strong&gt;
﻿
&lt;strong&gt;Documented HCCs&lt;/strong&gt; come from this year's confirmed claims/encounters, mapped through the current ICD-10 → HCC crosswalk. If you don't want to maintain your own crosswalk, the &lt;code&gt;/getHCCCrosswalk&lt;/code&gt; sibling endpoint resolves ICD-10-CM codes to HCCs under a pinned model.
﻿
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;documented&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;map_icd_to_hcc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS-HCC-V28 Continuing Enrollee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current_year_diagnoses&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_confirmed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;﻿&lt;br&gt;
&lt;strong&gt;Suspected HCCs&lt;/strong&gt; come from weaker signals: prior-year HCCs that didn't recur, relevant labs, medications that imply a condition, and problem-list entries that never made it to a claim.&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;suspected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;suspected&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;prior_year_hccs&lt;/span&gt;            &lt;span class="c1"&gt;# chronic conditions rarely resolve
&lt;/span&gt;&lt;span class="n"&gt;suspected&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="nf"&gt;hccs_from_medications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# e.g., insulin -&amp;gt; diabetes family
&lt;/span&gt;&lt;span class="n"&gt;suspected&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="nf"&gt;hccs_from_labs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# e.g., eGFR -&amp;gt; CKD staging
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
To turn a candidate condition set into a RAF impact, score it through the API. The endpoint is itemized, so you get a per-HCC coefficient back rather than a single opaque number:&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://restapi.npidataservices.com/raf/api/v1/getScore &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"ApiKey: &lt;/span&gt;&lt;span class="nv"&gt;$RAF_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"accept: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "CMS-HCC-V28 Continuing Enrollee",
    "factor": "Community NonDual Aged",
    "age": 66,
    "gender": "MALE",
    "HCC_Codes": ["E119", "C61", "N1832", "I509", "J449"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
Note the auth: a custom &lt;code&gt;ApiKey:&lt;/code&gt; header, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;Authorization: Bearer&lt;/code&gt; (a Bearer header returns &lt;code&gt;401&lt;/code&gt;). ICD-10-CM codes go in &lt;strong&gt;without dots&lt;/strong&gt; (&lt;code&gt;E11.9&lt;/code&gt; -&amp;gt; &lt;code&gt;E119&lt;/code&gt;).&lt;br&gt;
﻿&lt;br&gt;
&lt;strong&gt;Step 2: Compute the gap and weight it&lt;/strong&gt;&lt;br&gt;
﻿&lt;br&gt;
A raw list of missing HCCs is noise. Engineers add value by ranking. The natural weight is the RAF coefficient — how much each closed gap would actually move the score.&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;suspected&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;documented&lt;/span&gt;
&lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hcc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf_delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;coefficient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS-HCC-V28 Continuing Enrollee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raf_delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
Now your clinical team gets a worklist sorted by impact instead of an undifferentiated dump. If you want the conceptual grounding for how those coefficients add up into a member's score, this RAF explainer is a solid reference — you can &lt;a href="https://www.vbcriskanalytics.com/raf-score?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p007" rel="noopener noreferrer"&gt;read more here&lt;/a&gt;.&lt;br&gt;
﻿&lt;br&gt;
&lt;strong&gt;Step 3: Close the loop with provenance&lt;/strong&gt;&lt;br&gt;
﻿&lt;br&gt;
A gap you can't explain is a gap nobody will act on. For every suggested HCC, attach the evidence:&lt;br&gt;
﻿&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"member_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SYNTH-00417"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suspected_hcc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HCC38"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"raf_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"metformin (synthetic)"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prior_hcc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;﻿&lt;br&gt;
Provenance is also what protects you later. A gap closed with documentation behind it survives a RADV (Risk Adjustment Data Validation) audit; a gap "closed" by guessing does not. Build the evidence trail from day one.&lt;/p&gt;

&lt;p&gt;﻿&lt;strong&gt;Step 4: Treat it as a recurring job, not a project&lt;/strong&gt;&lt;br&gt;
﻿&lt;br&gt;
Gaps reopen. Members get new labs, conditions resolve, the model changes. Schedule the pipeline (monthly is common), snapshot the open/closed state, and track closure rate over time as your real KPI.&lt;br&gt;
﻿&lt;br&gt;
&lt;strong&gt;A few engineering gotchas﻿&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use synthetic fixtures.&lt;/strong&gt; Never test against live member data. Generate illustrative members that exercise each HCC family.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin the model version.&lt;/strong&gt; A gap computed under V28 must be reproducible later; don't let the crosswalk float.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency.&lt;/strong&gt; Re-running the pipeline shouldn't duplicate open gaps — key on &lt;code&gt;(member, hcc, year)&lt;/code&gt;.
﻿
&lt;strong&gt;Wrapping up&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HCC gap analysis isn't glamorous, but it's one of the highest-leverage pipelines you can build on the risk-adjustment side: it directly connects documentation quality to a plan's revenue accuracy and audit posture. If you want the broader, less code-heavy treatment of finding and closing these gaps, the full &lt;a href="https://www.vbcriskanalytics.com/blogs/hcc-gap-analysis?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=vbc-web-lb-2026&amp;amp;utm_content=p007" rel="noopener noreferrer"&gt;HCC gap analysis guide&lt;/a&gt; covers the program side that complements the pipeline above.&lt;br&gt;
﻿&lt;br&gt;
&lt;em&gt;VBC Risk Analytics. Educational only — not coding, billing, or clinical advice; verify against the current CMS Rate Announcement. Synthetic data only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vbc</category>
      <category>hcc</category>
      <category>backend</category>
      <category>healthtech</category>
    </item>
  </channel>
</rss>
