hccinfhir 0.2.0__py3-none-any.whl → 0.2.1__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/constants.py ADDED
@@ -0,0 +1,240 @@
1
+ """
2
+ CMS Risk Adjustment Domain Constants
3
+
4
+ This module contains constants used across the HCC risk adjustment system,
5
+ including dual eligibility codes, OREC/CREC values, and state-specific mappings.
6
+
7
+ References:
8
+ - CMS Rate Announcement and Call Letter
9
+ - Medicare Advantage Enrollment and Disenrollment Guidance
10
+ - X12 834 Implementation Guides
11
+ """
12
+
13
+ from typing import Set, Dict
14
+
15
+ # =============================================================================
16
+ # DUAL ELIGIBILITY CODES
17
+ # =============================================================================
18
+ # CMS Dual Eligibility Status Codes (Medicare + Medicaid)
19
+ # Used in coefficient prefix selection (CNA_, CFA_, CPA_, etc.)
20
+
21
+ VALID_DUAL_CODES: Set[str] = {'00', '01', '02', '03', '04', '05', '06', '08'}
22
+
23
+ # Non-Dual Eligible
24
+ NON_DUAL_CODE: str = '00'
25
+
26
+ # Full Benefit Dual Eligible (receive both Medicare and full Medicaid benefits)
27
+ # Uses CFA_ (Community, Full Benefit Dual, Aged) or CFD_ (Disabled) prefixes
28
+ FULL_BENEFIT_DUAL_CODES: Set[str] = {
29
+ '02', # QMB Plus (Qualified Medicare Beneficiary Plus)
30
+ '04', # SLMB Plus (Specified Low-Income Medicare Beneficiary Plus)
31
+ '08', # Other Full Benefit Dual Eligible
32
+ }
33
+
34
+ # Partial Benefit Dual Eligible (Medicare + limited Medicaid)
35
+ # Uses CPA_ (Community, Partial Benefit Dual, Aged) or CPD_ (Disabled) prefixes
36
+ PARTIAL_BENEFIT_DUAL_CODES: Set[str] = {
37
+ '01', # QMB Only
38
+ '03', # SLMB Only
39
+ '05', # QDWI (Qualified Disabled and Working Individual)
40
+ '06', # QI (Qualifying Individual)
41
+ }
42
+
43
+ # =============================================================================
44
+ # OREC - Original Reason for Entitlement Code
45
+ # =============================================================================
46
+ # Determines if beneficiary has ESRD and affects coefficient prefix selection
47
+
48
+ VALID_OREC_VALUES: Set[str] = {'0', '1', '2', '3'}
49
+
50
+ OREC_DESCRIPTIONS: Dict[str, str] = {
51
+ '0': 'Old Age and Survivors Insurance (OASI)',
52
+ '1': 'Disability Insurance Benefits (DIB)',
53
+ '2': 'ESRD - End-Stage Renal Disease',
54
+ '3': 'DIB and ESRD',
55
+ }
56
+
57
+ # OREC codes indicating ESRD status (per CMS documentation)
58
+ OREC_ESRD_CODES: Set[str] = {'2', '3'}
59
+
60
+ # =============================================================================
61
+ # CREC - Current Reason for Entitlement Code
62
+ # =============================================================================
63
+ # Current entitlement status (may differ from OREC)
64
+
65
+ VALID_CREC_VALUES: Set[str] = {'0', '1', '2', '3'}
66
+
67
+ CREC_DESCRIPTIONS: Dict[str, str] = {
68
+ '0': 'Old Age and Survivors Insurance (OASI)',
69
+ '1': 'Disability Insurance Benefits (DIB)',
70
+ '2': 'ESRD - End-Stage Renal Disease',
71
+ '3': 'DIB and ESRD',
72
+ }
73
+
74
+ # CREC codes indicating ESRD status
75
+ CREC_ESRD_CODES: Set[str] = {'2', '3'}
76
+
77
+ # =============================================================================
78
+ # COEFFICIENT PREFIX GROUPS
79
+ # =============================================================================
80
+ # Used for prefix_override logic in model_demographics.py
81
+
82
+ # ESRD model prefixes
83
+ ESRD_PREFIXES: Set[str] = {'DI_', 'DNE_', 'GI_', 'GNE_', 'GFPA_', 'GFPN_', 'GNPA_', 'GNPN_'}
84
+
85
+ # CMS-HCC new enrollee prefixes
86
+ NEW_ENROLLEE_PREFIXES: Set[str] = {'NE_', 'SNPNE_', 'DNE_', 'GNE_'}
87
+
88
+ # CMS-HCC community prefixes
89
+ COMMUNITY_PREFIXES: Set[str] = {'CNA_', 'CND_', 'CFA_', 'CFD_', 'CPA_', 'CPD_'}
90
+
91
+ # Institutionalized prefixes
92
+ INSTITUTIONAL_PREFIXES: Set[str] = {'INS_', 'GI_'}
93
+
94
+ # Full Benefit Dual prefixes
95
+ FULL_BENEFIT_DUAL_PREFIXES: Set[str] = {'CFA_', 'CFD_', 'GFPA_', 'GFPN_'}
96
+
97
+ # Partial Benefit Dual prefixes
98
+ PARTIAL_BENEFIT_DUAL_PREFIXES: Set[str] = {'CPA_', 'CPD_'}
99
+
100
+ # Non-Dual prefixes
101
+ NON_DUAL_PREFIXES: Set[str] = {'CNA_', 'CND_', 'GNPA_', 'GNPN_'}
102
+
103
+ # =============================================================================
104
+ # DEMOGRAPHIC CODES
105
+ # =============================================================================
106
+
107
+ VALID_SEX_CODES: Set[str] = {'M', 'F'}
108
+
109
+ # X12 834 Gender Code mappings
110
+ X12_SEX_CODE_MAPPING: Dict[str, str] = {
111
+ 'M': 'M',
112
+ 'F': 'F',
113
+ '1': 'M', # X12 numeric code
114
+ '2': 'F', # X12 numeric code
115
+ }
116
+
117
+ # =============================================================================
118
+ # X12 834 MAINTENANCE TYPE CODES
119
+ # =============================================================================
120
+ # INS03 - Maintenance Type Code
121
+
122
+ MAINTENANCE_TYPE_CHANGE: str = '001'
123
+ MAINTENANCE_TYPE_ADD: str = '021'
124
+ MAINTENANCE_TYPE_CANCEL: str = '024'
125
+ MAINTENANCE_TYPE_REINSTATE: str = '025'
126
+
127
+ MAINTENANCE_TYPE_DESCRIPTIONS: Dict[str, str] = {
128
+ '001': 'Change',
129
+ '021': 'Addition',
130
+ '024': 'Cancellation/Termination',
131
+ '025': 'Reinstatement',
132
+ }
133
+
134
+ # =============================================================================
135
+ # STATE-SPECIFIC MAPPINGS
136
+ # =============================================================================
137
+
138
+ # -----------------------------------------------------------------------------
139
+ # California DHCS Medi-Cal Aid Codes
140
+ # -----------------------------------------------------------------------------
141
+ # Maps California-specific aid codes to CMS dual eligibility codes
142
+ # Source: California DHCS 834 Implementation Guide
143
+
144
+ MEDI_CAL_AID_CODES: Dict[str, str] = {
145
+ # Full Benefit Dual (QMB Plus, SLMB Plus)
146
+ '4N': '02', # QMB Plus - Aged
147
+ '4P': '02', # QMB Plus - Disabled
148
+ '5B': '04', # SLMB Plus - Aged
149
+ '5D': '04', # SLMB Plus - Disabled
150
+
151
+ # Partial Benefit Dual (QMB Only, SLMB Only, QI)
152
+ '4M': '01', # QMB Only - Aged
153
+ '4O': '01', # QMB Only - Disabled
154
+ '5A': '03', # SLMB Only - Aged
155
+ '5C': '03', # SLMB Only - Disabled
156
+ '5E': '06', # QI - Aged
157
+ '5F': '06', # QI - Disabled
158
+ }
159
+
160
+ # -----------------------------------------------------------------------------
161
+ # Medicare Status Code Mappings
162
+ # -----------------------------------------------------------------------------
163
+ # Maps Medicare status codes (from various sources) to CMS dual eligibility codes
164
+ # Used in X12 834 REF*ABB segment and other payer files
165
+
166
+ MEDICARE_STATUS_CODE_MAPPING: Dict[str, str] = {
167
+ # QMB - Qualified Medicare Beneficiary
168
+ 'QMB': '01', # QMB Only (Partial)
169
+ 'QMBONLY': '01',
170
+ 'QMBPLUS': '02', # QMB Plus (Full Benefit)
171
+ 'QMB+': '02',
172
+
173
+ # SLMB - Specified Low-Income Medicare Beneficiary
174
+ 'SLMB': '03', # SLMB Only (Partial)
175
+ 'SLMBONLY': '03',
176
+ 'SLMBPLUS': '04', # SLMB Plus (Full Benefit)
177
+ 'SLMB+': '04',
178
+
179
+ # Other dual eligibility programs
180
+ 'QDWI': '05', # Qualified Disabled and Working Individual
181
+ 'QI': '06', # Qualifying Individual
182
+ 'QI1': '06',
183
+ 'FBDE': '08', # Full Benefit Dual Eligible (Other)
184
+ 'OTHERFULL': '08',
185
+ }
186
+
187
+ # =============================================================================
188
+ # HELPER FUNCTIONS
189
+ # =============================================================================
190
+
191
+ def is_full_benefit_dual(dual_code: str) -> bool:
192
+ """Check if dual eligibility code is Full Benefit Dual"""
193
+ return dual_code in FULL_BENEFIT_DUAL_CODES
194
+
195
+ def is_partial_benefit_dual(dual_code: str) -> bool:
196
+ """Check if dual eligibility code is Partial Benefit Dual"""
197
+ return dual_code in PARTIAL_BENEFIT_DUAL_CODES
198
+
199
+ def is_esrd_by_orec(orec: str) -> bool:
200
+ """Check if OREC indicates ESRD status"""
201
+ return orec in OREC_ESRD_CODES
202
+
203
+ def is_esrd_by_crec(crec: str) -> bool:
204
+ """Check if CREC indicates ESRD status"""
205
+ return crec in CREC_ESRD_CODES
206
+
207
+ def normalize_medicare_status_code(status: str) -> str:
208
+ """Normalize Medicare status code (uppercase, no spaces/hyphens)"""
209
+ if not status:
210
+ return ''
211
+ return status.upper().replace(' ', '').replace('-', '')
212
+
213
+ def map_medicare_status_to_dual_code(status: str) -> str:
214
+ """Map Medicare status code to dual eligibility code
215
+
216
+ Args:
217
+ status: Medicare status code (e.g., 'QMB Plus', 'SLMB', 'QI')
218
+
219
+ Returns:
220
+ Dual eligibility code ('01'-'08') or '00' if not found
221
+ """
222
+ if not status:
223
+ return NON_DUAL_CODE
224
+
225
+ normalized = normalize_medicare_status_code(status)
226
+ return MEDICARE_STATUS_CODE_MAPPING.get(normalized, NON_DUAL_CODE)
227
+
228
+ def map_aid_code_to_dual_status(aid_code: str) -> str:
229
+ """Map California Medi-Cal aid code to dual eligibility code
230
+
231
+ Args:
232
+ aid_code: California aid code (e.g., '4N', '5B')
233
+
234
+ Returns:
235
+ Dual eligibility code ('01'-'08') or '00' if not found
236
+ """
237
+ if not aid_code:
238
+ return NON_DUAL_CODE
239
+
240
+ return MEDI_CAL_AID_CODES.get(aid_code, NON_DUAL_CODE)
@@ -1,29 +1,23 @@
1
- from typing import List, Optional, Dict
1
+ from typing import List, Optional, Dict, Any, Tuple
2
2
  from pydantic import BaseModel
