<?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>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>
    <item>
      <title>Healthcare Risk Adjustment Tools from VBC Risk Analytics</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Tue, 21 Apr 2026 15:51:40 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/building-healthcare-risk-adjustment-tools-an-introduction-to-vbc-risk-analytics-50n6</link>
      <guid>https://dev.to/vbc_risk_analytics/building-healthcare-risk-adjustment-tools-an-introduction-to-vbc-risk-analytics-50n6</guid>
      <description>&lt;p&gt;Healthcare data engineering is hard. Healthcare &lt;em&gt;risk adjustment&lt;/em&gt; data engineering is a category of hard that deserves its own word.&lt;/p&gt;

&lt;p&gt;If you've ever worked in health IT, you know the stack: EHR extracts that arrive in twelve different formats, ICD-10 codes that map to HCC categories under rules that change every model year, RAF scores that determine how much a health plan gets paid for every enrolled member, and CMS audits that can claw back millions if the documentation doesn't hold up.&lt;/p&gt;

&lt;p&gt;At VBC Risk Analytics, we've spent years building API-first tools in this space. This introductory post covers what risk adjustment actually is, why it's technically interesting, and what we're building — with the hope of connecting with developers, data engineers, and health IT professionals who work in this domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Risk Adjustment and Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;Risk adjustment is the process Medicare uses to ensure that health plans are paid fairly based on the health status of their enrolled members. Sicker members cost more to care for, so a plan that enrolls a predominantly sick population gets higher payments to offset those costs. A plan that enrolls predominantly healthy members gets lower payments.&lt;/p&gt;

&lt;p&gt;The mechanism that drives this is the &lt;strong&gt;Hierarchical Condition Category (HCC)&lt;/strong&gt; model — specifically, the CMS-HCC model maintained by the Centers for Medicare and Medicaid Services. Every Medicare Advantage member gets a &lt;strong&gt;Risk Adjustment Factor (RAF) score&lt;/strong&gt; that reflects their predicted cost relative to the average Medicare beneficiary.&lt;/p&gt;

&lt;p&gt;A RAF score of 1.0 means the member is expected to cost exactly as much as the average. A score of 1.5 means 50% more than average. A score of 0.7 means 30% less.&lt;/p&gt;

&lt;p&gt;The score is built by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Taking the member's demographic data (age, sex, Medicaid eligibility, etc.)&lt;/li&gt;
&lt;li&gt;Mapping their ICD-10 diagnosis codes to HCC categories&lt;/li&gt;
&lt;li&gt;Applying interaction factors for certain combinations of conditions&lt;/li&gt;
&lt;li&gt;Summing the coefficients from the CMS-HCC model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a health plan with 100,000 Medicare Advantage members, even small errors in this calculation — missed diagnoses, mapping mistakes, documentation gaps — compound into significant over- or underpayment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Complexity
&lt;/h2&gt;

&lt;p&gt;This sounds straightforward until you get into the actual implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ICD-10-to-HCC mapping&lt;/strong&gt; is not a simple lookup table. There are approximately 70,000 ICD-10-CM codes and 86 HCC categories in the CMS-HCC V28 model. Not all codes map to HCCs. Some codes map to multiple HCCs. The "hierarchical" part of HCC means that more severe conditions in a disease hierarchy suppress less severe ones — so if a patient has both HCC 18 (Diabetes with chronic complications) and HCC 19 (Diabetes without complication), only HCC 18 counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model coefficients&lt;/strong&gt; change with each model version. CMS transitioned from V24 to V28 over 2024-2026, blending the two models at different percentages each year. Code that was correct for V24 produces wrong answers for V28 if you don't update the coefficient tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The demographic adjusters&lt;/strong&gt; depend on whether a member is community-dwelling or institutionalized, whether they have Medicaid, and whether they're in their initial enrollment period. Getting these wrong affects every member, not just the complex ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RADV audits&lt;/strong&gt; add another layer. The Risk Adjustment Data Validation audit process has CMS selecting a sample of medical records and verifying that each HCC in the RAF score is supported by adequate documentation. Plans that fail RADV audits repay the overpayment — plus potential extrapolation penalties.&lt;/p&gt;

&lt;h2&gt;
  
  
  What VBC Risk Analytics Builds
&lt;/h2&gt;

