hccinfhir 0.1.8__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hccinfhir-0.2.0/PKG-INFO +946 -0
- hccinfhir-0.2.0/README.md +932 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/__init__.py +2 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/datamodels.py +102 -9
- hccinfhir-0.2.0/hccinfhir/defaults.py +31 -0
- hccinfhir-0.2.0/hccinfhir/extractor_834.py +530 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/hccinfhir.py +28 -13
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/model_calculate.py +29 -25
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/model_coefficients.py +2 -29
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/model_dx_to_cc.py +6 -11
- hccinfhir-0.2.0/hccinfhir/model_hierarchies.py +45 -0
- hccinfhir-0.2.0/hccinfhir/sample_files/sample_834_01.txt +1 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/samples.py +50 -5
- hccinfhir-0.2.0/hccinfhir/utils.py +247 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/pyproject.toml +1 -1
- hccinfhir-0.1.8/PKG-INFO +0 -782
- hccinfhir-0.1.8/README.md +0 -768
- hccinfhir-0.1.8/hccinfhir/model_hierarchies.py +0 -74
- hccinfhir-0.1.8/hccinfhir/utils.py +0 -74
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/.gitignore +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/LICENSE +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/__init__.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/hcc_is_chronic.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/hcc_is_chronic_without_esrd_model.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_coefficients_2025.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_coefficients_2026.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_dx_to_cc_2025.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_dx_to_cc_2026.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_eligible_cpt_hcpcs_2023.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_eligible_cpt_hcpcs_2024.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_eligible_cpt_hcpcs_2025.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_eligible_cpt_hcpcs_2026.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_hierarchies_2025.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/data/ra_hierarchies_2026.csv +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/extractor.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/extractor_837.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/extractor_fhir.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/filter.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/model_demographics.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/model_interactions.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/__init__.py +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_0.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_1.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_10.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_11.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_12.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_2.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_3.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_4.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_5.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_6.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_7.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_8.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_837_9.txt +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_eob_1.json +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_eob_2.json +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_eob_200.ndjson +0 -0
- {hccinfhir-0.1.8 → hccinfhir-0.2.0}/hccinfhir/sample_files/sample_eob_3.json +0 -0
hccinfhir-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: hccinfhir
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: HCC Algorithm for FHIR Resources
|
|
5
|
+
Project-URL: Homepage, https://github.com/mimilabs/hccinfhir
|
|
6
|
+
Project-URL: Issues, https://github.com/mimilabs/hccinfhir/issues
|
|
7
|
+
Author-email: Yubin Park <yubin.park@mimilabs.ai>
|
|
8
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Requires-Dist: pydantic>=2.10.3
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# HCCInFHIR
|
|
16
|
+
|
|
17
|
+
[](https://badge.fury.io/py/hccinfhir)
|
|
18
|
+
[](https://www.python.org/downloads/)
|
|
19
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
20
|
+
|
|
21
|
+
A comprehensive Python library for calculating HCC (Hierarchical Condition Category) risk adjustment scores from healthcare claims data. Supports multiple data sources including FHIR resources, X12 837 claims, X12 834 enrollment files, and direct diagnosis processing.
|
|
22
|
+
|
|
23
|
+
## 🚀 Quick Start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install hccinfhir
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
31
|
+
|
|
32
|
+
# Initialize processor
|
|
33
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
34
|
+
|
|
35
|
+
# Calculate from diagnosis codes
|
|
36
|
+
demographics = Demographics(age=67, sex="F")
|
|
37
|
+
diagnosis_codes = ["E11.9", "I10", "N18.3"]
|
|
38
|
+
|
|
39
|
+
result = processor.calculate_from_diagnosis(diagnosis_codes, demographics)
|
|
40
|
+
print(f"Risk Score: {result.risk_score}")
|
|
41
|
+
print(f"HCCs: {result.hcc_list}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 📋 Table of Contents
|
|
45
|
+
|
|
46
|
+
- [Key Features](#key-features)
|
|
47
|
+
- [Data Sources & Use Cases](#data-sources--use-cases)
|
|
48
|
+
- [Installation](#installation)
|
|
49
|
+
- [How-To Guides](#how-to-guides)
|
|
50
|
+
- [Working with CMS Encounter Data (837 Claims)](#working-with-cms-encounter-data-837-claims)
|
|
51
|
+
- [Processing X12 834 Enrollment for Dual Eligibility](#processing-x12-834-enrollment-for-dual-eligibility)
|
|
52
|
+
- [Processing Clearinghouse 837 Claims](#processing-clearinghouse-837-claims)
|
|
53
|
+
- [Using CMS BCDA API Data](#using-cms-bcda-api-data)
|
|
54
|
+
- [Direct Diagnosis Code Processing](#direct-diagnosis-code-processing)
|
|
55
|
+
- [Configuration](#configuration)
|
|
56
|
+
- [Supported HCC Models](#supported-hcc-models)
|
|
57
|
+
- [Custom Data Files](#custom-data-files)
|
|
58
|
+
- [Demographics Configuration](#demographics-configuration)
|
|
59
|
+
- [API Reference](#api-reference)
|
|
60
|
+
- [Advanced Features](#advanced-features)
|
|
61
|
+
- [Payment RAF Adjustments](#payment-raf-adjustments)
|
|
62
|
+
- [Demographic Prefix Override](#demographic-prefix-override)
|
|
63
|
+
- [Custom File Path Resolution](#custom-file-path-resolution)
|
|
64
|
+
- [Batch Processing](#batch-processing)
|
|
65
|
+
- [Converting to Dictionaries](#converting-to-dictionaries)
|
|
66
|
+
- [Sample Data](#sample-data)
|
|
67
|
+
- [Testing](#testing)
|
|
68
|
+
- [License](#license)
|
|
69
|
+
|
|
70
|
+
## ✨ Key Features
|
|
71
|
+
|
|
72
|
+
- **Multiple Input Formats**: FHIR EOB, X12 837, X12 834, direct diagnosis codes
|
|
73
|
+
- **Comprehensive HCC Models**: Support for CMS-HCC V22/V24/V28, ESRD models, RxHCC
|
|
74
|
+
- **Dual Eligibility Detection**: X12 834 parser with California DHCS Medi-Cal support
|
|
75
|
+
- **CMS Compliance**: Built-in filtering rules for eligible services
|
|
76
|
+
- **Payment RAF Adjustments**: MACI, normalization factors, frailty scores
|
|
77
|
+
- **Data Quality Workarounds**: Demographic prefix override for incorrect source data
|
|
78
|
+
- **Custom Data Files**: Full support for custom coefficients, mappings, and hierarchies
|
|
79
|
+
- **Flexible File Resolution**: Absolute paths, relative paths, or bundled data files
|
|
80
|
+
- **Type-Safe**: Built on Pydantic with full type hints
|
|
81
|
+
- **Well-Tested**: 155 comprehensive tests covering all features
|
|
82
|
+
|
|
83
|
+
## 📊 Data Sources & Use Cases
|
|
84
|
+
|
|
85
|
+
### 1. **X12 837 Claims (Professional & Institutional)**
|
|
86
|
+
- **Input**: X12 837 5010 transaction files + demographics
|
|
87
|
+
- **Use Case**: Medicare Advantage encounter data, health plan claims processing
|
|
88
|
+
- **Features**: Service-level extraction, CMS filtering, diagnosis pointer resolution
|
|
89
|
+
- **Output**: Risk scores with detailed HCC mappings and interactions
|
|
90
|
+
|
|
91
|
+
### 2. **X12 834 Enrollment Files**
|
|
92
|
+
- **Input**: X12 834 benefit enrollment transactions
|
|
93
|
+
- **Use Case**: Extract dual eligibility status, detect Medicaid coverage loss
|
|
94
|
+
- **Features**: California DHCS aid code mapping, Medicare status codes, coverage tracking
|
|
95
|
+
- **Output**: Demographics with accurate dual eligibility for risk calculations
|
|
96
|
+
|
|
97
|
+
### 3. **FHIR ExplanationOfBenefit Resources**
|
|
98
|
+
- **Input**: FHIR EOB from CMS Blue Button 2.0 / BCDA API
|
|
99
|
+
- **Use Case**: Applications processing Medicare beneficiary data
|
|
100
|
+
- **Features**: FHIR-native extraction, standardized data model
|
|
101
|
+
- **Output**: Service-level analysis with risk adjustment calculations
|
|
102
|
+
|
|
103
|
+
### 4. **Direct Diagnosis Codes**
|
|
104
|
+
- **Input**: ICD-10 diagnosis codes + demographics
|
|
105
|
+
- **Use Case**: Quick validation, research, prospective risk scoring
|
|
106
|
+
- **Features**: No claims data needed, fast calculation
|
|
107
|
+
- **Output**: HCC mappings and risk scores
|
|
108
|
+
|
|
109
|
+
## 🛠️ Installation
|
|
110
|
+
|
|
111
|
+
### Basic Installation
|
|
112
|
+
```bash
|
|
113
|
+
pip install hccinfhir
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Development Installation
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/yourusername/hccinfhir.git
|
|
119
|
+
cd hccinfhir
|
|
120
|
+
pip install -e .
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Requirements
|
|
124
|
+
- Python 3.9+
|
|
125
|
+
- Pydantic >= 2.10.3
|
|
126
|
+
|
|
127
|
+
## 📖 How-To Guides
|
|
128
|
+
|
|
129
|
+
### Working with CMS Encounter Data (837 Claims)
|
|
130
|
+
|
|
131
|
+
**Scenario**: You're a Medicare Advantage plan processing encounter data for CMS risk adjustment submissions.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
135
|
+
from hccinfhir.extractor import extract_sld
|
|
136
|
+
|
|
137
|
+
# Step 1: Configure processor
|
|
138
|
+
# All data file parameters are optional and default to the latest 2026 valuesets
|
|
139
|
+
processor = HCCInFHIR(
|
|
140
|
+
model_name="CMS-HCC Model V28",
|
|
141
|
+
filter_claims=True, # Apply CMS filtering rules
|
|
142
|
+
|
|
143
|
+
# Optional: Override with custom data files (omit to use bundled 2026 defaults)
|
|
144
|
+
# proc_filtering_filename="ra_eligible_cpt_hcpcs_2026.csv", # CPT/HCPCS codes
|
|
145
|
+
# dx_cc_mapping_filename="ra_dx_to_cc_2026.csv", # ICD-10 to HCC
|
|
146
|
+
# hierarchies_filename="ra_hierarchies_2026.csv", # HCC hierarchies
|
|
147
|
+
# is_chronic_filename="hcc_is_chronic.csv", # Chronic flags
|
|
148
|
+
# coefficients_filename="ra_coefficients_2026.csv" # RAF coefficients
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Step 2: Load 837 data
|
|
152
|
+
with open("encounter_data.txt", "r") as f:
|
|
153
|
+
raw_837_data = f.read()
|
|
154
|
+
|
|
155
|
+
# Step 3: Extract service-level data
|
|
156
|
+
service_data = extract_sld(raw_837_data, format="837")
|
|
157
|
+
|
|
158
|
+
# Step 4: Define beneficiary demographics
|
|
159
|
+
demographics = Demographics(
|
|
160
|
+
age=72,
|
|
161
|
+
sex="M",
|
|
162
|
+
dual_elgbl_cd="00", # Non-dual eligible
|
|
163
|
+
orec="0", # Original reason for entitlement
|
|
164
|
+
crec="0", # Current reason for entitlement
|
|
165
|
+
orig_disabled=False,
|
|
166
|
+
new_enrollee=False,
|
|
167
|
+
esrd=False
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Step 5: Calculate risk score
|
|
171
|
+
result = processor.run_from_service_data(service_data, demographics)
|
|
172
|
+
|
|
173
|
+
# Step 6: Review results
|
|
174
|
+
print(f"Risk Score: {result.risk_score:.3f}")
|
|
175
|
+
print(f"Active HCCs: {result.hcc_list}")
|
|
176
|
+
print(f"Disease Interactions: {result.interactions}")
|
|
177
|
+
print(f"Diagnosis Mappings:")
|
|
178
|
+
for cc, dx_codes in result.cc_to_dx.items():
|
|
179
|
+
print(f" HCC {cc}: {', '.join(dx_codes)}")
|
|
180
|
+
|
|
181
|
+
# Export for CMS submission
|
|
182
|
+
encounter_summary = {
|
|
183
|
+
"beneficiary_id": "12345",
|
|
184
|
+
"risk_score": result.risk_score,
|
|
185
|
+
"hcc_list": result.hcc_list,
|
|
186
|
+
"model": "V28",
|
|
187
|
+
"payment_year": 2026
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Processing X12 834 Enrollment for Dual Eligibility
|
|
192
|
+
|
|
193
|
+
**Scenario**: You need to extract dual eligibility status from enrollment files to ensure accurate risk scores. This is critical because dual-eligible beneficiaries can receive **30-50% higher RAF scores** due to different coefficient prefixes.
|
|
194
|
+
|
|
195
|
+
**Why This Matters**:
|
|
196
|
+
- Full Benefit Dual (QMB Plus, SLMB Plus): Uses `CFA_` prefix → ~50% higher RAF
|
|
197
|
+
- Partial Benefit Dual (QMB Only, SLMB Only, QI): Uses `CPA_` prefix → ~30% higher RAF
|
|
198
|
+
- Non-Dual: Uses `CNA_` prefix → baseline RAF
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
202
|
+
from hccinfhir.extractor_834 import (
|
|
203
|
+
extract_enrollment_834,
|
|
204
|
+
enrollment_to_demographics,
|
|
205
|
+
is_losing_medicaid,
|
|
206
|
+
medicaid_status_summary
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Step 1: Parse X12 834 enrollment file
|
|
210
|
+
with open("enrollment_834.txt", "r") as f:
|
|
211
|
+
x12_834_data = f.read()
|
|
212
|
+
|
|
213
|
+
enrollments = extract_enrollment_834(x12_834_data)
|
|
214
|
+
|
|
215
|
+
# Step 2: Process each member
|
|
216
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
217
|
+
|
|
218
|
+
for enrollment in enrollments:
|
|
219
|
+
# Convert enrollment to Demographics for RAF calculation
|
|
220
|
+
demographics = enrollment_to_demographics(enrollment)
|
|
221
|
+
|
|
222
|
+
print(f"\\n=== Member: {enrollment.member_id} ===")
|
|
223
|
+
print(f"MBI: {enrollment.mbi}")
|
|
224
|
+
print(f"Medicaid ID: {enrollment.medicaid_id}")
|
|
225
|
+
print(f"Dual Status: {enrollment.dual_elgbl_cd}")
|
|
226
|
+
print(f"Full Benefit Dual: {enrollment.is_full_benefit_dual}")
|
|
227
|
+
print(f"Partial Benefit Dual: {enrollment.is_partial_benefit_dual}")
|
|
228
|
+
|
|
229
|
+
# Step 3: Check for Medicaid coverage loss (critical for RAF projections)
|
|
230
|
+
if is_losing_medicaid(enrollment, within_days=90):
|
|
231
|
+
print(f"⚠️ ALERT: Member losing Medicaid coverage!")
|
|
232
|
+
print(f" Coverage ends: {enrollment.coverage_end_date}")
|
|
233
|
+
print(f" Expected RAF impact: -30% to -50%")
|
|
234
|
+
|
|
235
|
+
# Step 4: Get comprehensive Medicaid status
|
|
236
|
+
status = medicaid_status_summary(enrollment)
|
|
237
|
+
print(f"\\nMedicaid Status Summary:")
|
|
238
|
+
print(f" Has Medicare: {status['has_medicare']}")
|
|
239
|
+
print(f" Has Medicaid: {status['has_medicaid']}")
|
|
240
|
+
print(f" Dual Status Code: {status['dual_status']}")
|
|
241
|
+
print(f" Full Benefit Dual: {status['is_full_benefit_dual']}")
|
|
242
|
+
print(f" Partial Benefit Dual: {status['is_partial_benefit_dual']}")
|
|
243
|
+
print(f" Coverage End: {status['coverage_end_date']}")
|
|
244
|
+
|
|
245
|
+
# Step 5: Calculate RAF with accurate dual status
|
|
246
|
+
diagnosis_codes = ["E11.9", "I10", "N18.3"] # From claims
|
|
247
|
+
result = processor.calculate_from_diagnosis(diagnosis_codes, demographics)
|
|
248
|
+
print(f"\\nRAF Score: {result.risk_score:.3f}")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**California DHCS Medi-Cal Aid Codes** (automatically mapped):
|
|
252
|
+
```python
|
|
253
|
+
# Full Benefit Dual Aid Codes → dual_elgbl_cd='02' or '04'
|
|
254
|
+
'4N', '4P' # QMB Plus
|
|
255
|
+
'5B', '5D' # SLMB Plus
|
|
256
|
+
|
|
257
|
+
# Partial Benefit Dual Aid Codes → dual_elgbl_cd='01', '03', or '06'
|
|
258
|
+
'4M', '4O' # QMB Only
|
|
259
|
+
'5A', '5C' # SLMB Only
|
|
260
|
+
'5E', '5F' # QI (Qualifying Individual)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Medicare Status Codes** (REF*ABB segment):
|
|
264
|
+
```python
|
|
265
|
+
'QMBPLUS', 'QMB+' → '02' (Full Benefit)
|
|
266
|
+
'SLMBPLUS', 'SLMB+' → '04' (Full Benefit)
|
|
267
|
+
'QMBONLY', 'QMB' → '01' (Partial Benefit)
|
|
268
|
+
'SLMBONLY', 'SLMB' → '03' (Partial Benefit)
|
|
269
|
+
'QI', 'QI1' → '06' (Partial Benefit)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Processing Clearinghouse 837 Claims
|
|
273
|
+
|
|
274
|
+
**Scenario**: Health plan receiving 837 files from clearinghouses for member risk scoring.
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
278
|
+
from hccinfhir.extractor import extract_sld_list
|
|
279
|
+
|
|
280
|
+
# Configure processor
|
|
281
|
+
processor = HCCInFHIR(
|
|
282
|
+
model_name="CMS-HCC Model V28",
|
|
283
|
+
filter_claims=True
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Process multiple 837 files
|
|
287
|
+
claim_files = ["inst_claims.txt", "prof_claims.txt"]
|
|
288
|
+
all_service_data = []
|
|
289
|
+
|
|
290
|
+
for file_path in claim_files:
|
|
291
|
+
with open(file_path, "r") as f:
|
|
292
|
+
claims_data = f.read()
|
|
293
|
+
service_data = extract_sld_list([claims_data], format="837")
|
|
294
|
+
all_service_data.extend(service_data)
|
|
295
|
+
|
|
296
|
+
# Member demographics (from enrollment system or 834 file)
|
|
297
|
+
demographics = Demographics(
|
|
298
|
+
age=45,
|
|
299
|
+
sex="F",
|
|
300
|
+
dual_elgbl_cd="02", # Full benefit dual from 834
|
|
301
|
+
orig_disabled=True,
|
|
302
|
+
new_enrollee=False
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Calculate risk score
|
|
306
|
+
result = processor.run_from_service_data(all_service_data, demographics)
|
|
307
|
+
|
|
308
|
+
print(f"Member Risk Score: {result.risk_score:.3f}")
|
|
309
|
+
print(f"Active HCCs: {result.hcc_list}")
|
|
310
|
+
print(f"Total Services: {len(result.service_level_data)}")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Using CMS BCDA API Data
|
|
314
|
+
|
|
315
|
+
**Scenario**: Building an application that processes Medicare beneficiary data from the BCDA API.
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
319
|
+
import requests
|
|
320
|
+
|
|
321
|
+
# Configure for BCDA data
|
|
322
|
+
processor = HCCInFHIR(
|
|
323
|
+
model_name="CMS-HCC Model V24", # BCDA typically uses V24
|
|
324
|
+
filter_claims=True,
|
|
325
|
+
dx_cc_mapping_filename="ra_dx_to_cc_2025.csv"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Fetch EOB data from BCDA
|
|
329
|
+
# headers = {"Authorization": f"Bearer {access_token}"}
|
|
330
|
+
# response = requests.get("https://sandbox.bcda.cms.gov/api/v2/Patient/$export", headers=headers)
|
|
331
|
+
# eob_resources = response.json()
|
|
332
|
+
|
|
333
|
+
# For demo, use sample data
|
|
334
|
+
from hccinfhir import get_eob_sample_list
|
|
335
|
+
eob_resources = get_eob_sample_list(limit=50)
|
|
336
|
+
|
|
337
|
+
# Demographics (extract from EOB or enrollment system)
|
|
338
|
+
demographics = Demographics(
|
|
339
|
+
age=68,
|
|
340
|
+
sex="M",
|
|
341
|
+
dual_elgbl_cd="00",
|
|
342
|
+
new_enrollee=False,
|
|
343
|
+
esrd=False
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Process FHIR data
|
|
347
|
+
result = processor.run(eob_resources, demographics)
|
|
348
|
+
|
|
349
|
+
print(f"Beneficiary Risk Score: {result.risk_score:.3f}")
|
|
350
|
+
print(f"HCC Categories: {', '.join(result.hcc_list)}")
|
|
351
|
+
print(f"Service Period: {min(svc.service_date for svc in result.service_level_data if svc.service_date)} to {max(svc.service_date for svc in result.service_level_data if svc.service_date)}")
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Direct Diagnosis Code Processing
|
|
355
|
+
|
|
356
|
+
**Scenario**: Quick HCC mapping validation or research without claims data.
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
360
|
+
|
|
361
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
362
|
+
|
|
363
|
+
demographics = Demographics(
|
|
364
|
+
age=75,
|
|
365
|
+
sex="F",
|
|
366
|
+
dual_elgbl_cd="02", # Full benefit dual
|
|
367
|
+
orig_disabled=False,
|
|
368
|
+
new_enrollee=False
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
diagnosis_codes = [
|
|
372
|
+
"E11.9", # Type 2 diabetes
|
|
373
|
+
"I10", # Hypertension
|
|
374
|
+
"N18.3", # CKD stage 3
|
|
375
|
+
"F32.9", # Depression
|
|
376
|
+
"M79.3" # Panniculitis
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
result = processor.calculate_from_diagnosis(diagnosis_codes, demographics)
|
|
380
|
+
|
|
381
|
+
print("=== HCC Risk Analysis ===")
|
|
382
|
+
print(f"Risk Score: {result.risk_score:.3f}")
|
|
383
|
+
print(f"HCC Categories: {result.hcc_list}")
|
|
384
|
+
print(f"\\nDiagnosis Mappings:")
|
|
385
|
+
for cc, dx_list in result.cc_to_dx.items():
|
|
386
|
+
print(f" HCC {cc}: {', '.join(dx_list)}")
|
|
387
|
+
print(f"\\nApplied Coefficients:")
|
|
388
|
+
for coeff_name, value in result.coefficients.items():
|
|
389
|
+
print(f" {coeff_name}: {value:.3f}")
|
|
390
|
+
if result.interactions:
|
|
391
|
+
print(f"\\nDisease Interactions:")
|
|
392
|
+
for interaction, value in result.interactions.items():
|
|
393
|
+
print(f" {interaction}: {value:.3f}")
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## ⚙️ Configuration
|
|
397
|
+
|
|
398
|
+
### Supported HCC Models
|
|
399
|
+
|
|
400
|
+
| Model Name | Model Years | Use Case | Supported |
|
|
401
|
+
|------------|-------------|----------|-----------|
|
|
402
|
+
| `"CMS-HCC Model V22"` | 2024-2025 | Community populations | ✅ |
|
|
403
|
+
| `"CMS-HCC Model V24"` | 2024-2026 | Community populations (current) | ✅ |
|
|
404
|
+
| `"CMS-HCC Model V28"` | 2025-2026 | Community populations (latest) | ✅ |
|
|
405
|
+
| `"CMS-HCC ESRD Model V21"` | 2024-2025 | ESRD populations | ✅ |
|
|
406
|
+
| `"CMS-HCC ESRD Model V24"` | 2025-2026 | ESRD populations | ✅ |
|
|
407
|
+
| `"RxHCC Model V08"` | 2024-2026 | Part D prescription drug | ✅ |
|
|
408
|
+
|
|
409
|
+
### Custom Data Files
|
|
410
|
+
|
|
411
|
+
The library includes bundled CMS reference data for 2025 and 2026. You can override **all 5 data files** with custom versions:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
processor = HCCInFHIR(
|
|
415
|
+
model_name="CMS-HCC Model V28",
|
|
416
|
+
filter_claims=True,
|
|
417
|
+
|
|
418
|
+
# All files support absolute paths, relative paths, or bundled filenames
|
|
419
|
+
# See "Custom File Path Resolution" in Advanced Features for details
|
|
420
|
+
|
|
421
|
+
# 1. CPT/HCPCS Procedure Codes (for CMS filtering)
|
|
422
|
+
proc_filtering_filename="ra_eligible_cpt_hcpcs_2026.csv",
|
|
423
|
+
|
|
424
|
+
# 2. Diagnosis to HCC Mapping (ICD-10 → HCC)
|
|
425
|
+
dx_cc_mapping_filename="ra_dx_to_cc_2026.csv",
|
|
426
|
+
|
|
427
|
+
# 3. HCC Hierarchies (parent HCCs suppress child HCCs)
|
|
428
|
+
hierarchies_filename="ra_hierarchies_2026.csv",
|
|
429
|
+
|
|
430
|
+
# 4. Chronic Condition Flags
|
|
431
|
+
is_chronic_filename="hcc_is_chronic.csv",
|
|
432
|
+
|
|
433
|
+
# 5. RAF Coefficients (demographic + HCC + interaction coefficients)
|
|
434
|
+
coefficients_filename="ra_coefficients_2026.csv"
|
|
435
|
+
)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
> **💡 Tip**: For custom file paths (absolute, relative, or current directory), see [Custom File Path Resolution](#custom-file-path-resolution) in Advanced Features.
|
|
439
|
+
|
|
440
|
+
**File Format Requirements**:
|
|
441
|
+
|
|
442
|
+
1. **proc_filtering** (`ra_eligible_cpt_hcpcs_2026.csv`):
|
|
443
|
+
```csv
|
|
444
|
+
cpt_hcpcs_code
|
|
445
|
+
99213
|
|
446
|
+
99214
|
|
447
|
+
99215
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
2. **dx_cc_mapping** (`ra_dx_to_cc_2026.csv`):
|
|
451
|
+
```csv
|
|
452
|
+
diagnosis_code,cc,model_name
|
|
453
|
+
E119,38,CMS-HCC Model V28
|
|
454
|
+
I10,226,CMS-HCC Model V28
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
3. **hierarchies** (`ra_hierarchies_2026.csv`):
|
|
458
|
+
```csv
|
|
459
|
+
cc_parent,cc_child,model_domain,model_version,model_fullname
|
|
460
|
+
17,18,CMS-HCC,V28,CMS-HCC Model V28
|
|
461
|
+
17,19,CMS-HCC,V28,CMS-HCC Model V28
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
4. **is_chronic** (`hcc_is_chronic.csv`):
|
|
465
|
+
```csv
|
|
466
|
+
hcc,is_chronic,model_version,model_domain
|
|
467
|
+
1,True,V28,CMS-HCC
|
|
468
|
+
2,False,V28,CMS-HCC
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
5. **coefficients** (`ra_coefficients_2026.csv`):
|
|
472
|
+
```csv
|
|
473
|
+
coefficient,value,model_domain,model_version
|
|
474
|
+
cna_f70_74,0.395,CMS-HCC,V28
|
|
475
|
+
cna_hcc19,0.302,CMS-HCC,V28
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
> **📁 Reference**: See complete file formats and structure in the bundled data folder: [src/hccinfhir/data](https://github.com/mimilabs/hccinfhir/tree/main/src/hccinfhir/data)
|
|
479
|
+
|
|
480
|
+
### Demographics Configuration
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
from hccinfhir import Demographics
|
|
484
|
+
|
|
485
|
+
demographics = Demographics(
|
|
486
|
+
# Required fields
|
|
487
|
+
age=67, # Age in years
|
|
488
|
+
sex="F", # "M" or "F" (also accepts "1" or "2")
|
|
489
|
+
|
|
490
|
+
# Dual eligibility (critical for payment accuracy)
|
|
491
|
+
dual_elgbl_cd="00", # "00"=Non-dual, "01"=Partial, "02"=Full
|
|
492
|
+
# "03"=Partial, "04"=Full, "05"=QDWI
|
|
493
|
+
# "06"=QI, "08"=Other full benefit dual
|
|
494
|
+
|
|
495
|
+
# Medicare entitlement
|
|
496
|
+
orec="0", # Original reason for entitlement
|
|
497
|
+
# "0"=Old age, "1"=Disability, "2"=ESRD, "3"=Both
|
|
498
|
+
crec="0", # Current reason for entitlement
|
|
499
|
+
|
|
500
|
+
# Status flags
|
|
501
|
+
orig_disabled=False, # Original disability (affects category)
|
|
502
|
+
new_enrollee=False, # New to Medicare (<12 months)
|
|
503
|
+
esrd=False, # End-Stage Renal Disease (auto-detected from orec/crec)
|
|
504
|
+
|
|
505
|
+
# Optional fields
|
|
506
|
+
snp=False, # Special Needs Plan
|
|
507
|
+
low_income=False, # Low-income subsidy (Part D)
|
|
508
|
+
lti=False, # Long-term institutionalized
|
|
509
|
+
graft_months=None, # Months since kidney transplant (ESRD models)
|
|
510
|
+
fbd=False, # Full benefit dual (auto-set from dual_elgbl_cd)
|
|
511
|
+
pbd=False, # Partial benefit dual (auto-set)
|
|
512
|
+
|
|
513
|
+
# Auto-calculated (can override)
|
|
514
|
+
category="CNA" # Beneficiary category (auto-calculated if omitted)
|
|
515
|
+
)
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## 📚 API Reference
|
|
519
|
+
|
|
520
|
+
### Main Classes
|
|
521
|
+
|
|
522
|
+
#### `HCCInFHIR`
|
|
523
|
+
Main processor class for HCC risk adjustment calculations.
|
|
524
|
+
|
|
525
|
+
**Initialization**:
|
|
526
|
+
```python
|
|
527
|
+
HCCInFHIR(
|
|
528
|
+
filter_claims: bool = True,
|
|
529
|
+
model_name: ModelName = "CMS-HCC Model V28",
|
|
530
|
+
proc_filtering_filename: str = "ra_eligible_cpt_hcpcs_2026.csv",
|
|
531
|
+
dx_cc_mapping_filename: str = "ra_dx_to_cc_2026.csv",
|
|
532
|
+
hierarchies_filename: str = "ra_hierarchies_2026.csv",
|
|
533
|
+
is_chronic_filename: str = "hcc_is_chronic.csv",
|
|
534
|
+
coefficients_filename: str = "ra_coefficients_2026.csv"
|
|
535
|
+
)
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Methods**:
|
|
539
|
+
- `run(eob_list, demographics, prefix_override=None, maci=0.0, norm_factor=1.0, frailty_score=0.0)`
|
|
540
|
+
- Process FHIR ExplanationOfBenefit resources
|
|
541
|
+
|
|
542
|
+
- `run_from_service_data(service_data, demographics, prefix_override=None, maci=0.0, norm_factor=1.0, frailty_score=0.0)`
|
|
543
|
+
- Process service-level data
|
|
544
|
+
|
|
545
|
+
- `calculate_from_diagnosis(diagnosis_codes, demographics, prefix_override=None, maci=0.0, norm_factor=1.0, frailty_score=0.0)`
|
|
546
|
+
- Calculate from diagnosis codes only
|
|
547
|
+
|
|
548
|
+
#### `Demographics`
|
|
549
|
+
Patient demographic information for risk adjustment.
|
|
550
|
+
|
|
551
|
+
**Key Fields**:
|
|
552
|
+
- `age: int` - Patient age in years
|
|
553
|
+
- `sex: str` - Patient sex ("M"/"F" or "1"/"2")
|
|
554
|
+
- `dual_elgbl_cd: str` - Dual eligibility status (see configuration)
|
|
555
|
+
- `orec: str` - Original reason for Medicare entitlement
|
|
556
|
+
- `crec: str` - Current reason for Medicare entitlement
|
|
557
|
+
- `orig_disabled: bool` - Original disability status
|
|
558
|
+
- `new_enrollee: bool` - New enrollee flag
|
|
559
|
+
- `esrd: bool` - ESRD status (auto-calculated from orec/crec)
|
|
560
|
+
- `snp: bool` - Special Needs Plan
|
|
561
|
+
- `low_income: bool` - Low-income subsidy
|
|
562
|
+
- `lti: bool` - Long-term institutionalized
|
|
563
|
+
- `graft_months: Optional[int]` - Months since kidney transplant
|
|
564
|
+
|
|
565
|
+
#### `RAFResult`
|
|
566
|
+
Comprehensive risk adjustment calculation results.
|
|
567
|
+
|
|
568
|
+
**Fields**:
|
|
569
|
+
- `risk_score: float` - Final RAF score
|
|
570
|
+
- `risk_score_demographics: float` - Demographics-only component
|
|
571
|
+
- `risk_score_chronic_only: float` - Chronic conditions component (V24/V28)
|
|
572
|
+
- `risk_score_hcc: float` - HCC conditions component
|
|
573
|
+
- `risk_score_payment: float` - Final payment RAF with adjustments
|
|
574
|
+
- `hcc_list: List[str]` - Active HCC categories
|
|
575
|
+
- `cc_to_dx: Dict[str, Set[str]]` - HCCs mapped to diagnosis codes
|
|
576
|
+
- `coefficients: Dict[str, float]` - Applied coefficients
|
|
577
|
+
- `interactions: Dict[str, float]` - Disease interactions
|
|
578
|
+
- `demographics: Demographics` - Demographics used
|
|
579
|
+
- `model_name: str` - HCC model used
|
|
580
|
+
- `version: str` - Library version
|
|
581
|
+
- `diagnosis_codes: List[str]` - Input diagnosis codes
|
|
582
|
+
- `service_level_data: Optional[List[ServiceLevelData]]` - Service records
|
|
583
|
+
|
|
584
|
+
### Utility Functions
|
|
585
|
+
|
|
586
|
+
```python
|
|
587
|
+
from hccinfhir import (
|
|
588
|
+
get_eob_sample, # Get sample FHIR EOB
|
|
589
|
+
get_837_sample, # Get sample 837 claim
|
|
590
|
+
get_834_sample, # Get sample 834 enrollment
|
|
591
|
+
get_eob_sample_list, # Get multiple EOBs
|
|
592
|
+
get_837_sample_list, # Get multiple 837s
|
|
593
|
+
list_available_samples, # List all samples
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
from hccinfhir.extractor import (
|
|
597
|
+
extract_sld, # Extract from single resource
|
|
598
|
+
extract_sld_list, # Extract from multiple resources
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
from hccinfhir.extractor_834 import (
|
|
602
|
+
extract_enrollment_834, # Parse 834 enrollment file
|
|
603
|
+
enrollment_to_demographics, # Convert to Demographics
|
|
604
|
+
is_losing_medicaid, # Check Medicaid loss
|
|
605
|
+
medicaid_status_summary, # Get comprehensive status
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
from hccinfhir.filter import apply_filter # Apply CMS filtering
|
|
609
|
+
from hccinfhir.model_calculate import calculate_raf # Direct calculation
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## 🔧 Advanced Features
|
|
613
|
+
|
|
614
|
+
### Payment RAF Adjustments
|
|
615
|
+
|
|
616
|
+
Apply CMS payment adjustments to RAF scores:
|
|
617
|
+
|
|
618
|
+
```python
|
|
619
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
620
|
+
|
|
621
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
622
|
+
demographics = Demographics(age=70, sex="F")
|
|
623
|
+
diagnosis_codes = ["E11.9", "I50.22", "N18.3"]
|
|
624
|
+
|
|
625
|
+
# Apply payment adjustments
|
|
626
|
+
result = processor.calculate_from_diagnosis(
|
|
627
|
+
diagnosis_codes,
|
|
628
|
+
demographics,
|
|
629
|
+
maci=0.059, # MA Coding Intensity Adjustment (5.9% reduction for 2026)
|
|
630
|
+
norm_factor=1.015, # Normalization factor (1.5% for 2026)
|
|
631
|
+
frailty_score=0.0 # Frailty adjustment (if applicable)
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
print(f"Base RAF Score: {result.risk_score:.3f}")
|
|
635
|
+
print(f"Payment RAF Score: {result.risk_score_payment:.3f}")
|
|
636
|
+
print(f"Payment Adjustment: {((result.risk_score_payment / result.risk_score) - 1) * 100:.1f}%")
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Common Adjustment Values**:
|
|
640
|
+
- **MACI** (MA Coding Intensity): 5.94% (2025), 5.90% (2026)
|
|
641
|
+
- **Normalization**: 1.022 (2025), 1.015 (2026)
|
|
642
|
+
- **Frailty**: 0.0 to 0.6 (when applicable)
|
|
643
|
+
|
|
644
|
+
### Demographic Prefix Override
|
|
645
|
+
|
|
646
|
+
**Problem**: Demographic data quality issues leading to incorrect RAF calculations.
|
|
647
|
+
|
|
648
|
+
**Solution**: Manually specify the coefficient prefix.
|
|
649
|
+
|
|
650
|
+
```python
|
|
651
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
652
|
+
|
|
653
|
+
# ESRD patient with incorrect orec/crec codes
|
|
654
|
+
processor = HCCInFHIR(model_name="CMS-HCC ESRD Model V24")
|
|
655
|
+
demographics = Demographics(
|
|
656
|
+
age=65,
|
|
657
|
+
sex="F",
|
|
658
|
+
orec="0", # Should be '2' or '3', but data is wrong
|
|
659
|
+
crec="0"
|
|
660
|
+
)
|
|
661
|
+
diagnosis_codes = ["N18.6", "E11.22", "I12.0"]
|
|
662
|
+
|
|
663
|
+
# Force ESRD dialysis coefficients
|
|
664
|
+
result = processor.calculate_from_diagnosis(
|
|
665
|
+
diagnosis_codes,
|
|
666
|
+
demographics,
|
|
667
|
+
prefix_override='DI_' # ESRD Dialysis prefix
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
print(f"RAF Score with override: {result.risk_score:.3f}")
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**Common Prefix Values**:
|
|
674
|
+
|
|
675
|
+
CMS-HCC Models:
|
|
676
|
+
- `CNA_` - Community, Non-Dual, Aged
|
|
677
|
+
- `CND_` - Community, Non-Dual, Disabled
|
|
678
|
+
- `CFA_` - Community, Full Benefit Dual, Aged
|
|
679
|
+
- `CFD_` - Community, Full Benefit Dual, Disabled
|
|
680
|
+
- `CPA_` - Community, Partial Benefit Dual, Aged
|
|
681
|
+
- `CPD_` - Community, Partial Benefit Dual, Disabled
|
|
682
|
+
- `INS_` - Long-Term Institutionalized
|
|
683
|
+
- `NE_` - New Enrollee
|
|
684
|
+
- `SNPNE_` - SNP New Enrollee
|
|
685
|
+
|
|
686
|
+
ESRD Models:
|
|
687
|
+
- `DI_` - Dialysis
|
|
688
|
+
- `DNE_` - Dialysis New Enrollee
|
|
689
|
+
- `GI_`, `GNE_` - Graft variations
|
|
690
|
+
|
|
691
|
+
RxHCC Models:
|
|
692
|
+
- `Rx_CE_LowAged_` - Community, Low Income, Aged
|
|
693
|
+
- `Rx_CE_NoLowAged_` - Community, Not Low Income, Aged
|
|
694
|
+
- `Rx_NE_Lo_` - New Enrollee, Low Income
|
|
695
|
+
|
|
696
|
+
See [CLAUDE.md](./CLAUDE.md#coefficient-prefix-reference) for complete reference.
|
|
697
|
+
|
|
698
|
+
### Custom File Path Resolution
|
|
699
|
+
|
|
700
|
+
The library uses intelligent path resolution to locate data files with the following priority:
|
|
701
|
+
|
|
702
|
+
1. **Absolute path** - If you provide an absolute path, it uses that exact location
|
|
703
|
+
2. **Relative to current working directory** - Checks `./your_file.csv` or `./custom_data/your_file.csv`
|
|
704
|
+
3. **Bundled package data** - Falls back to built-in CMS reference files
|
|
705
|
+
|
|
706
|
+
This allows flexible deployment scenarios without changing code.
|
|
707
|
+
|
|
708
|
+
> **📁 Data File Reference**: See the bundled CMS reference files for format examples: [src/hccinfhir/data](https://github.com/mimilabs/hccinfhir/tree/main/src/hccinfhir/data)
|
|
709
|
+
|
|
710
|
+
#### Basic Examples
|
|
711
|
+
|
|
712
|
+
```python
|
|
713
|
+
from hccinfhir import HCCInFHIR
|
|
714
|
+
|
|
715
|
+
# Option 1: Use bundled data (default - no setup needed)
|
|
716
|
+
processor = HCCInFHIR(
|
|
717
|
+
model_name="CMS-HCC Model V28",
|
|
718
|
+
dx_cc_mapping_filename="ra_dx_to_cc_2026.csv" # ✅ Loads from package
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
# Option 2: Relative path from current directory
|
|
722
|
+
# Assumes: ./custom_data/my_dx_mapping.csv exists
|
|
723
|
+
processor = HCCInFHIR(
|
|
724
|
+
model_name="CMS-HCC Model V28",
|
|
725
|
+
dx_cc_mapping_filename="custom_data/my_dx_mapping.csv" # ✅ ./custom_data/
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# Option 3: Absolute path (production deployments)
|
|
729
|
+
processor = HCCInFHIR(
|
|
730
|
+
model_name="CMS-HCC Model V28",
|
|
731
|
+
dx_cc_mapping_filename="/var/data/cms/dx_mapping_2026.csv" # ✅ Absolute
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
# Option 4: Mix bundled and custom files
|
|
735
|
+
processor = HCCInFHIR(
|
|
736
|
+
model_name="CMS-HCC Model V28",
|
|
737
|
+
dx_cc_mapping_filename="ra_dx_to_cc_2026.csv", # Bundled default
|
|
738
|
+
coefficients_filename="custom_coefficients.csv" # Custom from current dir
|
|
739
|
+
)
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
#### Real-World Scenarios
|
|
743
|
+
|
|
744
|
+
**Scenario 1: Development Environment**
|
|
745
|
+
```python
|
|
746
|
+
# Use bundled files for testing
|
|
747
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Scenario 2: Custom Coefficients for Research**
|
|
751
|
+
```python
|
|
752
|
+
# Keep standard mappings, customize coefficients
|
|
753
|
+
# File: ./research/adjusted_coefficients.csv
|
|
754
|
+
processor = HCCInFHIR(
|
|
755
|
+
model_name="CMS-HCC Model V28",
|
|
756
|
+
coefficients_filename="research/adjusted_coefficients.csv"
|
|
757
|
+
)
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**Scenario 3: Production with Centralized Data**
|
|
761
|
+
```python
|
|
762
|
+
# All custom files in shared network location
|
|
763
|
+
data_path = "/mnt/shared/cms_data/2026"
|
|
764
|
+
processor = HCCInFHIR(
|
|
765
|
+
model_name="CMS-HCC Model V28",
|
|
766
|
+
proc_filtering_filename=f"{data_path}/cpt_hcpcs.csv",
|
|
767
|
+
dx_cc_mapping_filename=f"{data_path}/dx_to_cc.csv",
|
|
768
|
+
hierarchies_filename=f"{data_path}/hierarchies.csv",
|
|
769
|
+
is_chronic_filename=f"{data_path}/chronic_flags.csv",
|
|
770
|
+
coefficients_filename=f"{data_path}/coefficients.csv"
|
|
771
|
+
)
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Scenario 4: Docker Container with Mounted Volume**
|
|
775
|
+
```python
|
|
776
|
+
# Files mounted at /app/data
|
|
777
|
+
processor = HCCInFHIR(
|
|
778
|
+
model_name="CMS-HCC Model V28",
|
|
779
|
+
dx_cc_mapping_filename="/app/data/dx_to_cc_custom.csv",
|
|
780
|
+
coefficients_filename="/app/data/coefficients_custom.csv"
|
|
781
|
+
# Other files use bundled defaults
|
|
782
|
+
)
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
#### Error Handling
|
|
786
|
+
|
|
787
|
+
```python
|
|
788
|
+
from hccinfhir import HCCInFHIR
|
|
789
|
+
|
|
790
|
+
try:
|
|
791
|
+
processor = HCCInFHIR(
|
|
792
|
+
model_name="CMS-HCC Model V28",
|
|
793
|
+
dx_cc_mapping_filename="nonexistent.csv"
|
|
794
|
+
)
|
|
795
|
+
except FileNotFoundError as e:
|
|
796
|
+
print(f"File not found: {e}")
|
|
797
|
+
# Error shows all locations checked:
|
|
798
|
+
# - Current directory: /path/to/cwd
|
|
799
|
+
# - Package data: hccinfhir.data
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Batch Processing
|
|
803
|
+
|
|
804
|
+
```python
|
|
805
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
806
|
+
|
|
807
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
808
|
+
|
|
809
|
+
# Process multiple beneficiaries
|
|
810
|
+
beneficiaries = [
|
|
811
|
+
{"id": "001", "age": 67, "sex": "F", "dual": "00", "dx": ["E11.9", "I10"]},
|
|
812
|
+
{"id": "002", "age": 45, "sex": "M", "dual": "02", "dx": ["N18.4", "F32.9"]},
|
|
813
|
+
{"id": "003", "age": 78, "sex": "F", "dual": "01", "dx": ["F03.90", "I48.91"]},
|
|
814
|
+
]
|
|
815
|
+
|
|
816
|
+
results = []
|
|
817
|
+
for ben in beneficiaries:
|
|
818
|
+
demographics = Demographics(
|
|
819
|
+
age=ben["age"],
|
|
820
|
+
sex=ben["sex"],
|
|
821
|
+
dual_elgbl_cd=ben["dual"]
|
|
822
|
+
)
|
|
823
|
+
result = processor.calculate_from_diagnosis(ben["dx"], demographics)
|
|
824
|
+
results.append({
|
|
825
|
+
"beneficiary_id": ben["id"],
|
|
826
|
+
"risk_score": result.risk_score,
|
|
827
|
+
"risk_score_payment": result.risk_score_payment,
|
|
828
|
+
"hcc_list": result.hcc_list
|
|
829
|
+
})
|
|
830
|
+
|
|
831
|
+
# Export results
|
|
832
|
+
import json
|
|
833
|
+
with open("risk_scores.json", "w") as f:
|
|
834
|
+
json.dump(results, f, indent=2)
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### Converting to Dictionaries
|
|
838
|
+
|
|
839
|
+
All Pydantic models support dictionary conversion for JSON serialization, database storage, or legacy code:
|
|
840
|
+
|
|
841
|
+
```python
|
|
842
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
843
|
+
|
|
844
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
845
|
+
demographics = Demographics(age=67, sex="F")
|
|
846
|
+
result = processor.calculate_from_diagnosis(["E11.9"], demographics)
|
|
847
|
+
|
|
848
|
+
# Convert to dictionary
|
|
849
|
+
result_dict = result.model_dump()
|
|
850
|
+
print(result_dict["risk_score"]) # Dictionary access
|
|
851
|
+
|
|
852
|
+
# JSON-safe conversion
|
|
853
|
+
result_json = result.model_dump(mode='json')
|
|
854
|
+
|
|
855
|
+
# Partial conversion
|
|
856
|
+
summary = result.model_dump(include={"risk_score", "hcc_list", "model_name"})
|
|
857
|
+
|
|
858
|
+
# Exclude large nested data
|
|
859
|
+
compact = result.model_dump(exclude={"service_level_data"})
|
|
860
|
+
|
|
861
|
+
# Convert to JSON string
|
|
862
|
+
json_string = result.model_dump_json()
|
|
863
|
+
|
|
864
|
+
# API response (FastAPI)
|
|
865
|
+
from fastapi import FastAPI
|
|
866
|
+
app = FastAPI()
|
|
867
|
+
|
|
868
|
+
@app.post("/calculate")
|
|
869
|
+
def calculate_risk(diagnosis_codes: list, demographics: dict):
|
|
870
|
+
demo = Demographics(**demographics)
|
|
871
|
+
result = processor.calculate_from_diagnosis(diagnosis_codes, demo)
|
|
872
|
+
return result.model_dump(mode='json') # Automatic JSON serialization
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
## 📝 Sample Data
|
|
876
|
+
|
|
877
|
+
Comprehensive sample data for testing and development:
|
|
878
|
+
|
|
879
|
+
```python
|
|
880
|
+
from hccinfhir import (
|
|
881
|
+
get_eob_sample,
|
|
882
|
+
get_837_sample,
|
|
883
|
+
get_834_sample,
|
|
884
|
+
list_available_samples
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
# FHIR EOB samples (3 individual + 200 batch)
|
|
888
|
+
eob = get_eob_sample(1) # Cases 1, 2, 3 (returns single dict)
|
|
889
|
+
eob_list = get_eob_sample_list(limit=50) # Returns list
|
|
890
|
+
|
|
891
|
+
# Usage: processor.run() expects a list, so wrap single EOB
|
|
892
|
+
result = processor.run([eob], demographics) # Note: [eob] not eob
|
|
893
|
+
|
|
894
|
+
# X12 837 samples (13 different scenarios)
|
|
895
|
+
claim = get_837_sample(0) # Cases 0-12 (returns string)
|
|
896
|
+
claims = get_837_sample_list([0, 1, 2]) # Returns list
|
|
897
|
+
|
|
898
|
+
# X12 834 enrollment samples
|
|
899
|
+
enrollment_834 = get_834_sample(1) # Currently only case 1 available (returns string)
|
|
900
|
+
|
|
901
|
+
# List all available samples
|
|
902
|
+
info = list_available_samples()
|
|
903
|
+
print(f"EOB samples: {info['eob_case_numbers']}")
|
|
904
|
+
print(f"837 samples: {info['837_case_numbers']}")
|
|
905
|
+
print(f"834 samples: {info['834_case_numbers']}")
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
## 🧪 Testing
|
|
909
|
+
|
|
910
|
+
```bash
|
|
911
|
+
# Activate virtual environment
|
|
912
|
+
hatch shell
|
|
913
|
+
|
|
914
|
+
# Install in development mode
|
|
915
|
+
pip install -e .
|
|
916
|
+
|
|
917
|
+
# Run all tests (155 tests)
|
|
918
|
+
pytest tests/
|
|
919
|
+
|
|
920
|
+
# Run specific test file
|
|
921
|
+
pytest tests/test_model_calculate.py -v
|
|
922
|
+
|
|
923
|
+
# Run with coverage
|
|
924
|
+
pytest tests/ --cov=hccinfhir --cov-report=html
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
## 📄 License
|
|
928
|
+
|
|
929
|
+
Apache License 2.0. See [LICENSE](LICENSE) for details.
|
|
930
|
+
|
|
931
|
+
## 📞 Support
|
|
932
|
+
|
|
933
|
+
- **Claude Code Documentation**: [CLAUDE.md](./CLAUDE.md) - Comprehensive developer guide
|
|
934
|
+
- **Issues**: [GitHub Issues](https://github.com/mimilabs/hccinfhir/issues)
|
|
935
|
+
|
|
936
|
+
## 👥 Contributors
|
|
937
|
+
|
|
938
|
+
We're grateful to all contributors who have helped improve this project:
|
|
939
|
+
|
|
940
|
+
- [@choyiny](https://github.com/choyiny) - Custom CSV input feature, file path improvements
|
|
941
|
+
|
|
942
|
+
**Want to contribute?** We're always looking for great minds to contribute to this project! Simply make a PR or open a ticket and we'll get connected.
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
**Made with ❤️ by the HCCInFHIR team**
|