SDK Guide

Suspecting SDK

One SQL statement that turns pharmacy claims into a ranked list of suspected risk-adjustment conditions per member. It runs in your warehouse against your claims and five TwinFyRx tables — no model to host, and no member data leaves your environment.

Concepts

Overview

For every drug, drug_hcc_suspect lists the CMS-HCC and RxHCC categories it points at, the share of the drug's use behind each, and a confidence tier. The SDK joins that to a member's fills and to the diagnoses already on their medical claims.

gapStatus

A drug pointing at the category was filled, and no diagnosis in the category is on the member's claims this year or last.

recaptureStatus

A diagnosis in the category was coded last year and not yet this year — with or without a drug behind it.

documentedStatus

Already coded this year, in this category or a more severe one of the same CMS hierarchy. Returned so you can reconcile, excluded from the open worklist.

A suspect is a prompt for clinical review, not a diagnosis. The SDK orders the worklist; a clinician or chart review confirms.

Setup

Installation

The SDK is delivered during onboarding as an installable Python package, twinfyrx_sdk: one module, a schema mapping template, a demo and a synthetic claims set. It generates SQL, has no dependencies of its own, and fetches nothing at install or run time.

pip install ./sdk                 # from the delivered folder
pip install "./sdk[demo]"         # adds duckdb and pandas for the demo

from twinfyrx_sdk import coverage_sql, suspects_sql, evidence_sql, SuspectSchema

# or print the SQL and skip Python altogether
python -m twinfyrx_sdk --year 2026 > suspects.sql

Compatibility: Python 3.9+. The demo and find_suspects() also need duckdb and pandas. One statement serves every warehouse: joins, aggregates, LIKE, one window function and DATE literals, with no dialect-specific functions. Tested on DuckDB and PostgreSQL.

First run

Try It on Synthetic Claims

The SDK ships with 300 synthetic members on real drugs and real NDCs, already joined to the TwinFyRx tables they need, so the first run takes no setup.

pip install "./sdk[demo]"
python -m twinfyrx_sdk.demo
python -m twinfyrx_sdk.demo --model rxhcc_v08 --min-tier unrated --top 20

The demo prints the input check, open suspects by status and tier, and the top of the worklist with the drugs behind each suspect. The synthetic members show the mechanics — statuses, tiers, ordering — not accuracy. The drugs, NDCs, categories and tiers they join to are the published tables.

On the free sample: the sample download holds all five tables, so the SDK runs on it against your own claims — for the sample's 50 drugs.

Configuration

Schema Mapping

The scan reads two of your tables and five of ours. Map yours with views, or pass your table and column names through SuspectSchema.

TableFrom
rx_claimsYou
medical_claimsYou
ndc_crosswalkTwinFyRx
drug_hcc_suspectTwinFyRx
icd10TwinFyRx
hcc_categoryTwinFyRx
hcc_hierarchyTwinFyRx
from twinfyrx_sdk import SuspectSchema

schema = SuspectSchema(
    rx_claims_table="pharmacy.fills",
    rx_member_id="pat_id",
    rx_ndc="ndc11",
    rx_fill_date="date_of_service",

    medical_claims_table="medical.claim_dx",
    mc_member_id="pat_id",
    mc_icd10_code="dx_code",        # dotted or dotless, either works
    mc_service_date="service_date",
)

Use views when a column needs cleaning. SuspectSchema only renames. NDCs must be 11 digits with no dashes and leading zeros kept; diagnoses must be one per row. The mapping template in twinfyrx_sdk/sql/ has the fixes for dashed or numeric NDCs and for diagnoses spread across columns.

Verification

Input Check

Run coverage_sql() before the scan. A wrong NDC or member-id format does not raise an error — it returns a short, plausible-looking list. Eight counts show whether your claims join.

print(coverage_sql(year=2026, schema=schema))

step  check_name                                                  row_count
   1  pharmacy fills in 2026                                           3785
   2  - with an NDC found in ndc_crosswalk                             3785
   3  - for a drug with a high or moderate cms_hcc_v28 category        1416
   4  members with a fill in 2026                                       300
   5  - who also appear in medical_claims                               300
   6  diagnosis rows in 2025-2026                                      1100
   7  - with a code found in icd10                                     1100
   8  - in a cms_hcc_v28 category                                       492

