hccinfhir 0.1.5__py3-none-any.whl → 0.1.6__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/datamodels.py CHANGED
@@ -23,6 +23,45 @@ DxCCMappingFilename = Literal[
23
23
  "ra_dx_to_cc_2026.csv"
24
24
  ]
25
25
 
26
+ PrefixOverride = Literal[
27
+ # CMS-HCC Community prefixes
28
+ "CNA_", # Community, Non-Dual, Aged
29
+ "CND_", # Community, Non-Dual, Disabled
30
+ "CFA_", # Community, Full Benefit Dual, Aged
31
+ "CFD_", # Community, Full Benefit Dual, Disabled
32
+ "CPA_", # Community, Partial Benefit Dual, Aged
33
+ "CPD_", # Community, Partial Benefit Dual, Disabled
34
+ # CMS-HCC Institutional
35
+ "INS_", # Long-Term Institutionalized
36
+ # CMS-HCC New Enrollee
37
+ "NE_", # New Enrollee
38
+ "SNPNE_", # Special Needs Plan New Enrollee
39
+ # ESRD Dialysis
40
+ "DI_", # Dialysis
41
+ "DNE_", # Dialysis New Enrollee
42
+ # ESRD Graft
43
+ "GI_", # Graft, Institutionalized
44
+ "GNE_", # Graft, New Enrollee
45
+ "GFPA_", # Graft, Full Benefit Dual, Aged
46
+ "GFPN_", # Graft, Full Benefit Dual, Non-Aged
47
+ "GNPA_", # Graft, Non-Dual, Aged
48
+ "GNPN_", # Graft, Non-Dual, Non-Aged
49
+ # ESRD Transplant
50
+ "TRANSPLANT_KIDNEY_ONLY_1M", # 1 month post-transplant
51
+ "TRANSPLANT_KIDNEY_ONLY_2M", # 2 months post-transplant
52
+ "TRANSPLANT_KIDNEY_ONLY_3M", # 3 months post-transplant
53
+ # RxHCC Community Enrollee
54
+ "Rx_CE_LowAged_", # Community Enrollee, Low Income, Aged
55
+ "Rx_CE_LowNoAged_", # Community Enrollee, Low Income, Non-Aged
56
+ "Rx_CE_NoLowAged_", # Community Enrollee, Not Low Income, Aged
57
+ "Rx_CE_NoLowNoAged_", # Community Enrollee, Not Low Income, Non-Aged
58
+ "Rx_CE_LTI_", # Community Enrollee, Long-Term Institutionalized
59
+ # RxHCC New Enrollee
60
+ "Rx_NE_Lo_", # New Enrollee, Low Income
61
+ "Rx_NE_NoLo_", # New Enrollee, Not Low Income
62
+ "Rx_NE_LTI_", # New Enrollee, Long-Term Institutionalized
63
+ ]
64
+
26
65
  class ServiceLevelData(BaseModel):