3
3
  from datetime import datetime, date
4
4
  from hccinfhir.datamodels import Demographics, EnrollmentData
5
+ from hccinfhir.constants import (
6
+ VALID_DUAL_CODES,
7
+ FULL_BENEFIT_DUAL_CODES,
8
+ PARTIAL_BENEFIT_DUAL_CODES,
9
+ VALID_OREC_VALUES,
10
+ VALID_CREC_VALUES,
11
+ X12_SEX_CODE_MAPPING,
12
+ NON_DUAL_CODE,
13
+ map_medicare_status_to_dual_code,
14
+ map_aid_code_to_dual_status,
15
+ )
5
16
 
6
17
  TRANSACTION_TYPES = {
7
18
  "005010X220A1": "834", # Benefit Enrollment and Maintenance
8
19
  }
9
20
 
10
- # California Medi-Cal Aid Codes mapping to dual eligibility status
11
- MEDI_CAL_AID_CODES = {
12
- # Full Benefit Dual (QMB Plus, SLMB Plus)
13
- '4N': '02', # QMB Plus - Aged
14
- '4P': '02', # QMB Plus - Disabled
15
- '5B': '04', # SLMB Plus - Aged
16
- '5D': '04', # SLMB Plus - Disabled
17
-
18
- # Partial Benefit Dual (QMB Only, SLMB Only, QI)
19
- '4M': '01', # QMB Only - Aged
20
- '4O': '01', # QMB Only - Disabled
21
- '5A': '03', # SLMB Only - Aged
22
- '5C': '03', # SLMB Only - Disabled
23
- '5E': '06', # QI - Aged
24
- '5F': '06', # QI - Disabled
25
- }
26
-
27
21
  class MemberContext(BaseModel):
