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.
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.
A drug pointing at the category was filled, and no diagnosis in the category is on the member's claims this year or last.
A diagnosis in the category was coded last year and not yet this year — with or without a drug behind it.
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.
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.
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.
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.
| Table | From |
|---|---|
| rx_claims | You |
| medical_claims | You |
| ndc_crosswalk | TwinFyRx |
| drug_hcc_suspect | TwinFyRx |
| icd10 | TwinFyRx |
| hcc_category | TwinFyRx |
| hcc_hierarchy | TwinFyRx |
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.
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.
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 onlyFills 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
One row per member and condition category.
| Column | Type | Description |
|---|---|---|
| member_id | VARCHAR | Member identifier from your claims |
| cc | VARCHAR | Condition category in the chosen risk model |
| label | VARCHAR | CMS's name for the category |
| status | VARCHAR | gap, recapture, or documented |
| tier | VARCHAR | Strongest confidence tier among the member's drugs for the category; null for a recapture with no drug behind it |
| score | DOUBLE | Orders 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_drugs | INTEGER | How 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.
| Column | Description |
|---|---|
| drug_id, drug | The drug filled, as a clinical drug form ("furosemide Oral Tablet") |
| pct_volume | Share of the drug's use that falls in the category |
| confidence_tier | The tier of this drug for the category |
| fill_count, first_fill, last_fill | The member's fills of the drug in the measurement year |
| contributing_icd10_codes | The diagnoses that connect the drug to the category — where a chart review should look |
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.
highshare ≥ 90%
In MEPS, the fill was for a condition in the category about 85% of the time.
moderateshare 70–90%
In MEPS, about two-thirds of the time.
unratedshare < 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.
What the SDK Does Not Do
Integration Workflow
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.
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.
Run the scan
Generate the statement for your measurement year and risk model, and run it in your warehouse.
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.
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.