&lt;p&gt;Our platform at &lt;a href="https://www.vbcriskanalytics.com" rel="noopener noreferrer"&gt;VBC Risk Analytics&lt;/a&gt; addresses the risk adjustment workflow end-to-end. A few things we've built:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAF Score API&lt;/strong&gt;: A REST endpoint that takes a member's demographics and ICD-10 codes and returns a fully calculated RAF score with HCC mapping details, model version, and coefficient breakdown. Handles V24, V28, and the blended transition percentages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ICD-10 Data Lookup API&lt;/strong&gt;: Fast lookup for ICD-10-CM codes — descriptions, HCC mappings, validity flags, hierarchy relationships. Useful for coding workflow tools, eligibility systems, and CDI applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RADV Audit Scrubber&lt;/strong&gt;: Before a RADV audit happens, this tool reviews the documentation supporting each HCC against CMS audit criteria. It flags potential documentation deficiencies so they can be addressed before CMS asks for the medical records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPI Lookup API&lt;/strong&gt;: Provider verification via the National Plan and Provider Enumeration System (NPPES), useful for linking clinical data to provider records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why API-First?
&lt;/h2&gt;

&lt;p&gt;Most risk adjustment software is built as monolithic platforms — you buy the whole system or you buy nothing. We took an API-first approach because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Most health plans and provider groups already have analytics infrastructure. They need specific capabilities, not replacement systems.&lt;/li&gt;
&lt;li&gt;Health IT developers building EHR integrations, population health tools, and care management platforms need programmatic access to risk adjustment data.&lt;/li&gt;
&lt;li&gt;APIs are testable, versionable, and composable in ways that dashboard-only tools aren't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building something in the health IT space and you need RAF scoring, ICD-10 lookup, or provider verification, check out the &lt;a href="https://www.vbcriskanalytics.com/healthcare-apis" rel="noopener noreferrer"&gt;healthcare APIs at VBC Risk Analytics&lt;/a&gt;. We have documentation and sandbox access available.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In future posts on Dev.to, I'll be going deeper on specific technical topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ICD-10-to-HCC mapping algorithm — how it actually works under the hood&lt;/li&gt;
&lt;li&gt;CMS-HCC V28 changes and what broke in existing implementations&lt;/li&gt;
&lt;li&gt;RADV audit data modeling — structuring your documentation review pipeline&lt;/li&gt;
&lt;li&gt;Building a risk stratification system from claims data&lt;/li&gt;
&lt;li&gt;NPI verification edge cases and the mess that is provider data quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work in health IT or healthcare data engineering, I'd love to connect. Drop a comment below or reach out through &lt;a href="https://www.vbcriskanalytics.com" rel="noopener noreferrer"&gt;VBC Risk Analytics&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>healthit</category>
      <category>healthcare</category>
      <category>dataengineering</category>
      <category>api</category>
    </item>
    <item>
      <title>Building Healthcare Risk Adjustment Tools: An Introduction to VBC Risk Analytics</title>
      <dc:creator>VBC Risk Analytics</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:32:48 +0000</pubDate>
      <link>https://dev.to/vbc_risk_analytics/building-healthcare-risk-adjustment-tools-an-introduction-to-vbc-risk-analytics-2n41</link>
      <guid>https://dev.to/vbc_risk_analytics/building-healthcare-risk-adjustment-tools-an-introduction-to-vbc-risk-analytics-2n41</guid>
      <description>&lt;p&gt;Healthcare data engineering is hard. Healthcare &lt;em&gt;risk adjustment&lt;/em&gt; data engineering is a category of hard that deserves its own word.&lt;/p&gt;

&lt;p&gt;If you've ever worked in health IT, you know the stack: EHR extracts that arrive in twelve different formats, ICD-10 codes that map to HCC categories under rules that change every model year, RAF scores that determine how much a health plan gets paid for every enrolled member, and CMS audits that can claw back millions if the documentation doesn't hold up.&lt;/p&gt;

&lt;p&gt;At VBC Risk Analytics, we've spent years building API-first tools in this space. This introductory post covers what risk adjustment actually is, why it's technically interesting, and what we're building — with the hope of connecting with developers, data engineers, and health IT professionals who work in this domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Risk Adjustment and Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;Risk adjustment is the process Medicare uses to ensure that health plans are paid fairly based on the health status of their enrolled members. Sicker members cost more to care for, so a plan that enrolls a predominantly sick population gets higher payments to offset those costs. A plan that enrolls predominantly healthy members gets lower payments.&lt;/p&gt;

&lt;p&gt;The mechanism that drives this is the &lt;strong&gt;Hierarchical Condition Category (HCC)&lt;/strong&gt; model — specifically, the CMS-HCC model maintained by the Centers for Medicare and Medicaid Services. Every Medicare Advantage member gets a &lt;strong&gt;Risk Adjustment Factor (RAF) score&lt;/strong&gt; that reflects their predicted cost relative to the average Medicare beneficiary.&lt;/p&gt;

&lt;p&gt;A RAF score of 1.0 means the member is expected to cost exactly as much as the average. A score of 1.5 means 50% more than average. A score of 0.7 means 30% less.&lt;/p&gt;

