hccinfhir 0.2.5__py3-none-any.whl → 0.2.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.
@@ -115,6 +115,8 @@ def calculate_raf(diagnosis_codes: List[str],
115
115
  demographic_interactions[key] = value
116
116
  elif key.startswith('OriginallyDisabled_'):
117
117
  demographic_interactions[key] = value
118
+ elif key == 'LTIMCAID':
119
+ demographic_interactions[key] = value
118
120
 
119
121
  coefficients_demographics = apply_coefficients(demographics,
120
122
  set(),
@@ -101,7 +101,11 @@ def apply_coefficients(demographics: Demographics,
101
101
 
102
102
  # Apply the coefficients
103
103
  for hcc in hcc_set:
104
- key = (f"{prefix}HCC{hcc}".lower(), model_name)
104
+ # For RxHCC models, use RXHCC prefix instead of HCC
105
+ if 'RxHCC' in model_name:
106
+ key = (f"{prefix}RXHCC{hcc}".lower(), model_name)
107
+ else:
108
+ key = (f"{prefix}HCC{hcc}".lower(), model_name)
105
109
 
106
110
  if key in coefficients:
107
111
  value = coefficients[key]
@@ -112,11 +116,23 @@ def apply_coefficients(demographics: Demographics,
112
116
  if interaction_value < 1:
113
117
  continue
114
118
 
119
+ # Standard prefix-based lookup
115
120
  key = (f"{prefix}{interaction_key}".lower(), model_name)
116
121
  if key in coefficients:
117
122
  value = coefficients[key]
118
123
  output[interaction_key] = value
119
124
 
125
+ # No-prefix lookup for ESRD duration coefficients stored without prefix
126
+ # ESRD V21: GE65_DUR*, LT65_DUR*; ESRD V24: FGC_*, FGI_*, LTI_GE65/LT65
127
+ if (interaction_key.startswith('FGC') or
128
+ interaction_key.startswith('FGI') or
129
+ interaction_key.startswith('GE65_DUR') or
130
+ interaction_key.startswith('LT65_DUR') or
131
+ interaction_key in ('LTI_GE65', 'LTI_LT65')):
132
+ key = (interaction_key.lower(), model_name)
133
+ if key in coefficients:
134
+ value = coefficients[key]
135
+ output[interaction_key] = value
120
136
 
121
137
  return output
122
138
 
@@ -6,50 +6,145 @@ def has_any_hcc(hcc_list: List[str], hcc_set: Set[str]) -> int:
6
6
  return int(bool(set(hcc_list) & hcc_set))
7
7
 
8
8
  def create_demographic_interactions(demographics: Demographics) -> dict:
9
- """Creates common demographic-based interactions"""
9
+ """Creates common demographic-based interactions.
10
+
11
+ This function creates interaction variables that are model-agnostic.
12
+ The coefficient lookup will match only the relevant coefficients for
13
+ each model. Comments indicate which models primarily use each interaction.
14
+ """
10
15
  interactions = {}
11
- # Determine sex from demographics.sex instead of category
12
- # Category can start with 'NEM'/'NEF' for new enrollees, not just 'M'/'F'
16
+
17
+ # Common demographic flags
13
18
  is_female = demographics.sex in ('F', '2')
14
19
  is_male = demographics.sex in ('M', '1')
15
20
  is_aged = not demographics.non_aged
16
-
17
- # Original Disability interactions
21
+ lti = int(demographics.lti) if demographics.lti else 0
22
+ fbd = demographics.fbd
23
+ pbd = demographics.pbd
24
+ graft_months = demographics.graft_months
25
+
26
+ # Medicaid indicator (any dual status)
27
+ mcaid = 1 if demographics.dual_elgbl_cd in {
28
+ '01', '02', '03', '04', '05', '06', '07', '08', '09', '10'
29
+ } else 0
30
+
31
+ # Original Disability interactions (V22, V24, V28, ESRD V21, V24)
32
+ # Only for aged (65+); looked up with prefix (e.g., CNA_, DI_)
18
33
  if is_aged:
19
34
  interactions['OriginallyDisabled_Female'] = int(demographics.orig_disabled) * int(is_female)
20
35
  interactions['OriginallyDisabled_Male'] = int(demographics.orig_disabled) * int(is_male)
21
- else:
22
- interactions['OriginallyDisabled_Female'] = 0
23
- interactions['OriginallyDisabled_Male'] = 0
24
36
 
25
- # LTI interactions - used for ESRD models
26
- if demographics.lti:
37
+ # Originally ESRD interactions (ESRD V21, V24 Dialysis); looked up as DI_Originally_ESRD_*
38
+ if is_aged and demographics.orec in ('2', '3'):
39
+ interactions['Originally_ESRD_Female'] = int(is_female)
40
+ interactions['Originally_ESRD_Male'] = int(is_male)
41
+
42
+ # MCAID × sex × age interactions (ESRD V21 Dialysis and Community Graft only)
43
+ # V21 used MCAID; V24 uses FBDual/PBDual (handled in create_dual_interactions)
44
+ if mcaid:
45
+ interactions['MCAID_Female_Aged'] = int(is_female) * int(is_aged)
46
+ interactions['MCAID_Female_NonAged'] = int(is_female) * int(not is_aged)
47
+ interactions['MCAID_Male_Aged'] = int(is_male) * int(is_aged)
48
+ interactions['MCAID_Male_NonAged'] = int(is_male) * int(not is_aged)
49
+
50
+ # LTI interactions for ESRD models
51
+ if lti:
52
+ # ESRD V24 Dialysis: looked up as DI_LTI_Aged, DI_LTI_NonAged
27
53
  interactions['LTI_Aged'] = int(is_aged)
28
54
  interactions['LTI_NonAged'] = int(not is_aged)
29
- else:
30
- interactions['LTI_Aged'] = 0
31
- interactions['LTI_NonAged'] = 0
32
-
55
+ # ESRD V24 Graft Institutional: looked up WITHOUT prefix as LTI_GE65, LTI_LT65
56
+ interactions['LTI_GE65'] = int(is_aged)
57
+ interactions['LTI_LT65'] = int(not is_aged)
58
+
59
+ # LTIMCAID for V24, V28 Institutional model; looked up as INS_LTIMCAID
60
+ if lti and mcaid:
61
+ interactions['LTIMCAID'] = lti * mcaid
62
+
63
+ # New Enrollee interactions for V24, V28, ESRD V21, ESRD V24
33
64
  nemcaid = False
34
- if demographics.new_enrollee and demographics.dual_elgbl_cd in {'01', '02', '03', '04', '05', '06', '08'}:
65
+ if demographics.new_enrollee and demographics.dual_elgbl_cd in {
66
+ '01', '02', '03', '04', '05', '06', '08'
67
+ }:
35
68
  nemcaid = True
36
- ne_origds = int(demographics.age >= 65 and (demographics.orec is not None and demographics.orec == "1"))
37
-
38
- fbd = demographics.fbd
69
+ ne_origds = int(
70
+ demographics.age >= 65 and
71
+ demographics.orec is not None and
72
+ demographics.orec == "1"
73
+ )
39
74
 
40
- # Four mutually exclusive groups
75
+ # V24, V28, ESRD V21: MCAID/NMCAID style; looked up with NE_ or SNPNE_ prefix
41
76
  interactions.update({
42
77
  f'NMCAID_NORIGDIS_{demographics.category}': int(not nemcaid and not ne_origds),
43
78
  f'MCAID_NORIGDIS_{demographics.category}': int(nemcaid and not ne_origds),
44
79
  f'NMCAID_ORIGDIS_{demographics.category}': int(not nemcaid and ne_origds),
45
80
  f'MCAID_ORIGDIS_{demographics.category}': int(nemcaid and ne_origds),
81
+ })
82
+
83
+ # ESRD V24: FBD/ND_PBD style; looked up with DNE_ or GNE_ prefix
84
+ interactions.update({
46
85
  f'FBD_NORIGDIS_{demographics.category}': int(fbd and not ne_origds),
47
86
  f'FBD_ORIGDIS_{demographics.category}': int(fbd and ne_origds),
48
87
  f'ND_PBD_NORIGDIS_{demographics.category}': int(not fbd and not ne_origds),
49
88
  f'ND_PBD_ORIGDIS_{demographics.category}': int(not fbd and ne_origds)
50
89
  })
51
90
 
52
- # output only non-zero interactions
91
+ # Functioning Graft Duration "transplant bumps" for ESRD models
92
+ # All looked up WITHOUT prefix - they match directly by name
93
+ if graft_months and graft_months >= 4:
94
+ is_dur4_9 = (4 <= graft_months <= 9)
95
+ is_dur10pl = (graft_months >= 10)
96
+
97
+ # ESRD V21: simple age-based bumps (GE65_DUR4_9, LT65_DUR4_9, etc.)
98
+ if is_dur4_9:
99
+ interactions['GE65_DUR4_9'] = int(is_aged)
100
+ interactions['LT65_DUR4_9'] = int(not is_aged)
101
+ if is_dur10pl:
102
+ interactions['GE65_DUR10PL'] = int(is_aged)
103
+ interactions['LT65_DUR10PL'] = int(not is_aged)
104
+
105
+ # ESRD V24: FGC (Community) / FGI (Institutional) stratified by dual status
106
+ if not fbd:
107
+ # Non-Dual and Partial Benefit Dual (ND_PBD)
108
+ if is_dur4_9:
109
+ interactions.update({
110
+ 'FGC_GE65_DUR4_9_ND_PBD': int(is_aged) * int(not lti),
111
+ 'FGC_LT65_DUR4_9_ND_PBD': int(not is_aged) * int(not lti),
112
+ 'FGI_GE65_DUR4_9_ND_PBD': int(is_aged) * lti,
113
+ 'FGI_LT65_DUR4_9_ND_PBD': int(not is_aged) * lti,
114
+ })
115
+ if is_dur10pl:
116
+ interactions.update({
117
+ 'FGC_GE65_DUR10PL_ND_PBD': int(is_aged) * int(not lti),
118
+ 'FGC_LT65_DUR10PL_ND_PBD': int(not is_aged) * int(not lti),
119
+ 'FGI_GE65_DUR10PL_ND_PBD': int(is_aged) * lti,
120
+ 'FGI_LT65_DUR10PL_ND_PBD': int(not is_aged) * lti,
121
+ })
122
+ # Extra PBD flag for Partial Benefit Dual members
123
+ if pbd:
124
+ interactions.update({
125
+ 'FGC_PBD_GE65_flag': int(is_aged) * int(not lti),
126
+ 'FGC_PBD_LT65_flag': int(not is_aged) * int(not lti),
127
+ 'FGI_PBD_GE65_flag': int(is_aged) * lti,
128
+ 'FGI_PBD_LT65_flag': int(not is_aged) * lti,
129
+ })
130
+ else:
131
+ # Full Benefit Dual (FBD)
132
+ if is_dur4_9:
133
+ interactions.update({
134
+ 'FGC_GE65_DUR4_9_FBD': int(is_aged) * int(not lti),
135
+ 'FGC_LT65_DUR4_9_FBD': int(not is_aged) * int(not lti),
136
+ 'FGI_GE65_DUR4_9_FBD': int(is_aged) * lti,
137
+ 'FGI_LT65_DUR4_9_FBD': int(not is_aged) * lti,
138
+ })
139
+ if is_dur10pl:
140
+ interactions.update({
141
+ 'FGC_GE65_DUR10PL_FBD': int(is_aged) * int(not lti),
142
+ 'FGC_LT65_DUR10PL_FBD': int(not is_aged) * int(not lti),
143
+ 'FGI_GE65_DUR10PL_FBD': int(is_aged) * lti,
144
+ 'FGI_LT65_DUR10PL_FBD': int(not is_aged) * lti,
145
+ })
146
+
147
+ # Output only non-zero interactions
53
148
  interactions = {k: v for k, v in interactions.items() if v > 0}
54
149
 
55
150
  return interactions
@@ -214,7 +309,7 @@ def create_disease_interactions(model_name: ModelName,
214
309
  'HCC85_gRenal_V24': diagnostic_cats['CHF'] * diagnostic_cats['RENAL_V24'],
215
310
  'gCopdCF_CARD_RESP_FAIL': diagnostic_cats['gCopdCF'] * diagnostic_cats['CARD_RESP_FAIL'],
216
311
  'HCC85_HCC96': int('85' in hcc_set) * int('96' in hcc_set),
217
- 'gSubstanceAbuse_gPsych': diagnostic_cats['gSubstanceUseDisorder_V24'] * diagnostic_cats['gPsychiatric_V24'],
312
+ 'gSubstanceUseDisorder_gPsych': diagnostic_cats['gSubstanceUseDisorder_V24'] * diagnostic_cats['gPsychiatric_V24'],
218
313
  'SEPSIS_PRESSURE_ULCER': diagnostic_cats['SEPSIS'] * diagnostic_cats['PRESSURE_ULCER'],
219
314
  'SEPSIS_ARTIF_OPENINGS': diagnostic_cats['SEPSIS'] * int('188' in hcc_set),
220
315
  'ART_OPENINGS_PRESS_ULCER': int('188' in hcc_set) * diagnostic_cats['PRESSURE_ULCER'],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hccinfhir
3
- Version: 0.2.5
3
+ Version: 0.2.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
@@ -8,12 +8,12 @@ hccinfhir/extractor_837.py,sha256=fGsvBTWIj9dsHLGGR67AdlYDSsFi5qnSVlTgwkL1f-E,15
8
8
  hccinfhir/extractor_fhir.py,sha256=wUN3vTm1oTZ-KvfcDebnpQMxAC-7YlRKv12Wrv3p85A,8490
9
9
  hccinfhir/filter.py,sha256=j_yD2g6RBXVUV9trKkWzsQ35x3fRvfKUPvEXKUefI64,2007
10
10
  hccinfhir/hccinfhir.py,sha256=NydnH3WBvuyskn76hY70LpUS6XuIEoax_kip1mgfpHw,11225
11
- hccinfhir/model_calculate.py,sha256=_TUWNVUsBym0pre3wltXvRuipQaONQ0QBfWPFNAeDsQ,8347
12
- hccinfhir/model_coefficients.py,sha256=5n3QzHX6FJ3MlO0cV9NS7Bqt-lxzVvT_M3zFaWq6Gng,4685
11
+ hccinfhir/model_calculate.py,sha256=32JOFItbEQ5OAGPrvfLnQSgW2aBiZ-JCS7ixEB6TAZY,8429
12
+ hccinfhir/model_coefficients.py,sha256=PGZDAFRyz7asT0epl4xTatNCsuzYaISdbXEHU2wQ27U,5504
13
13
  hccinfhir/model_demographics.py,sha256=nImKtJCq1HkR9w2GU8aikybJFgow71CPufBRV8Jn7fM,8932
14
14
  hccinfhir/model_dx_to_cc.py,sha256=Yjc6xKI-jMXsbOzS_chc4NI15Bwagb7BwZZ8cKQaTbk,1540
15
15
  hccinfhir/model_hierarchies.py,sha256=cboUnSHZZfOxA8QZKV4QIE-32duElssML32OqYT-65g,1542
16
- hccinfhir/model_interactions.py,sha256=g6jK27Xu8RQUHS3lk4sk2v6w6wqd52mdbGn0BsnR7Pk,21394
16
+ hccinfhir/model_interactions.py,sha256=prguJoOWBIO97UEpD0njXPvYM6-hoNjBIFYxDOxkLt0,25816
17
17
  hccinfhir/samples.py,sha256=2VSWS81cv9EnaHqK7sd6CjwG6FUI9E--5wHgD000REI,9952
18
18
  hccinfhir/utils.py,sha256=WQ2atW0CrdX7sAz_YRLeY4JD-CuH0o-WRusQ_xVVfgY,12152
19
19
  hccinfhir/data/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
@@ -29,8 +29,8 @@ hccinfhir/data/ra_eligible_cpt_hcpcs_2024.csv,sha256=CawKImfCb8fFMDbWwqvNLRyRAda
29
29
  hccinfhir/data/ra_eligible_cpt_hcpcs_2025.csv,sha256=-tMvv2su5tsSbGUh6fZZCMUEkXInBpcTtbUCi2o_UwI,40359
30
30
  hccinfhir/data/ra_eligible_cpt_hcpcs_2026.csv,sha256=EYGN7k_rgCpJe59lL_yNInUcCkdETDWGSFTXII3LZ0Y,40497
31
31
  hccinfhir/data/ra_hierarchies_2025.csv,sha256=HQSPNloe6mvvwMgv8ZwYAfWKkT2b2eUvm4JQy6S_mVQ,13045
32
- hccinfhir/data/ra_hierarchies_2026.csv,sha256=A6ZQZb0rpRWrySBB_KA5S4PGtMxWuzB2guU3aBE09v0,19596
33
- hccinfhir/data/ra_labels_2026.csv,sha256=YstfP7s-3ZwjP4I_GYPPj3_yn-PQK3Q0Q_MVYZhsfjY,50248
32
+ hccinfhir/data/ra_hierarchies_2026.csv,sha256=pKevSx-dYfLyO-Leruh2AFLn5uO4y49O9EOr-O6-cbY,19595
33
+ hccinfhir/data/ra_labels_2026.csv,sha256=P-Ym0np06E_CxwELdBGZZ7j5NwhXLsHoRPnp3jeYWn4,50248
34
34
  hccinfhir/sample_files/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
35
35
  hccinfhir/sample_files/sample_834_01.txt,sha256=J2HMXfY6fAFpV36rvLQ3QymRRS2TPqf3TQY6CNS7TrE,1627
36
36
  hccinfhir/sample_files/sample_834_02.txt,sha256=vSvjM69kKfOW9e-8dvlO9zDcRPpOD7LmekLu68z4aB4,926
@@ -55,7 +55,7 @@ hccinfhir/sample_files/sample_eob_1.json,sha256=_NGSVR2ysFpx-DcTvyga6dFCzhQ8Vi9f
55
55
  hccinfhir/sample_files/sample_eob_2.json,sha256=FcnJcx0ApOczxjJ_uxVLzCep9THfNf4xs9Yf7hxk8e4,1769
56
56
  hccinfhir/sample_files/sample_eob_200.ndjson,sha256=CxpjeQ1DCMUzZILaM68UEhfxO0p45YGhDDoCZeq8PxU,1917986
57
57
  hccinfhir/sample_files/sample_eob_3.json,sha256=4BW4wOMBEEU9RDfJR15rBEvk0KNHyuMEh3e055y87Hc,2306
58
- hccinfhir-0.2.5.dist-info/METADATA,sha256=fjqK-VAKUQ_zblUqz4p8qddjSZje16tYVnPGEYyzw4Q,37381
59
- hccinfhir-0.2.5.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
60
- hccinfhir-0.2.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
61
- hccinfhir-0.2.5.dist-info/RECORD,,
58
+ hccinfhir-0.2.7.dist-info/METADATA,sha256=d52fnw3hxrWH7BLxEtkB-aefn4K6bo0g6JjLivHTBFM,37381
59
+ hccinfhir-0.2.7.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
60
+ hccinfhir-0.2.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
61
+ hccinfhir-0.2.7.dist-info/RECORD,,