28
22
  """Tracks member-level data across segments within 834 transaction"""
29
23
  # Identifiers
@@ -95,7 +89,7 @@ def is_medicaid_terminated(enrollment: EnrollmentData) -> bool:
95
89
  """Check if Medicaid coverage is being terminated (maintenance type 024)"""
96
90
  return enrollment.maintenance_type == '024'
97
91
 
98
- def medicaid_status_summary(enrollment: EnrollmentData) -> Dict[str, any]:
92
+ def medicaid_status_summary(enrollment: EnrollmentData) -> Dict[str, Any]:
99
93
  """Get summary of Medicaid coverage status for monitoring
100
94
 
101
95
  Args:
@@ -157,45 +151,28 @@ def get_segment_value(segment: List[str], index: int, default: Optional[str] = N
157
151
  pass
158
152
  return default
159
153
 
160
- def map_medicare_status_to_dual_code(status: Optional[str]) -> Optional[str]:
161
- """Map Medicare status codes to dual eligibility codes
154
+ def parse_composite_ref_value(value: str) -> str:
155
+ """Parse X12 composite element format: 'qualifier;id;...'
162
156
 
163
- California Medi-Cal uses these status codes:
164
- - QMB = Qualified Medicare Beneficiary
165
- - QMBPLUS = QMB Plus (Full Benefit)
166
- - SLMB = Specified Low-Income Medicare Beneficiary
167
- - SLMBPLUS = SLMB Plus (Full Benefit)
168
- - QI = Qualifying Individual
169
- - QDWI = Qualified Disabled Working Individual
157
+ X12 uses semicolons to separate sub-elements within a composite data element.
158
+ Example: REF*23*9;20061234; where 9 is the ID type qualifier
159
+
160
+ Args:
161
+ value: Raw REF segment value (e.g., '9;20061234;' or '20061234')
162
+
163
+ Returns:
164
+ The last non-empty sub-element (the actual ID)
170
165
  """