27
66
  """
28
67
  Represents standardized service-level data extracted from healthcare claims.
@@ -93,8 +132,9 @@ class RAFResult(BaseModel):
93
132
  """Risk adjustment calculation results"""
94
133
  risk_score: float = Field(..., description="Final RAF score")
95
134
  risk_score_demographics: float = Field(..., description="Demographics-only risk score")
96
- risk_score_chronic_only: float = Field(..., description="Chronic conditions risk score")
135
+ risk_score_chronic_only: float = Field(..., description="Chronic conditions risk score")
97
136
  risk_score_hcc: float = Field(..., description="HCC conditions risk score")
137
+ risk_score_payment: float = Field(..., description="Payment RAF score (adjusted for MACI, normalization, and frailty)")
98
138
  hcc_list: List[str] = Field(default_factory=list, description="List of active HCC categories")
99
139
  cc_to_dx: Dict[str, Set[str]] = Field(default_factory=dict, description="Condition categories mapped to diagnosis codes")
100
140
  coefficients: Dict[str, float] = Field(default_factory=dict, description="Applied model coefficients")
@@ -162,8 +162,8 @@ def extract_sld_fhir(eob_data: dict) -> List[ServiceLevelData]:
162
162
  eob.billablePeriod.get_service_date() if eob.billablePeriod else None),
163
163
  'place_of_service': item.locationCodeableConcept.get_code(SYSTEMS['context']['place'])
164
164
  if item.locationCodeableConcept else None,
165
- 'modifiers': [m.get_code(SYSTEMS['procedures']['hcpcs'])
166
- for m in (item.modifier or []) if m],
165
+ 'modifiers': [code for m in (item.modifier or [])
166
+ if m and (code := m.get_code(SYSTEMS['procedures']['hcpcs'])) is not None],
167
167
  'allowed_amount': next((adj.get('amount', {}).get('value')
168
168
  for adj in (item.adjudication or [])
169
169
  if any(c.get('code') == 'eligible'
hccinfhir/hccinfhir.py CHANGED
@@ -1,8 +1,8 @@
1
- from typing import List, Dict, Any, Union
1
+ from typing import List, Dict, Any, Union, Optional
2
2
  from hccinfhir.extractor import extract_sld_list
3
3
  from hccinfhir.filter import apply_filter
4
4
  from hccinfhir.model_calculate import calculate_raf
5
- from hccinfhir.datamodels import Demographics, ServiceLevelData, RAFResult, ModelName, ProcFilteringFilename, DxCCMappingFilename
5
+ from hccinfhir.datamodels import Demographics, ServiceLevelData, RAFResult, ModelName, ProcFilteringFilename, DxCCMappingFilename, PrefixOverride
6
6
  from hccinfhir.utils import load_proc_filtering, load_dx_to_cc_mapping
7
7
 
8
8
  class HCCInFHIR:
@@ -41,8 +41,12 @@ class HCCInFHIR:
41
41
  return Demographics(**demographics)
42
42
  return demographics
43
43
 
44
- def _calculate_raf_from_demographics(self, diagnosis_codes: List[str],
45
- demographics: Demographics) -> RAFResult:
44
+ def _calculate_raf_from_demographics_and_dx_codes(self, diagnosis_codes: List[str],
45
+ demographics: Demographics,
46
+ prefix_override: Optional[PrefixOverride] = None,
47
+ maci: float = 0.0,
48
+ norm_factor: float = 1.0,
49
+ frailty_score: float = 0.0) -> RAFResult:
46
50
  """Calculate RAF score using demographics data."""
47
51
  return calculate_raf(
48
52
  diagnosis_codes=diagnosis_codes,
@@ -55,22 +59,36 @@ class HCCInFHIR:
55
59
  new_enrollee=demographics.new_enrollee,
56
60
  snp=demographics.snp,
57
61
  low_income=demographics.low_income,
62
+ lti=demographics.lti,
58
63
  graft_months=demographics.graft_months,
59
- dx_to_cc_mapping=self.dx_to_cc_mapping
64
+ dx_to_cc_mapping=self.dx_to_cc_mapping,
65
+ prefix_override=prefix_override,
66
+ maci=maci,
67
+ norm_factor=norm_factor,
68
+ frailty_score=frailty_score
60
69
  )
61
70
 
62
71
  def _get_unique_diagnosis_codes(self, service_data: List[ServiceLevelData]) -> List[str]:
63
72
  """Extract unique diagnosis codes from service level data."""
64
73
  return list({code for sld in service_data for code in sld.claim_diagnosis_codes})
65
74
 
66
- def run(self, eob_list: List[Dict[str, Any]],
67
- demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
75
+ def run(self, eob_list: List[Dict[str, Any]],
76
+ demographics: Union[Demographics, Dict[str, Any]],
77
+ prefix_override: Optional[PrefixOverride] = None,
78
+ maci: float = 0.0,
79
+ norm_factor: float = 1.0,
80
+ frailty_score: float = 0.0) -> RAFResult:
68
81
  """Process EOB resources and calculate RAF scores.