Step 2 near zero is an NDC format problem, not a coverage problem. Step 5 low means member ids differ between your pharmacy and medical tables, and every suspect will come back as a gap. Steps 3 and 8 are normally a minority: most drugs are used for several conditions, and most diagnoses carry no risk-adjustment category.

Execution

Running the Scan

suspects_sql() returns the statement for a measurement year. Run it wherever your claims live, or use find_suspects() to execute it on a DuckDB connection and get a DataFrame.

from twinfyrx_sdk import suspects_sql, find_suspects

# The SQL, to run in your warehouse
sql = suspects_sql(
    year=2026,                    # calendar year of the service dates
    model_key="cms_hcc_v28",      # or cms_hcc_v24, rxhcc_v08
    min_tier="moderate",          # high and moderate suspects only
    schema=schema,
)

# Or execute directly on DuckDB
worklist = find_suspects(conn, year=2026, schema=schema)   # open suspects only

Fills count in the measurement year; diagnoses are read for that year and the one before. For Medicare Advantage, the measurement year is the year before the payment year. The statement has six parts: the member's drugs in the year, what each drug points at, a per-category roll-up across the member's drugs, the categories already coded this year and last, CMS's hierarchies, and the status for each.

Hierarchies are applied. Risk adjustment counts only the most severe category of a hierarchy. A category is not listed as a gap when a more severe one is on the member's claims this year; when the more severe one was coded last year only, that category is the recapture and the less severe one is left off. A drug pointing at a more severe category than the one on the claims still lists as a gap. Pass SuspectSchema(hcc_hierarchy_table=None) to see the list without them.

Output

Output

One row per member and condition category.

ColumnTypeDescription
member_idVARCHARMember identifier from your claims
ccVARCHARCondition category in the chosen risk model
labelVARCHARCMS's name for the category
statusVARCHARgap, recapture, or documented
tierVARCHARStrongest confidence tier among the member's drugs for the category; null for a recapture with no drug behind it
scoreDOUBLEOrders the worklist within a tier. Each of the member's drugs adds −log10(1 − share): 0.5 at a 70% share, 1.0 at 90%, 2.0 at 99%. No upper bound; not a probability
supporting_drugsINTEGERHow many of the member's drugs point at the category

evidence_sql() returns the reason for each row — the drugs behind it, one row per member, category and drug. Join it to the worklist on member_id and cc.

ColumnDescription
drug_id, drugThe drug filled, as a clinical drug form ("furosemide Oral Tablet")
pct_volumeShare of the drug's use that falls in the category
confidence_tierThe tier of this drug for the category
fill_count, first_fill, last_fillThe member's fills of the drug in the measurement year
contributing_icd10_codesThe diagnoses that connect the drug to the category — where a chart review should look
Evidence

Confidence Tiers

The tier is the claim to act on; the share is the estimate behind it. Tiers are cut where the shares hold up against AHRQ MEPS prescription–diagnosis data.

high

share ≥ 90%

In MEPS, the fill was for a condition in the category about 85% of the time.

moderate

share 70–90%

In MEPS, about two-thirds of the time.

unrated

share < 70%

A known use of the drug, but not a suspect on a fill alone. Excluded by default.

What that check covers: commonly dispensed retail drugs, at condition level — MEPS records the condition a prescription was for, not the billable code. Most oncology, HIV and other specialty drugs have too little MEPS volume to check.

Scope

What the SDK Does Not Do

EnrollmentIt does not know who is still a member. Join the worklist to your eligibility table.
Encounter rulesIt counts every diagnosis row you give it. Apply CMS's eligible-encounter rules in the mapping view if you need them.
DollarsIt ranks suspects. It does not compute risk scores or revenue.
Deployment

Integration Workflow

01

Load the TwinFyRx tables

ndc_crosswalk, drug_hcc_suspect, icd10, hcc_category and hcc_hierarchy — delivered as Parquet, DuckDB or via the API, on your refresh cadence.

02

Map your claims

Two views or one SuspectSchema: pharmacy fills and medical claim diagnoses. A one-time setup. Run the input check before going further.

03

Run the scan

Generate the statement for your measurement year and risk model, and run it in your warehouse.

04

Work the list

Order by tier, then score. High-tier gaps first, then moderate, then recaptures. Route to clinical review or chart retrieval with the evidence rows attached.

05

Measure confirmation

Track how often each tier confirms on your own population. That number, not ours, is what your program should run on.

Ready to integrate?

The SDK is included with your API access. Request access to receive the package, your API key, and onboarding support.