hccinfhir 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl
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/data/hcc_is_chronic.csv +835 -836
- hccinfhir/data/hcc_is_chronic_without_esrd_model.csv +877 -0
- hccinfhir/datamodels.py +41 -1
- hccinfhir/extractor_837.py +2 -5
- hccinfhir/extractor_fhir.py +2 -2
- hccinfhir/hccinfhir.py +74 -23
- hccinfhir/model_calculate.py +44 -28
- hccinfhir/model_coefficients.py +11 -7
- hccinfhir/model_demographics.py +62 -12
- hccinfhir/model_interactions.py +34 -11
- {hccinfhir-0.1.5.dist-info → hccinfhir-0.1.7.dist-info}/METADATA +91 -2
- {hccinfhir-0.1.5.dist-info → hccinfhir-0.1.7.dist-info}/RECORD +14 -13
- {hccinfhir-0.1.5.dist-info → hccinfhir-0.1.7.dist-info}/WHEEL +0 -0
- {hccinfhir-0.1.5.dist-info → hccinfhir-0.1.7.dist-info}/licenses/LICENSE +0 -0
hccinfhir/model_interactions.py
CHANGED
|
@@ -8,8 +8,10 @@ def has_any_hcc(hcc_list: list[str], hcc_set: set[str]) -> int:
|
|
|
8
8
|
def create_demographic_interactions(demographics: Demographics) -> dict:
|
|
9
9
|
"""Creates common demographic-based interactions"""
|
|
10
10
|
interactions = {}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# Determine sex from demographics.sex instead of category
|
|
12
|
+
# Category can start with 'NEM'/'NEF' for new enrollees, not just 'M'/'F'
|
|
13
|
+
is_female = demographics.sex in ('F', '2')
|
|
14
|
+
is_male = demographics.sex in ('M', '1')
|
|
13
15
|
is_aged = not demographics.non_aged
|
|
14
16
|
|
|
15
17
|
# Original Disability interactions
|
|
@@ -33,21 +35,32 @@ def create_demographic_interactions(demographics: Demographics) -> dict:
|
|
|
33
35
|
nemcaid = True
|
|
34
36
|
ne_origds = int(demographics.age >= 65 and (demographics.orec is not None and demographics.orec == "1"))
|
|
35
37
|
|
|
38
|
+
fbd = demographics.fbd
|
|
39
|
+
|
|
36
40
|
# Four mutually exclusive groups
|
|
37
41
|
interactions.update({
|
|
38
42
|
f'NMCAID_NORIGDIS_{demographics.category}': int(not nemcaid and not ne_origds),
|
|
39
43
|
f'MCAID_NORIGDIS_{demographics.category}': int(nemcaid and not ne_origds),
|
|
40
44
|
f'NMCAID_ORIGDIS_{demographics.category}': int(not nemcaid and ne_origds),
|
|
41
|
-
f'MCAID_ORIGDIS_{demographics.category}': int(nemcaid and ne_origds)
|
|
45
|
+
f'MCAID_ORIGDIS_{demographics.category}': int(nemcaid and ne_origds),
|
|
46
|
+
f'FBD_NORIGDIS_{demographics.category}': int(fbd and not ne_origds),
|
|
47
|
+
f'FBD_ORIGDIS_{demographics.category}': int(fbd and ne_origds),
|
|
48
|
+
f'ND_PBD_NORIGDIS_{demographics.category}': int(not fbd and not ne_origds),
|
|
49
|
+
f'ND_PBD_ORIGDIS_{demographics.category}': int(not fbd and ne_origds)
|
|
42
50
|
})
|
|
43
51
|
|
|
52
|
+
# output only non-zero interactions
|
|
53
|
+
interactions = {k: v for k, v in interactions.items() if v > 0}
|
|
54
|
+
|
|
44
55
|
return interactions
|
|
45
56
|
|
|
46
57
|
def create_dual_interactions(demographics: Demographics) -> dict:
|
|
47
58
|
"""Creates dual status interactions"""
|
|
48
59
|
interactions = {}
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
# Determine sex from demographics.sex instead of category
|
|
61
|
+
# Category can start with 'NEM'/'NEF' for new enrollees, not just 'M'/'F'
|
|
62
|
+
is_female = demographics.sex in ('F', '2')
|
|
63
|
+
is_male = demographics.sex in ('M', '1')
|
|
51
64
|
is_aged = not demographics.non_aged
|
|
52
65
|
|
|
53
66
|
if demographics.fbd:
|
|
@@ -77,12 +90,17 @@ def create_hcc_counts(hcc_set: set[str]) -> dict:
|
|
|
77
90
|
counts[f'D{i}'] = int(hcc_count == i)
|
|
78
91
|
counts['D10P'] = int(hcc_count >= 10)
|
|
79
92
|
|
|
93
|
+
# output only non-zero counts
|
|
94
|
+
counts = {k: v for k, v in counts.items() if v > 0}
|
|
95
|
+
|
|
80
96
|
return counts
|
|
81
97
|
|
|
82
98
|
def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
83
99
|
"""Creates disease categories based on model version"""
|
|
100
|
+
categories = {}
|
|
101
|
+
|
|
84
102
|
if model_name == "CMS-HCC Model V28":
|
|
85
|
-
|
|
103
|
+
categories = {
|
|
86
104
|
'CANCER_V28': has_any_hcc(['17', '18', '19', '20', '21', '22', '23'], hcc_set),
|
|
87
105
|
'DIABETES_V28': has_any_hcc(['35', '36', '37', '38'], hcc_set),
|
|
88
106
|
'CARD_RESP_FAIL_V28': has_any_hcc(['211', '212', '213'], hcc_set),
|
|
@@ -96,7 +114,7 @@ def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
|
96
114
|
'ULCER_V28': has_any_hcc(['379', '380', '381', '382'], hcc_set)
|
|
97
115
|
}
|
|
98
116
|
elif model_name == "CMS-HCC Model V24":
|
|
99
|
-
|
|
117
|
+
categories = {
|
|
100
118
|
'CANCER': has_any_hcc(['8', '9', '10', '11', '12'], hcc_set),
|
|
101
119
|
'DIABETES': has_any_hcc(['17', '18', '19'], hcc_set),
|
|
102
120
|
'CARD_RESP_FAIL': has_any_hcc(['82', '83', '84'], hcc_set),
|
|
@@ -109,7 +127,7 @@ def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
|
109
127
|
'PRESSURE_ULCER': has_any_hcc(['157', '158', '159'], hcc_set) # added in 2018-11-20
|
|
110
128
|
}
|
|
111
129
|
elif model_name == "CMS-HCC Model V22":
|
|
112
|
-
|
|
130
|
+
categories = {
|
|
113
131
|
'CANCER': has_any_hcc(['8', '9', '10', '11', '12'], hcc_set),
|
|
114
132
|
'DIABETES': has_any_hcc(['17', '18', '19'], hcc_set),
|
|
115
133
|
'CARD_RESP_FAIL': has_any_hcc(['82', '83', '84'], hcc_set),
|
|
@@ -122,7 +140,7 @@ def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
|
122
140
|
'PRESSURE_ULCER': has_any_hcc(['157', '158'], hcc_set) # added in 2012-10-19
|
|
123
141
|
}
|
|
124
142
|
elif model_name == "CMS-HCC ESRD Model V24":
|
|
125
|
-
|
|
143
|
+
categories = {
|
|
126
144
|
'CANCER': has_any_hcc(['8', '9', '10', '11', '12'], hcc_set),
|
|
127
145
|
'DIABETES': has_any_hcc(['17', '18', '19'], hcc_set),
|
|
128
146
|
'CARD_RESP_FAIL': has_any_hcc(['82', '83', '84'], hcc_set),
|
|
@@ -135,7 +153,7 @@ def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
|
135
153
|
'gPsychiatric_V24': has_any_hcc(['57', '58', '59', '60'], hcc_set)
|
|
136
154
|
}
|
|
137
155
|
elif model_name == "CMS-HCC ESRD Model V21":
|
|
138
|
-
|
|
156
|
+
categories = {
|
|
139
157
|
'CANCER': has_any_hcc(['8', '9', '10', '11', '12'], hcc_set),
|
|
140
158
|
'DIABETES': has_any_hcc(['17', '18', '19'], hcc_set),
|
|
141
159
|
'IMMUNE': int('47' in hcc_set),
|
|
@@ -150,7 +168,9 @@ def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
|
|
|
150
168
|
elif model_name == "RxHCC Model V08":
|
|
151
169
|
# RxModel doesn't seem to have any diagnostic category interactions
|
|
152
170
|
return {}
|
|
153
|
-
|
|
171
|
+
|
|
172
|
+
# keep the zero-valued categories. Will be filtered out later
|
|
173
|
+
return categories
|
|
154
174
|
|
|
155
175
|
def create_disease_interactions(model_name: ModelName,
|
|
156
176
|
diagnostic_cats: dict,
|
|
@@ -317,6 +337,9 @@ def create_disease_interactions(model_name: ModelName,
|
|
|
317
337
|
'NonAged_RXHCC159': demographics.non_aged * int('159' in hcc_set),
|
|
318
338
|
'NonAged_RXHCC163': demographics.non_aged * int('163' in hcc_set)
|
|
319
339
|
})
|
|
340
|
+
|
|
341
|
+
# output only non-zero interactions
|
|
342
|
+
interactions = {k: v for k, v in interactions.items() if v > 0}
|
|
320
343
|
|
|
321
344
|
return interactions
|
|
322
345
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: hccinfhir
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: HCC Algorithm for FHIR Resources
|
|
5
5
|
Project-URL: Homepage, https://github.com/mimilabs/hccinfhir
|
|
6
6
|
Project-URL: Issues, https://github.com/mimilabs/hccinfhir/issues
|
|
@@ -535,6 +535,89 @@ export_data = result.model_dump(
|
|
|
535
535
|
)
|
|
536
536
|
```
|
|
537
537
|
|
|
538
|
+
### Overriding Demographic Categorization
|
|
539
|
+
|
|
540
|
+
**Problem**: Sometimes demographic data has quality issues (e.g., ESRD patients with incorrect `orec`/`crec` codes), leading to wrong risk score calculations.
|
|
541
|
+
|
|
542
|
+
**Solution**: Use the `prefix_override` parameter to manually specify the coefficient prefix.
|
|
543
|
+
|
|
544
|
+
#### Common Use Case: ESRD Patients with Incorrect OREC/CREC
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
from hccinfhir import HCCInFHIR, Demographics
|
|
548
|
+
|
|
549
|
+
# ESRD dialysis patient, but source data has wrong orec/crec codes
|
|
550
|
+
processor = HCCInFHIR(model_name="CMS-HCC ESRD Model V24")
|
|
551
|
+
|
|
552
|
+
demographics = Demographics(
|
|
553
|
+
age=65,
|
|
554
|
+
sex="F",
|
|
555
|
+
orec="0", # Should be '2' or '3' for ESRD, but data is incorrect
|
|
556
|
+
crec="0" # Should be '2' or '3' for ESRD, but data is incorrect
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
diagnosis_codes = ["N18.6", "E11.22", "I12.0"] # ESRD + diabetes + hypertensive CKD
|
|
560
|
+
|
|
561
|
+
# Force ESRD dialysis coefficients despite incorrect orec/crec
|
|
562
|
+
result = processor.calculate_from_diagnosis(
|
|
563
|
+
diagnosis_codes,
|
|
564
|
+
demographics,
|
|
565
|
+
prefix_override='DI_' # DI_ = ESRD Dialysis
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
print(f"Risk Score with override: {result.risk_score}")
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
#### Other Common Scenarios
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
# Long-term institutionalized patient not properly flagged
|
|
575
|
+
processor = HCCInFHIR(model_name="CMS-HCC Model V28")
|
|
576
|
+
demographics = Demographics(age=78, sex="M")
|
|
577
|
+
diagnosis_codes = ["F03.90", "I48.91", "N18.4"]
|
|
578
|
+
|
|
579
|
+
result = processor.calculate_from_diagnosis(
|
|
580
|
+
diagnosis_codes,
|
|
581
|
+
demographics,
|
|
582
|
+
prefix_override='INS_' # INS_ = Institutionalized
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# New enrollee with missing flag
|
|
586
|
+
result = processor.calculate_from_diagnosis(
|
|
587
|
+
diagnosis_codes,
|
|
588
|
+
demographics,
|
|
589
|
+
prefix_override='NE_' # NE_ = New Enrollee
|
|
590
|
+
)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### Common Prefix Values
|
|
594
|
+
|
|
595
|
+
**CMS-HCC Models (V22, V24, V28):**
|
|
596
|
+
- `CNA_` - Community, Non-Dual, Aged (65+)
|
|
597
|
+
- `CND_` - Community, Non-Dual, Disabled (<65)
|
|
598
|
+
- `CFA_` - Community, Full Benefit Dual, Aged
|
|
599
|
+
- `CFD_` - Community, Full Benefit Dual, Disabled
|
|
600
|
+
- `CPA_` - Community, Partial Benefit Dual, Aged
|
|
601
|
+
- `CPD_` - Community, Partial Benefit Dual, Disabled
|
|
602
|
+
- `INS_` - Long-Term Institutionalized
|
|
603
|
+
- `NE_` - New Enrollee
|
|
604
|
+
- `SNPNE_` - Special Needs Plan New Enrollee
|
|
605
|
+
|
|
606
|
+
**ESRD Models (V21, V24):**
|
|
607
|
+
- `DI_` - Dialysis (standard)
|
|
608
|
+
- `DNE_` - Dialysis New Enrollee
|
|
609
|
+
- `GI_` - Graft, Institutionalized
|
|
610
|
+
- `GNE_` - Graft, New Enrollee
|
|
611
|
+
- `GFPA_`, `GFPN_`, `GNPA_`, `GNPN_` - Graft with various dual/age combinations
|
|
612
|
+
|
|
613
|
+
**RxHCC Model (V08):**
|
|
614
|
+
- `Rx_CE_LowAged_` - Community, Low Income, Aged
|
|
615
|
+
- `Rx_CE_NoLowAged_` - Community, Not Low Income, Aged
|
|
616
|
+
- `Rx_NE_Lo_` - New Enrollee, Low Income
|
|
617
|
+
- `Rx_CE_LTI_` - Community, Long-Term Institutionalized
|
|
618
|
+
|
|
619
|
+
See [CLAUDE.md](./CLAUDE.md#coefficient-prefix-reference) for complete prefix reference.
|
|
620
|
+
|
|
538
621
|
### Custom Filtering Rules
|
|
539
622
|
|
|
540
623
|
```python
|
|
@@ -544,7 +627,7 @@ from hccinfhir.filter import apply_filter
|
|
|
544
627
|
filtered_data = apply_filter(
|
|
545
628
|
service_data,
|
|
546
629
|
include_inpatient=True,
|
|
547
|
-
include_outpatient=True,
|
|
630
|
+
include_outpatient=True,
|
|
548
631
|
eligible_cpt_hcpcs_file="custom_procedures.csv"
|
|
549
632
|
)
|
|
550
633
|
```
|
|
@@ -665,6 +748,12 @@ FROM mimi_ws_1.cmspayment.ra_eligible_cpt_hcpcs
|
|
|
665
748
|
WHERE is_included = 'yes' AND YEAR(mimi_src_file_date) = 2025;
|
|
666
749
|
```
|
|
667
750
|
|
|
751
|
+
`hcc_is_chronic.csv`
|
|
752
|
+
```sql
|
|
753
|
+
SELECT hcc, is_chronic, model_version, model_domain
|
|
754
|
+
FROM cmspayment.ra_report_to_congress
|
|
755
|
+
WHERE mimi_src_file_name = '2024riskadjustmentinma-rtc.pdf'
|
|
756
|
+
```
|
|
668
757
|
|
|
669
758
|
## 🧪 Testing
|
|
670
759
|
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
hccinfhir/__init__.py,sha256=G_5m6jm3_BK5NdcZWoi0NEKJEsE_LjAU1RaLaL9xNPU,1043
|
|
2
|
-
hccinfhir/datamodels.py,sha256=
|
|
2
|
+
hccinfhir/datamodels.py,sha256=EHkuWMhmHBt8GfVP3lrxfSogu-qZQzeforFzp0Bn_bM,7714
|
|
3
3
|
hccinfhir/extractor.py,sha256=xL9c2VT-e2I7_c8N8j4Og42UEgVuCzyn9WFp3ntM5Ro,1822
|
|
4
|
-
hccinfhir/extractor_837.py,sha256=
|
|
5
|
-
hccinfhir/extractor_fhir.py,sha256=
|
|
4
|
+
hccinfhir/extractor_837.py,sha256=Irp0ROWgZv6jru9w5sdkoTSYHDTh0v_I_xYRhWdHOjw,13037
|
|
5
|
+
hccinfhir/extractor_fhir.py,sha256=wUN3vTm1oTZ-KvfcDebnpQMxAC-7YlRKv12Wrv3p85A,8490
|
|
6
6
|
hccinfhir/filter.py,sha256=j_yD2g6RBXVUV9trKkWzsQ35x3fRvfKUPvEXKUefI64,2007
|
|
7
|
-
hccinfhir/hccinfhir.py,sha256=
|
|
8
|
-
hccinfhir/model_calculate.py,sha256=
|
|
9
|
-
hccinfhir/model_coefficients.py,sha256=
|
|
10
|
-
hccinfhir/model_demographics.py,sha256=
|
|
7
|
+
hccinfhir/hccinfhir.py,sha256=tgNWGYvsQWOlmcnP-3yH3KfXgZtQ3IdxYfGP9SNSJb0,9879
|
|
8
|
+
hccinfhir/model_calculate.py,sha256=vM0b4BkLafebk7A3yFxsEONRW4GkVbUl4GxNlLz5N8Q,6742
|
|
9
|
+
hccinfhir/model_coefficients.py,sha256=2Y_xCjX4__B1_xkX3pp-XTOW4KEAWo6RCVOOJ7ZZajM,5931
|
|
10
|
+
hccinfhir/model_demographics.py,sha256=CR4WC8XVq-CI1nYJoVFc5-KXTw-pKoVlHkHqfnXlnj0,9121
|
|
11
11
|
hccinfhir/model_dx_to_cc.py,sha256=guJny2Mb9z8YRNWCXGSIE3APbE06zwnA2NRkjAeUs60,1765
|
|
12
12
|
hccinfhir/model_hierarchies.py,sha256=0kdBmF_8e_ikMHBDhlw2I7jT3DupHfUn6o5mWj7v3Yo,2910
|
|
13
|
-
hccinfhir/model_interactions.py,sha256=
|
|
13
|
+
hccinfhir/model_interactions.py,sha256=xdsTuc3ii8U_MaPpYv0SnR4eR_w72eHKUJn1mwmZHm4,21379
|
|
14
14
|
hccinfhir/samples.py,sha256=cqytZ-N08cPGqnUXJfzrEhhHW2qL7SDPYLD9-CHXZIU,8446
|
|
15
15
|
hccinfhir/utils.py,sha256=AAHwzMSW8O9VZp1KLcdlN3OeBbxQtqQRtbTTdrKf7M0,2784
|
|
16
16
|
hccinfhir/data/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
|
|
17
|
-
hccinfhir/data/hcc_is_chronic.csv,sha256=
|
|
17
|
+
hccinfhir/data/hcc_is_chronic.csv,sha256=Bwd-RND6SdEsKP-assoBaXnjUJAuDXhSkwWlymux72Y,19701
|
|
18
|
+
hccinfhir/data/hcc_is_chronic_without_esrd_model.csv,sha256=eVVI4_8mQNkiBiNO3kattfT_zfcV18XgmiltdzZEXSo,17720
|
|
18
19
|
hccinfhir/data/ra_coefficients_2025.csv,sha256=I0S2hoJlfig-D0oSFxy0b3Piv7m9AzOGo2CwR6bcQ9w,215191
|
|
19
20
|
hccinfhir/data/ra_coefficients_2026.csv,sha256=0gfjGgVdIEWkBO01NaAbTLMzHCYINA0rf_zl8ojngCY,288060
|
|
20
21
|
hccinfhir/data/ra_dx_to_cc_2025.csv,sha256=4N7vF6VZndkl7d3Fo0cGsbAPAZdCjAizSH8BOKsZNAo,1618924
|
|
@@ -43,7 +44,7 @@ hccinfhir/sample_files/sample_eob_1.json,sha256=_NGSVR2ysFpx-DcTvyga6dFCzhQ8Vi9f
|
|
|
43
44
|
hccinfhir/sample_files/sample_eob_2.json,sha256=FcnJcx0ApOczxjJ_uxVLzCep9THfNf4xs9Yf7hxk8e4,1769
|
|
44
45
|
hccinfhir/sample_files/sample_eob_200.ndjson,sha256=CxpjeQ1DCMUzZILaM68UEhfxO0p45YGhDDoCZeq8PxU,1917986
|
|
45
46
|
hccinfhir/sample_files/sample_eob_3.json,sha256=4BW4wOMBEEU9RDfJR15rBEvk0KNHyuMEh3e055y87Hc,2306
|
|
46
|
-
hccinfhir-0.1.
|
|
47
|
-
hccinfhir-0.1.
|
|
48
|
-
hccinfhir-0.1.
|
|
49
|
-
hccinfhir-0.1.
|
|
47
|
+
hccinfhir-0.1.7.dist-info/METADATA,sha256=o2Uur5C9layHtKuWQeEQ1THAwMpCLmEkmnVTvrOUWDE,24819
|
|
48
|
+
hccinfhir-0.1.7.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
49
|
+
hccinfhir-0.1.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
50
|
+
hccinfhir-0.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|