69
-
82
+
70
83
  Args:
71
84
  eob_list: List of EOB resources
72
85
  demographics: Demographics information
73
-
86
+ prefix_override: Optional prefix to override auto-detected demographic prefix.
87
+ Use when demographic categorization is incorrect (e.g., ESRD patients with orec=0).
88
+ maci: Major Adjustment to Coding Intensity (0.0-1.0, default 0.0)
89
+ norm_factor: Normalization factor (default 1.0)
90
+ frailty_score: Frailty adjustment score (default 0.0)
91
+
74
92
  Returns:
75
93
  RAFResult object containing calculated scores and processed data
76
94
  """
@@ -84,16 +102,36 @@ class HCCInFHIR:
84
102
 
85
103
  if self.filter_claims:
86
104
  sld_list = apply_filter(sld_list, professional_cpt=self.professional_cpt)
87
-
105
+
88
106
  # Calculate RAF score
89
107
  unique_dx_codes = self._get_unique_diagnosis_codes(sld_list)
90
- raf_result = self._calculate_raf_from_demographics(unique_dx_codes, demographics)
91
-
108
+ raf_result = self._calculate_raf_from_demographics_and_dx_codes(
109
+ unique_dx_codes, demographics, prefix_override, maci, norm_factor, frailty_score
110
+ )
111
+
92
112
  # Create new result with service data included
93
113
  return raf_result.model_copy(update={'service_level_data': sld_list})
94
114
 
95
- def run_from_service_data(self, service_data: List[Union[ServiceLevelData, Dict[str, Any]]],
96
- demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
115
+ def run_from_service_data(self, service_data: List[Union[ServiceLevelData, Dict[str, Any]]],
116
+ demographics: Union[Demographics, Dict[str, Any]],
117
+ prefix_override: Optional[PrefixOverride] = None,
118
+ maci: float = 0.0,
119
+ norm_factor: float = 1.0,
120
+ frailty_score: float = 0.0) -> RAFResult:
121
+ """Process service-level data and calculate RAF scores.
122
+
123
+ Args:
124
+ service_data: List of ServiceLevelData objects or dictionaries
125
+ demographics: Demographics information
126
+ prefix_override: Optional prefix to override auto-detected demographic prefix.
127
+ Use when demographic categorization is incorrect (e.g., ESRD patients with orec=0).
128
+ maci: Major Adjustment to Coding Intensity (0.0-1.0, default 0.0)
129
+ norm_factor: Normalization factor (default 1.0)
130
+ frailty_score: Frailty adjustment score (default 0.0)
131
+
132
+ Returns:
133
+ RAFResult object containing calculated scores and processed data
134
+ """
97
135
  demographics = self._ensure_demographics(demographics)
98
136
 
99
137
  if not isinstance(service_data, list):
@@ -116,25 +154,36 @@ class HCCInFHIR:
116
154
  )
117
155
 
118
156
  if self.filter_claims:
119
- standardized_data = apply_filter(standardized_data,
157
+ standardized_data = apply_filter(standardized_data,
120
158
  professional_cpt=self.professional_cpt)
121
159
 
122
-
160
+
123
161
  # Calculate RAF score
124
162
  unique_dx_codes = self._get_unique_diagnosis_codes(standardized_data)
125
- raf_result = self._calculate_raf_from_demographics(unique_dx_codes, demographics)
126
-
163
+ raf_result = self._calculate_raf_from_demographics_and_dx_codes(
164
+ unique_dx_codes, demographics, prefix_override, maci, norm_factor, frailty_score
165
+ )
166
+
127
167
  # Create new result with service data included
128
168
  return raf_result.model_copy(update={'service_level_data': standardized_data})
129
169
 
130
170
  def calculate_from_diagnosis(self, diagnosis_codes: List[str],
131
- demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
171
+ demographics: Union[Demographics, Dict[str, Any]],
172
+ prefix_override: Optional[PrefixOverride] = None,
173
+ maci: float = 0.0,
174
+ norm_factor: float = 1.0,
175
+ frailty_score: float = 0.0) -> RAFResult:
132
176
  """Calculate RAF scores from a list of diagnosis codes.