171
- if not status:
172
- return None
166
+ if not value:
167
+ return value
173
168
 
174
- status_upper = status.upper().replace(' ', '').replace('-', '')
175
-
176
- mapping = {
177
- 'QMB': '01', # QMB Only (Partial)
178
- 'QMBONLY': '01',
179
- 'QMBPLUS': '02', # QMB Plus (Full Benefit)
180
- 'QMB+': '02',
181
- 'SLMB': '03', # SLMB Only (Partial)
182
- 'SLMBONLY': '03',
183
- 'SLMBPLUS': '04', # SLMB Plus (Full Benefit)
184
- 'SLMB+': '04',
185
- 'QDWI': '05',
186
- 'QI': '06',
187
- 'QI1': '06',
188
- 'FBDE': '08', # Full Benefit Dual Eligible (Other)
189
- 'OTHERFULL': '08',
190
- }
169
+ if ';' in value:
170
+ # Split by semicolon and filter out empty parts
171
+ parts = [p for p in value.split(';') if p]
172
+ return parts[-1] if parts else value
191
173
 
192
- return mapping.get(status_upper)
174
+ return value
193
175
 
194
- def map_aid_code_to_dual_status(aid_code: Optional[str]) -> Optional[str]:
195
- """Map California Medi-Cal aid code to dual eligibility status"""
196
- if not aid_code:
197
- return None
198
- return MEDI_CAL_AID_CODES.get(aid_code)
199
176
 
200
177
  def determine_dual_status(member: MemberContext) -> str:
201
178
  """Intelligently derive dual eligibility code from available data
@@ -208,19 +185,19 @@ def determine_dual_status(member: MemberContext) -> str:
208
185
  5. Default to non-dual ('00')
209
186
  """
210
187
  # Priority 1: Explicit dual_elgbl_cd
211
- if member.dual_elgbl_cd and member.dual_elgbl_cd in ['01','02','03','04','05','06','08']:
188
+ if member.dual_elgbl_cd and member.dual_elgbl_cd in VALID_DUAL_CODES:
212
189
  return member.dual_elgbl_cd
213
190
 
214
191
  # Priority 2: California aid code mapping
215
192
  if member.medi_cal_aid_code:
216
193
  dual_code = map_aid_code_to_dual_status(member.medi_cal_aid_code)
217
- if dual_code:
194
+ if dual_code != NON_DUAL_CODE:
218
195
  return dual_code
219
196
 
220
197
  # Priority 3: Medicare status code
221
198
  if member.medicare_status_code:
222
199
  dual_code = map_medicare_status_to_dual_code(member.medicare_status_code)
223
- if dual_code:
200
+ if dual_code != NON_DUAL_CODE:
224
201
  return dual_code
225
202
 
226
203
  # Priority 4: Both Medicare and Medicaid coverage present
@@ -229,9 +206,9 @@ def determine_dual_status(member: MemberContext) -> str:
229
206
  return '08'
230
207
 
231
208
  # Default: Non-dual
232
- return '00'
209
+ return NON_DUAL_CODE
233
210
 