&lt;p&gt;The score is built by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Taking the member's demographic data (age, sex, Medicaid eligibility, etc.)&lt;/li&gt;
&lt;li&gt;Mapping their ICD-10 diagnosis codes to HCC categories&lt;/li&gt;
&lt;li&gt;Applying interaction factors for certain combinations of conditions&lt;/li&gt;
&lt;li&gt;Summing the coefficients from the CMS-HCC model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a health plan with 100,000 Medicare Advantage members, even small errors in this calculation — missed diagnoses, mapping mistakes, documentation gaps — compound into significant over- or underpayment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Complexity
&lt;/h2&gt;

&lt;p&gt;This sounds straightforward until you get into the actual implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ICD-10-to-HCC mapping&lt;/strong&gt; is not a simple lookup table. There are approximately 70,000 ICD-10-CM codes and 86 HCC categories in the CMS-HCC V28 model. Not all codes map to HCCs. Some codes map to multiple HCCs. The "hierarchical" part of HCC means that more severe conditions in a disease hierarchy suppress less severe ones — so if a patient has both HCC 18 (Diabetes with chronic complications) and HCC 19 (Diabetes without complication), only HCC 18 counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model coefficients&lt;/strong&gt; change with each model version. CMS transitioned from V24 to V28 over 2024-2026, blending the two models at different percentages each year. Code that was correct for V24 produces wrong answers for V28 if you don't update the coefficient tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The demographic adjusters&lt;/strong&gt; depend on whether a member is community-dwelling or institutionalized, whether they have Medicaid, and whether they're in their initial enrollment period. Getting these wrong affects every member, not just the complex ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RADV audits&lt;/strong&gt; add another layer. The Risk Adjustment Data Validation audit process has CMS selecting a sample of medical records and verifying that each HCC in the RAF score is supported by adequate documentation. Plans that fail RADV audits repay the overpayment — plus potential extrapolation penalties.&lt;/p&gt;

&lt;h2&gt;
  
  
  What VBC Risk Analytics Builds
&lt;/h2&gt;

&lt;p&gt;Our platform at &lt;a href="https://www.vbcriskanalytics.com" rel="noopener noreferrer"&gt;VBC Risk Analytics&lt;/a&gt; addresses the risk adjustment workflow end-to-end. A few things we've built:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAF Score API&lt;/strong&gt;: A REST endpoint that takes a member's demographics and ICD-10 codes and returns a fully calculated RAF score with HCC mapping details, model version, and coefficient breakdown. Handles V24, V28, and the blended transition percentages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ICD-10 Data Lookup API&lt;/strong&gt;: Fast lookup for ICD-10-CM codes — descriptions, HCC mappings, validity flags, hierarchy relationships. Useful for coding workflow tools, eligibility systems, and CDI applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RADV Audit Scrubber&lt;/strong&gt;: Before a RADV audit happens, this tool reviews the documentation supporting each HCC against CMS audit criteria. It flags potential documentation deficiencies so they can be addressed before CMS asks for the medical records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPI Lookup API&lt;/strong&gt;: Provider verification via the National Plan and Provider Enumeration System (NPPES), useful for linking clinical data to provider records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why API-First?
&lt;/h2&gt;

&lt;p&gt;Most risk adjustment software is built as monolithic platforms — you buy the whole system or you buy nothing. We took an API-first approach because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Most health plans and provider groups already have analytics infrastructure. They need specific capabilities, not replacement systems.&lt;/li&gt;
&lt;li&gt;Health IT developers building EHR integrations, population health tools, and care management platforms need programmatic access to risk adjustment data.&lt;/li&gt;
&lt;li&gt;APIs are testable, versionable, and composable in ways that dashboard-only tools aren't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building something in the health IT space and you need RAF scoring, ICD-10 lookup, or provider verification, check out the &lt;a href="https://www.vbcriskanalytics.com/healthcare-apis" rel="noopener noreferrer"&gt;healthcare APIs at VBC Risk Analytics&lt;/a&gt;. We have documentation and sandbox access available.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In future posts on Dev.to, I'll be going deeper on specific technical topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ICD-10-to-HCC mapping algorithm — how it actually works under the hood&lt;/li&gt;
&lt;li&gt;CMS-HCC V28 changes and what broke in existing implementations&lt;/li&gt;
&lt;li&gt;RADV audit data modeling — structuring your documentation review pipeline&lt;/li&gt;
&lt;li&gt;Building a risk stratification system from claims data&lt;/li&gt;
&lt;li&gt;NPI verification edge cases and the mess that is provider data quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work in health IT or healthcare data engineering, I'd love to connect. Drop a comment below or reach out through &lt;a href="https://www.vbcriskanalytics.com" rel="noopener noreferrer"&gt;VBC Risk Analytics&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>healthit</category>
      <category>healthcare</category>
      <category>api</category>
      <category>python</category>
    </item>
  </channel>
</rss>