133
-
177
+
134
178
  Args:
135
179
  diagnosis_codes: List of diagnosis codes
136
180
  demographics: Demographics information
137
-
181
+ prefix_override: Optional prefix to override auto-detected demographic prefix.
182
+ Use when demographic categorization is incorrect (e.g., ESRD patients with orec=0).
183
+ maci: Major Adjustment to Coding Intensity (0.0-1.0, default 0.0)
184
+ norm_factor: Normalization factor (default 1.0)
185
+ frailty_score: Frailty adjustment score (default 0.0)
186
+
138
187
  Raises:
139
188
  ValueError: If diagnosis_codes is empty or not a list
140
189
  """
@@ -142,7 +191,9 @@ class HCCInFHIR:
142
191
  raise ValueError("diagnosis_codes must be a list")
143
192
  if not diagnosis_codes:
144
193
  raise ValueError("diagnosis_codes list cannot be empty")
145
-
194
+
146
195
  demographics = self._ensure_demographics(demographics)
147
- raf_result = self._calculate_raf_from_demographics(diagnosis_codes, demographics)
196
+ raf_result = self._calculate_raf_from_demographics_and_dx_codes(
197
+ diagnosis_codes, demographics, prefix_override, maci, norm_factor, frailty_score
198
+ )
148
199
  return raf_result
@@ -1,5 +1,5 @@
1
- from typing import List, Union, Dict, Tuple, Set
2
- from hccinfhir.datamodels import ModelName, RAFResult
1
+ from typing import List, Union, Dict, Tuple, Set, Optional
2
+ from hccinfhir.datamodels import ModelName, RAFResult, PrefixOverride
3
3
  from hccinfhir.model_demographics import categorize_demographics
4
4
  from hccinfhir.model_dx_to_cc import apply_mapping
5
5
  from hccinfhir.model_hierarchies import apply_hierarchies
@@ -17,17 +17,22 @@ is_chronic_default = load_is_chronic(mapping_file_default)
17
17
 
18
18
  def calculate_raf(diagnosis_codes: List[str],
19
19
  model_name: ModelName = "CMS-HCC Model V28",
20
- age: Union[int, float] = 65,
21
- sex: str = 'F',
20
+ age: Union[int, float] = 65,
21
+ sex: str = 'F',
22
22
  dual_elgbl_cd: str = 'NA',
23
- orec: str = '0',
23
+ orec: str = '0',
24
24
  crec: str = '0',
25
- new_enrollee: bool = False,
25
+ new_enrollee: bool = False,
26
26
  snp: bool = False,
27
27
  low_income: bool = False,
28
+ lti: bool = False,
28
29
  graft_months: int = None,
29
30
  dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default,
30
- is_chronic_mapping: Dict[Tuple[str, ModelName], bool] = is_chronic_default) -> RAFResult:
31
+ is_chronic_mapping: Dict[Tuple[str, ModelName], bool] = is_chronic_default,
32
+ prefix_override: Optional[PrefixOverride] = None,
33
+ maci: float = 0.0,
34
+ norm_factor: float = 1.0,
35
+ frailty_score: float = 0.0) -> RAFResult:
31
36
  """
32
37
  Calculate Risk Adjustment Factor (RAF) based on diagnosis codes and demographic information.
33
38
 