234
- def classify_dual_benefit_level(dual_code: str) -> tuple[bool, bool]:
211
+ def classify_dual_benefit_level(dual_code: str) -> Tuple[bool, bool]:
235
212
  """Classify as Full Benefit Dual (FBD) or Partial Benefit Dual (PBD)
236
213
 
237
214
  Full Benefit Dual codes: 02, 04, 08
@@ -242,11 +219,8 @@ def classify_dual_benefit_level(dual_code: str) -> tuple[bool, bool]:
242
219
  - Uses CPA_ (Community, Partial Benefit Dual, Aged) prefix
243
220
  - Uses CPD_ (Community, Partial Benefit Dual, Disabled) prefix
244
221
  """
245
- full_benefit_codes = {'02', '04', '08'}
246
- partial_benefit_codes = {'01', '03', '05', '06'}
247
-
248
- is_fbd = dual_code in full_benefit_codes
249
- is_pbd = dual_code in partial_benefit_codes
222
+ is_fbd = dual_code in FULL_BENEFIT_DUAL_CODES
223
+ is_pbd = dual_code in PARTIAL_BENEFIT_DUAL_CODES
250
224
 
251
225
  return is_fbd, is_pbd
252
226
 
@@ -284,7 +258,7 @@ def parse_834_enrollment(segments: List[List[str]]) -> List[EnrollmentData]:
284
258
  enrollments = []
285
259
  member = MemberContext()
286
260
 
287
- for i, segment in enumerate(segments):
261
+ for segment in segments:
288
262
  if len(segment) < 2:
289
263
  continue
290
264
 
@@ -330,11 +304,11 @@ def parse_834_enrollment(segments: List[List[str]]) -> List[EnrollmentData]:
330
304
 
331
305
  # Medicaid Identifiers
332
306
  elif qualifier == '1D': # Medicaid/Recipient ID
333
- member.medicaid_id = value
307
+ member.medicaid_id = parse_composite_ref_value(value)
334
308
  member.has_medicaid = True
335
309
  elif qualifier == '23': # Medicaid Recipient ID (alternative)
336
310
  if not member.medicaid_id:
337
- member.medicaid_id = value
311
+ member.medicaid_id = parse_composite_ref_value(value)
338
312
  member.has_medicaid = True
339
313
 
340
314
  # California Medi-Cal Specific
@@ -345,13 +319,13 @@ def parse_834_enrollment(segments: List[List[str]]) -> List[EnrollmentData]:
345
319
 
346
320
  # Custom dual eligibility indicators
347
321
  elif qualifier == 'F5': # Dual Eligibility Code (custom)
348
- if value in ['01','02','03','04','05','06','08']:
322
+ if value in VALID_DUAL_CODES:
349
323
  member.dual_elgbl_cd = value
350
324
  elif qualifier == 'DX': # OREC (custom)
351
- if value in ['0','1','2','3']:
325
+ if value in VALID_OREC_VALUES:
352
326
  member.orec = value
353
327
  elif qualifier == 'DY': # CREC (custom)
354
- if value in ['0','1','2','3']:
328
+ if value in VALID_CREC_VALUES:
355
329
  member.crec = value
356
330
  elif qualifier == 'EJ': # Low Income Subsidy indicator
357
331
  member.low_income = (value.upper() in ['Y', 'YES', '1', 'TRUE'])
@@ -376,8 +350,8 @@ def parse_834_enrollment(segments: List[List[str]]) -> List[EnrollmentData]:
376
350
 
377
351
  # DMG03 = Gender Code
378
352
  sex = get_segment_value(segment, 3)
379
- if sex in ['M', 'F', '1', '2']:
380
- member.sex = 'M' if sex in ['M', '1'] else 'F'
353
+ if sex in X12_SEX_CODE_MAPPING:
354
+ member.sex = X12_SEX_CODE_MAPPING[sex]
381
355
 
382
356
  # ===== DTP - Date Time Periods =====
383
357
  elif seg_id == 'DTP' and len(segment) >= 4:
@@ -440,6 +414,13 @@ def parse_834_enrollment(segments: List[List[str]]) -> List[EnrollmentData]:
440
414
  member.has_medicare = True
441
415
  member.has_medicaid = True
442
416
 
417
+ # Detect LTI (Long Term Institutionalized)
418
+ if any(keyword in combined for keyword in [
419
+ 'LTC', 'LONG TERM CARE', 'LONG-TERM CARE', 'NURSING HOME',
420
+ 'SKILLED NURSING', 'SNF', 'INSTITUTIONALIZED'
421
+ ]):
422
+ member.lti = True
423
+
443
424
  # Don't forget last member
444
425
  if member.member_id or member.has_medicare or member.has_medicaid:
445
426
  enrollments.append(create_enrollment_data(member))