@@ -43,6 +48,10 @@ def calculate_raf(diagnosis_codes: List[str],
43
48
  snp: Special Needs Plan indicator
44
49
  low_income: Low income subsidy indicator
45
50
  graft_months: Number of months since transplant
51
+ prefix_override: Optional prefix to override auto-detected demographic prefix.
52
+ Use when demographic categorization from orec/crec is incorrect.
53
+ Common values: 'DI_' (ESRD Dialysis), 'DNE_' (ESRD Dialysis New Enrollee),
54
+ 'INS_' (Institutionalized), 'CFA_' (Community Full Dual Aged), etc.
46
55
 
47
56
  Returns:
48
57
  Dictionary containing RAF score and coefficients used in calculation
@@ -63,24 +72,27 @@ def calculate_raf(diagnosis_codes: List[str],
63
72
  elif 'HHS-HCC' in model_name: # not implemented yet
64
73
  version = 'V6'
65
74
 
66
- demographics = categorize_demographics(age,
67
- sex,
68
- dual_elgbl_cd,
69
- orec,
70
- crec,
71
- version,
72
- new_enrollee,
73
- snp,
74
- low_income,
75
- graft_months)
75
+ demographics = categorize_demographics(age,
76
+ sex,
77
+ dual_elgbl_cd,
78
+ orec,
79
+ crec,
80
+ version,
81
+ new_enrollee,
82
+ snp,
83
+ low_income,
84
+ lti,
85
+ graft_months,
86
+ prefix_override=prefix_override)
76
87
 
77
- cc_to_dx = apply_mapping(diagnosis_codes,
78
- model_name,
88
+ cc_to_dx = apply_mapping(diagnosis_codes,
89
+ model_name,
79
90
  dx_to_cc_mapping=dx_to_cc_mapping)
80
91
  hcc_set = set(cc_to_dx.keys())
81
92
  hcc_set = apply_hierarchies(hcc_set, model_name)
82
93
  interactions = apply_interactions(demographics, hcc_set, model_name)
83
- coefficients = apply_coefficients(demographics, hcc_set, interactions, model_name)
94
+ coefficients = apply_coefficients(demographics, hcc_set, interactions, model_name,
95
+ prefix_override=prefix_override)
84
96
 
85
97
  hcc_chronic = set()
86
98
  interactions_chronic = {}
@@ -100,14 +112,16 @@ def calculate_raf(diagnosis_codes: List[str],
100
112
  elif key.startswith('OriginallyDisabled_'):
101
113
  demographic_interactions[key] = value
102
114
 
103
- coefficients_demographics = apply_coefficients(demographics,
104
- set(),
105
- demographic_interactions,
106
- model_name)
107
- coefficients_chronic_only = apply_coefficients(demographics,
108
- hcc_chronic,
109
- interactions_chronic,
110
- model_name)
115
+ coefficients_demographics = apply_coefficients(demographics,
116
+ set(),
117
+ demographic_interactions,
118
+ model_name,
119
+ prefix_override=prefix_override)
120
+ coefficients_chronic_only = apply_coefficients(demographics,
121
+ hcc_chronic,
122
+ interactions_chronic,
123
+ model_name,
124
+ prefix_override=prefix_override)
111
125
 
112
126
  # Calculate risk scores
113
127
  print(f"Coefficients: {coefficients}")
@@ -116,12 +130,14 @@ def calculate_raf(diagnosis_codes: List[str],
116
130
  risk_score_demographics = sum(coefficients_demographics.values())
117
131
  risk_score_chronic_only = sum(coefficients_chronic_only.values()) - risk_score_demographics
118
132
  risk_score_hcc = risk_score - risk_score_demographics
133
+ risk_score_payment = risk_score * (1 - maci) / norm_factor + frailty_score
119
134
 
120
135
  return RAFResult(
121
136
  risk_score=risk_score,
122
137
  risk_score_demographics=risk_score_demographics,
123
138
  risk_score_chronic_only=risk_score_chronic_only,
124
139
  risk_score_hcc=risk_score_hcc,
140
+ risk_score_payment=risk_score_payment,
125
141
  hcc_list=list(hcc_set),
126
142
  cc_to_dx=cc_to_dx,
127
143
  coefficients=coefficients,
@@ -1,6 +1,6 @@
1
- from typing import Dict, Tuple
1
+ from typing import Dict, Tuple, Optional
2
2
  import importlib.resources
3
- from hccinfhir.datamodels import ModelName, Demographics
3
+ from hccinfhir.datamodels import ModelName, Demographics, PrefixOverride
4
4
 
5
5
  # Load default mappings from csv file
6
6
  coefficients_file_default = 'ra_coefficients_2026.csv'
@@ -90,15 +90,16 @@ def get_coefficent_prefix(demographics: Demographics,
90
90
  return prefix + '_'
91
91
 
92
92
 
93
- def apply_coefficients(demographics: Demographics,
94
- hcc_set: set[str],
93
+ def apply_coefficients(demographics: Demographics,
94
+ hcc_set: set[str],
95
95
  interactions: dict,
96
96
  model_name: ModelName = "CMS-HCC Model V28",
97
- coefficients: Dict[Tuple[str, ModelName], float] = coefficients_default) -> dict:
97
+ coefficients: Dict[Tuple[str, ModelName], float] = coefficients_default,
98
+ prefix_override: Optional[PrefixOverride] = None) -> dict:
98
99
  """Apply risk adjustment coefficients to HCCs and interactions.
99
100
 
100
101
  This function takes demographic information, HCC codes, and interaction variables and returns
101
- a dictionary mapping each variable to its corresponding coefficient value based on the
102
+ a dictionary mapping each variable to its corresponding coefficient value based on the
102
103
  specified model.
103
104
 
104
105
  Args:
@@ -108,13 +109,16 @@ def apply_coefficients(demographics: Demographics,
108
109
  model_name: Name of the risk adjustment model to use (default: "CMS-HCC Model V28")
109
110
  coefficients: Dictionary mapping (variable, model) tuples to coefficient values
110
111
  (default: coefficients_default)
112
+ prefix_override: Optional prefix to override auto-detected demographic prefix.
113
+ Common values: 'DI_' (ESRD Dialysis), 'DNE_' (ESRD Dialysis New Enrollee),
114
+ 'INS_' (Institutionalized), 'CFA_' (Community Full Dual Aged), etc.
111
115
 
112
116
  Returns:
113
117
  Dictionary mapping HCC codes and interaction variables to their coefficient values
114
118
  for variables that are present (HCC in hcc_set or interaction value = 1)
115
119
  """
116
120
  # Get the coefficient prefix
117
- prefix = get_coefficent_prefix(demographics, model_name)
121
+ prefix = prefix_override if prefix_override is not None else get_coefficent_prefix(demographics, model_name)
118
122
 
119
123
  output = {}
120
124
 
@@ -1,16 +1,18 @@
1
- from typing import Union
2
- from hccinfhir.datamodels import Demographics
1
+ from typing import Union, Optional
2
+ from hccinfhir.datamodels import Demographics, PrefixOverride
3
3
 
4
- def categorize_demographics(age: Union[int, float],
5
- sex: str,
4
+ def categorize_demographics(age: Union[int, float],
5
+ sex: str,
6
6
  dual_elgbl_cd: str = None,
7
- orec: str = None,
7
+ orec: str = None,
8
8
  crec: str = None,
9
9
  version: str = 'V2',
10
10
  new_enrollee: bool = False,
11
11
  snp: bool = False,
12
12
  low_income: bool = False,
13
- graft_months: int = None
13
+ lti: bool = False,
14
+ graft_months: int = None,
15
+ prefix_override: Optional[PrefixOverride] = None
14
16
  ) -> Demographics:
15
17
  """
16
18
  Categorize a beneficiary's demographics into risk adjustment categories.
@@ -23,10 +25,15 @@ def categorize_demographics(age: Union[int, float],
23
25
  sex: Beneficiary sex ('M'/'F' or '1'/'2')
24
26
  dual_elgbl_cd: Dual eligibility code ('00'-'10')
25
27
  orec: Original reason for entitlement code ('0'-'3')
26
- crec: Current reason for entitlement code ('0'-'3')
28
+ crec: Current reason for entitlement code ('0'-'3')
27
29
  version: Version of categorization to use ('V2', 'V4', 'V6')
28
30
  new_enrollee: Whether beneficiary is a new enrollee
29
31
  snp: Whether beneficiary is in a Special Needs Plan
32
+ low_income: Whether beneficiary is low income (RxHCC only)
33
+ lti: Whether beneficiary is long-term institutionalized
34
+ graft_months: Number of months since transplant (ESRD only)
35
+ prefix_override: Optional prefix to override demographic detection
36
+ (e.g., 'DI_', 'DNE_', 'INS_', 'CFA_', etc.)
30
37
 
31
38
  Returns:
32
39
  Demographics object containing derived fields like age/sex category,
@@ -82,6 +89,44 @@ def categorize_demographics(age: Union[int, float],
82
89
  esrd_crec = crec in {'2', '3'} if crec else False
83
90
  esrd = esrd_orec or esrd_crec
84
91
 
92
+ # Override demographics based on prefix_override
93
+ 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
+ # Set esrd flag
106
+ if prefix_override in esrd_prefixes:
107
+ esrd = True
108
+
109
+ # Set new_enrollee flag
110
+ if prefix_override in new_enrollee_prefixes:
111
+ new_enrollee = True
112
+ elif prefix_override in community_prefixes or prefix_override in institutional_prefixes:
113
+ new_enrollee = False
114
+
115
+ # Set dual eligibility flags based on prefix
116
+ if prefix_override in {'CFA_', 'CFD_', 'GFPA_', 'GFPN_'}:
117
+ is_fbd = True
118
+ is_pbd = False
119
+ elif prefix_override in {'CPA_', 'CPD_'}:
120
+ is_fbd = False
121
+ is_pbd = True
122
+ elif prefix_override in {'CNA_', 'CND_', 'GNPA_', 'GNPN_'}:
123
+ is_fbd = False
124
+ is_pbd = False
125
+
126
+ # Set lti flag based on prefix
127
+ if prefix_override in institutional_prefixes:
128
+ lti = True
129
+
85
130
  result_dict = {
86
131
  'version': version,
87
132
  'non_aged': non_aged,
@@ -97,6 +142,7 @@ def categorize_demographics(age: Union[int, float],
97
142
  'fbd': is_fbd,
98
143
  'pbd': is_pbd,
99
144
  'esrd': esrd,
145
+ 'lti': lti,
100
146
  'graft_months': graft_months,
101
147
  'low_income': low_income
102
148
  }
@@ -131,10 +177,14 @@ def categorize_demographics(age: Union[int, float],
131
177
  if orec is None or orec == '':
132
178
  orec = '0' # Default to 0 if OREC is None
133
179
 
134
- # New enrollee logic
180
+ # Determine prefix based on new_enrollee status
135
181
  if new_enrollee:
136
182
  prefix = 'NEF' if std_sex == '2' else 'NEM'
137
-
183
+ else:
184
+ prefix = 'F' if std_sex == '2' else 'M'
185
+
186
+ # CMS-HCC new enrollee logic with detailed 65-69 categories
187
+ if new_enrollee and not esrd:
138
188
  if age <= 34:
139
189
  category = f'{prefix}0_34'
140
190
  elif 34 < age <= 44:
@@ -167,9 +217,9 @@ def categorize_demographics(age: Union[int, float],
167
217
  category = f'{prefix}90_94'
168
218
  else:
169
219
  category = f'{prefix}95_GT'
170
-
220
+
221
+ # Standard logic with grouped 65_69 (for non-new-enrollee OR ESRD)
171
222
  else:
172
- prefix = 'F' if std_sex == '2' else 'M'
173
223
  age_ranges = [
174
224
  (0, 34, '0_34'),
175
225
  (34, 44, '35_44'),
@@ -184,7 +234,7 @@ def categorize_demographics(age: Union[int, float],
184
234
  (89, 94, '90_94'),
185
235
  (94, float('inf'), '95_GT')
186
236
  ]
187
-
237
+
188
238
  for low, high, suffix in age_ranges:
189
239
  if low < age <= high:
190
240
  category = f'{prefix}{suffix}'