@@ -1,4 +1,4 @@
1
- from typing import List, Optional, Dict
1
+ from typing import List, Optional, Dict, Tuple
2
2
  from pydantic import BaseModel
3
3
  from hccinfhir.datamodels import ServiceLevelData
4
4
 
@@ -65,7 +65,7 @@ def parse_diagnosis_codes(segment: List[str]) -> Dict[str, str]:
65
65
  dx_lookup[str(pos)] = code
66
66
  return dx_lookup
67
67
 
68
- def process_service_line(segments: List[List[str]], start_index: int) -> tuple[Optional[str], Optional[str]]:
68
+ def process_service_line(segments: List[List[str]], start_index: int) -> Tuple[Optional[str], Optional[str]]:
69
69
  """Extract NDC and service date from service line segments"""
70
70
  ndc = None
71
71
  service_date = None
@@ -1,4 +1,4 @@
1
- from typing import Dict, Tuple, Optional
1
+ from typing import Dict, Tuple, Optional, Set
2
2
  from hccinfhir.datamodels import ModelName, Demographics, PrefixOverride
3
3
 
4
4
  def get_coefficent_prefix(demographics: Demographics,
@@ -65,7 +65,7 @@ def get_coefficent_prefix(demographics: Demographics,
65
65
 
66
66
 
67
67
  def apply_coefficients(demographics: Demographics,
68
- hcc_set: set[str],
68
+ hcc_set: Set[str],
69
69
  interactions: dict,
70
70
  model_name: ModelName,
71
71
  coefficients: Dict[Tuple[str, ModelName], float],
@@ -1,5 +1,18 @@
1
1
  from typing import Union, Optional
2
2
  from hccinfhir.datamodels import Demographics, PrefixOverride
3
+ from hccinfhir.constants import (
4
+ FULL_BENEFIT_DUAL_CODES,
5
+ PARTIAL_BENEFIT_DUAL_CODES,
6
+ OREC_ESRD_CODES,
7
+ CREC_ESRD_CODES,
8
+ ESRD_PREFIXES,
9
+ NEW_ENROLLEE_PREFIXES,
10
+ COMMUNITY_PREFIXES,
11
+ INSTITUTIONAL_PREFIXES,
12
+ FULL_BENEFIT_DUAL_PREFIXES,
13
+ PARTIAL_BENEFIT_DUAL_PREFIXES,
14
+ NON_DUAL_PREFIXES,
15
+ )
3
16
 
4
17
  def categorize_demographics(age: Union[int, float],
5
18
  sex: str,
@@ -75,56 +88,40 @@ def categorize_demographics(age: Union[int, float],
75
88
  disabled = age < 65 and (orec is not None and orec != "0")
76
89
  orig_disabled = (orec is not None and orec == '1') and not disabled
77
90
 
78
- # Reference: https://resdac.org/cms-data/variables/medicare-medicaid-dual-eligibility-code-january
79
- # Full benefit dual codes
80
- fbd_codes = {'02', '04', '08'}
81
-
82
- # Partial benefit dual codes
83
- pbd_codes = {'01', '03', '05', '06'}
84
-
85
- is_fbd = dual_elgbl_cd in fbd_codes
86
- is_pbd = dual_elgbl_cd in pbd_codes
91
+ # Reference: https://resdac.org/cms-data/variables/medicare-medicaid-dual-eligibility-code-january
92
+ is_fbd = dual_elgbl_cd in FULL_BENEFIT_DUAL_CODES
93
+ is_pbd = dual_elgbl_cd in PARTIAL_BENEFIT_DUAL_CODES
87
94
 
88
- esrd_orec = orec in {'2', '3', '6'}
89
- esrd_crec = crec in {'2', '3'} if crec else False
95
+ # ESRD detection from OREC/CREC (CMS official codes: 2=ESRD, 3=DIB+ESRD)
96
+ esrd_orec = orec in OREC_ESRD_CODES
97
+ esrd_crec = crec in CREC_ESRD_CODES if crec else False
90
98
  esrd = esrd_orec or esrd_crec
91
99
 
92
100
  # Override demographics based on prefix_override
93
101
  if prefix_override:
94
- # ESRD model prefixes
95
- esrd_prefixes = {'DI_', 'DNE_', 'GI_', 'GNE_', 'GFPA_', 'GFPN_', 'GNPA_', 'GNPN_'}
96
- # CMS-HCC new enrollee prefixes
97
- new_enrollee_prefixes = {'NE_', 'SNPNE_', 'DNE_', 'GNE_'}
98
- # CMS-HCC community prefixes
99
- community_prefixes = {'CNA_', 'CND_', 'CFA_', 'CFD_', 'CPA_', 'CPD_'}
100
- # Institutionalized prefix
101
- institutional_prefixes = {'INS_', 'GI_'}
102
-
103
- # TODO: RxHCC prefixes
104
-
105
102
  # Set esrd flag
106
- if prefix_override in esrd_prefixes:
103
+ if prefix_override in ESRD_PREFIXES:
107
104
  esrd = True
108
105
 
109
106
  # Set new_enrollee flag
110
- if prefix_override in new_enrollee_prefixes:
107
+ if prefix_override in NEW_ENROLLEE_PREFIXES:
111
108
  new_enrollee = True
112
- elif prefix_override in community_prefixes or prefix_override in institutional_prefixes:
109
+ elif prefix_override in COMMUNITY_PREFIXES or prefix_override in INSTITUTIONAL_PREFIXES:
113
110
  new_enrollee = False
114
111
 
115
112
  # Set dual eligibility flags based on prefix
116
- if prefix_override in {'CFA_', 'CFD_', 'GFPA_', 'GFPN_'}:
113
+ if prefix_override in FULL_BENEFIT_DUAL_PREFIXES:
117
114
  is_fbd = True
118
115
  is_pbd = False
119
- elif prefix_override in {'CPA_', 'CPD_'}:
116
+ elif prefix_override in PARTIAL_BENEFIT_DUAL_PREFIXES:
120
117
  is_fbd = False
121
118
  is_pbd = True
122
- elif prefix_override in {'CNA_', 'CND_', 'GNPA_', 'GNPN_'}:
119
+ elif prefix_override in NON_DUAL_PREFIXES:
123
120
  is_fbd = False
124
121
  is_pbd = False
125
122
 
126
123
  # Set lti flag based on prefix
127
- if prefix_override in institutional_prefixes:
124
+ if prefix_override in INSTITUTIONAL_PREFIXES:
128
125
  lti = True
129
126
 
130
127
  result_dict = {
@@ -1,7 +1,7 @@
1
1
  from hccinfhir.datamodels import Demographics, ModelName
2
- from typing import Optional
2
+ from typing import Optional, List, Set, Dict
3
3
 
4
- def has_any_hcc(hcc_list: list[str], hcc_set: set[str]) -> int:
4
+ def has_any_hcc(hcc_list: List[str], hcc_set: Set[str]) -> int:
5
5
  """Returns 1 if any HCC in the list is present, 0 otherwise"""
6
6
  return int(bool(set(hcc_list) & hcc_set))
7
7
 
@@ -81,7 +81,7 @@ def create_dual_interactions(demographics: Demographics) -> dict:
81
81
 
82
82
  return interactions
83
83
 
84
- def create_hcc_counts(hcc_set: set[str]) -> dict:
84
+ def create_hcc_counts(hcc_set: Set[str]) -> Dict:
85
85
  """Creates HCC count variables"""
86
86
  counts = {}
87
87
  hcc_count = len(hcc_set)
@@ -95,7 +95,7 @@ def create_hcc_counts(hcc_set: set[str]) -> dict:
95
95
 
96
96
  return counts
97
97
 
98
- def get_diagnostic_categories(model_name: ModelName, hcc_set: set[str]) -> dict:
98
+ def get_diagnostic_categories(model_name: ModelName, hcc_set: Set[str]) -> Dict:
99
99
  """Creates disease categories based on model version"""
100
100
  categories = {}
101
101
 
@@ -343,9 +343,9 @@ def create_disease_interactions(model_name: ModelName,
343
343
 
344
344
  return interactions
345
345
 
346
- def apply_interactions(demographics: Demographics,
347
- hcc_set: set[str],
348
- model_name: ModelName = "CMS-HCC Model V28") -> dict:
346
+ def apply_interactions(demographics: Demographics,
347
+ hcc_set: Set[str],
348
+ model_name: ModelName = "CMS-HCC Model V28") -> Dict:
349
349
  """
350
350
  Calculate HCC interactions across CMS models. Handles CMS-HCC, ESRD, and RxHCC models.
351
351
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hccinfhir
3
- Version: 0.2.0
3
+ Version: 0.2.1
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
@@ -1,18 +1,19 @@
1
1
  hccinfhir/__init__.py,sha256=CKpYTUSzZdP3s1eB74w5JTe9OS3MtcvuUkv6ymgSyic,1085
2
+ hccinfhir/constants.py,sha256=C4Vyjtzgyd4Jm2I2X6cTYQZLe-jAMC8boUcy-7OXQDQ,8473
2
3
  hccinfhir/datamodels.py,sha256=NULDYb57R61v4EklOI_AAIuC1-OkLFH1InbAad48dZM,10601
3
4
  hccinfhir/defaults.py,sha256=tMNym0R6Nr6ibKTqOu6N1vLcdekL0ZmHyDNIOCOMsP4,1292
4
5
  hccinfhir/extractor.py,sha256=xL9c2VT-e2I7_c8N8j4Og42UEgVuCzyn9WFp3ntM5Ro,1822
5
- hccinfhir/extractor_834.py,sha256=vODcD53iU5ZwQsSbBE8Gix9D-0fz-EhwkmjqRO6LML8,19541
6
- hccinfhir/extractor_837.py,sha256=D60gUFtMk2S0NrJ0iq3ENo35yIwBmBQvF5TurJgRIa8,15327
6
+ hccinfhir/extractor_834.py,sha256=dIqovUOWm_7k_c6sUqTIzQua_kTQ8dLGy3-4-LECW3Y,18855
7
+ hccinfhir/extractor_837.py,sha256=fGsvBTWIj9dsHLGGR67AdlYDSsFi5qnSVlTgwkL1f-E,15334
7
8
  hccinfhir/extractor_fhir.py,sha256=wUN3vTm1oTZ-KvfcDebnpQMxAC-7YlRKv12Wrv3p85A,8490
8
9
  hccinfhir/filter.py,sha256=j_yD2g6RBXVUV9trKkWzsQ35x3fRvfKUPvEXKUefI64,2007
9
10
  hccinfhir/hccinfhir.py,sha256=rCnExvxZGKi1vLD4cHQ0nzPAGV6e-8C15MtJ2p7zAAk,11160
10
11
  hccinfhir/model_calculate.py,sha256=KSeZjKYBCfBYYIWOIckDg941OC8050MX2F7BZ2l3V8g,7663
11
- hccinfhir/model_coefficients.py,sha256=--8Gh5gYJez1v0cBA1ZqDn0QI6On-ia1-wPr-mqbRFs,4680
12
- hccinfhir/model_demographics.py,sha256=CR4WC8XVq-CI1nYJoVFc5-KXTw-pKoVlHkHqfnXlnj0,9121
12
+ hccinfhir/model_coefficients.py,sha256=5n3QzHX6FJ3MlO0cV9NS7Bqt-lxzVvT_M3zFaWq6Gng,4685
13
+ hccinfhir/model_demographics.py,sha256=nImKtJCq1HkR9w2GU8aikybJFgow71CPufBRV8Jn7fM,8932
13
14
  hccinfhir/model_dx_to_cc.py,sha256=Yjc6xKI-jMXsbOzS_chc4NI15Bwagb7BwZZ8cKQaTbk,1540
14
15
  hccinfhir/model_hierarchies.py,sha256=cboUnSHZZfOxA8QZKV4QIE-32duElssML32OqYT-65g,1542
15
- hccinfhir/model_interactions.py,sha256=xdsTuc3ii8U_MaPpYv0SnR4eR_w72eHKUJn1mwmZHm4,21379
16
+ hccinfhir/model_interactions.py,sha256=g6jK27Xu8RQUHS3lk4sk2v6w6wqd52mdbGn0BsnR7Pk,21394
16
17
  hccinfhir/samples.py,sha256=2VSWS81cv9EnaHqK7sd6CjwG6FUI9E--5wHgD000REI,9952
17
18
  hccinfhir/utils.py,sha256=9ki4o1wXyAYYr8BR9Skkz0PKL_1H_HYNV4LalEsASE0,8260
18
19
  hccinfhir/data/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
@@ -47,7 +48,7 @@ hccinfhir/sample_files/sample_eob_1.json,sha256=_NGSVR2ysFpx-DcTvyga6dFCzhQ8Vi9f
47
48
  hccinfhir/sample_files/sample_eob_2.json,sha256=FcnJcx0ApOczxjJ_uxVLzCep9THfNf4xs9Yf7hxk8e4,1769
48
49
  hccinfhir/sample_files/sample_eob_200.ndjson,sha256=CxpjeQ1DCMUzZILaM68UEhfxO0p45YGhDDoCZeq8PxU,1917986
49
50
  hccinfhir/sample_files/sample_eob_3.json,sha256=4BW4wOMBEEU9RDfJR15rBEvk0KNHyuMEh3e055y87Hc,2306
50
- hccinfhir-0.2.0.dist-info/METADATA,sha256=bRJ_IYdfpYNRnKLYeqyhVlKTob5K69SeCiC7FGzX8vM,31674
51
- hccinfhir-0.2.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
52
- hccinfhir-0.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
53
- hccinfhir-0.2.0.dist-info/RECORD,,
51
+ hccinfhir-0.2.1.dist-info/METADATA,sha256=FrzAPidGEXS8l-1vO_QRlemPZegXavDpn--TkmwQBxY,31674
52
+ hccinfhir-0.2.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
53
+ hccinfhir-0.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
54
+ hccinfhir-0.2.1.dist-info/RECORD,,