hccinfhir 0.1.9__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.
@@ -5,15 +5,7 @@ from hccinfhir.model_dx_to_cc import apply_mapping
5
5
  from hccinfhir.model_hierarchies import apply_hierarchies
6
6
  from hccinfhir.model_coefficients import apply_coefficients
7
7
  from hccinfhir.model_interactions import apply_interactions
8
- from hccinfhir.utils import load_dx_to_cc_mapping, load_is_chronic
9
-
10
- # Load default mappings from csv file
11
- mapping_file_default = 'ra_dx_to_cc_2026.csv'
12
- dx_to_cc_default = load_dx_to_cc_mapping(mapping_file_default)
13
-
14
- # Load default mappings from csv file
15
- mapping_file_default = 'hcc_is_chronic.csv'
16
- is_chronic_default = load_is_chronic(mapping_file_default)
8
+ from hccinfhir.defaults import dx_to_cc_default, hierarchies_default, is_chronic_default, coefficients_default
17
9
 
18
10
  def calculate_raf(diagnosis_codes: List[str],
19
11
  model_name: ModelName = "CMS-HCC Model V28",
@@ -29,6 +21,8 @@ def calculate_raf(diagnosis_codes: List[str],
29
21
  graft_months: int = None,
30
22
  dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default,
31
23
  is_chronic_mapping: Dict[Tuple[str, ModelName], bool] = is_chronic_default,
24
+ hierarchies_mapping: Dict[Tuple[str, ModelName], Set[str]] = hierarchies_default,
25
+ coefficients_mapping: Dict[Tuple[str, ModelName], float] = coefficients_default,
32
26
  prefix_override: Optional[PrefixOverride] = None,
33
27
  maci: float = 0.0,
34
28
  norm_factor: float = 1.0,
@@ -37,24 +31,32 @@ def calculate_raf(diagnosis_codes: List[str],
37
31
  Calculate Risk Adjustment Factor (RAF) based on diagnosis codes and demographic information.
38
32
 
39
33
  Args:
40
- diagnosis_codes: List of ICD-10 diagnosis codes
41
- model_name: Name of the HCC model to use
42
- age: Patient's age
43
- sex: Patient's sex ('M' or 'F')
44
- dual_elgbl_cd: Dual eligibility code
45
- orec: Original reason for entitlement code
46
- crec: Current reason for entitlement code
47
- new_enrollee: Whether the patient is a new enrollee
48
- snp: Special Needs Plan indicator
49
- low_income: Low income subsidy indicator
50
- graft_months: Number of months since transplant
34
+ diagnosis_codes: List of ICD-10 diagnosis codes.
35
+ model_name: Name of the HCC model to use.
36
+ age: Patient's age.
37
+ sex: Patient's sex ('M' or 'F').
38
+ dual_elgbl_cd: Dual eligibility code.
39
+ orec: Original reason for entitlement code.
40
+ crec: Current reason for entitlement code.
41
+ new_enrollee: Whether the patient is a new enrollee.
42
+ snp: Special Needs Plan indicator.
43
+ low_income: Low income subsidy indicator.
44
+ lti: Long-term institutional status indicator.
45
+ graft_months: Number of months since transplant.
46
+ dx_to_cc_mapping: Mapping of diagnosis codes to condition categories; defaults to packaged 2026 mappings.
47
+ is_chronic_mapping: Mapping of HCCs to a chronic flag for the selected model; defaults to packaged mappings.
48
+ hierarchies_mapping: Mapping of parent HCCs to child HCCs for hierarchical rules; defaults to packaged 2026 mappings.
49
+ coefficients_mapping: Mapping of coefficient names to values; defaults to packaged 2026 mappings.
51
50
  prefix_override: Optional prefix to override auto-detected demographic prefix.
52
51
  Use when demographic categorization from orec/crec is incorrect.
53
52
  Common values: 'DI_' (ESRD Dialysis), 'DNE_' (ESRD Dialysis New Enrollee),
54
53
  'INS_' (Institutionalized), 'CFA_' (Community Full Dual Aged), etc.
54
+ maci: Medicare Advantage coding intensity adjustment applied to payment score.
55
+ norm_factor: Normalization factor applied to payment score.
56
+ frailty_score: Frailty adjustment added to payment score.
55
57
 
56
58
  Returns:
57
- Dictionary containing RAF score and coefficients used in calculation
59
+ RAFResult with the calculated risk scores, intermediate inputs, and metadata for the model run.
58
60
 
59
61
  Raises:
60
62
  ValueError: If input parameters are invalid
@@ -89,10 +91,10 @@ def calculate_raf(diagnosis_codes: List[str],
89
91
  model_name,
90
92
  dx_to_cc_mapping=dx_to_cc_mapping)
91
93
  hcc_set = set(cc_to_dx.keys())
92
- hcc_set = apply_hierarchies(hcc_set, model_name)
94
+ hcc_set = apply_hierarchies(hcc_set, model_name, hierarchies_mapping)
93
95
  interactions = apply_interactions(demographics, hcc_set, model_name)
94
96
  coefficients = apply_coefficients(demographics, hcc_set, interactions, model_name,
95
- prefix_override=prefix_override)
97
+ coefficients_mapping, prefix_override=prefix_override)
96
98
 
97
99
  hcc_chronic = set()
98
100
  interactions_chronic = {}
@@ -116,11 +118,13 @@ def calculate_raf(diagnosis_codes: List[str],
116
118
  set(),
117
119
  demographic_interactions,
118
120
  model_name,
121
+ coefficients_mapping,
119
122
  prefix_override=prefix_override)
120
123
  coefficients_chronic_only = apply_coefficients(demographics,
121
124
  hcc_chronic,
122
125
  interactions_chronic,
123
126
  model_name,
127
+ coefficients_mapping,
124
128
  prefix_override=prefix_override)
125
129
 
126
130
  # Calculate risk scores
@@ -1,32 +1,6 @@
1
- from typing import Dict, Tuple, Optional
2
- import importlib.resources
1
+ from typing import Dict, Tuple, Optional, Set
3
2
  from hccinfhir.datamodels import ModelName, Demographics, PrefixOverride
4
3
 
5
- # Load default mappings from csv file
6
- coefficients_file_default = 'ra_coefficients_2026.csv'
7
- coefficients_default: Dict[Tuple[str, ModelName], float] = {} # (diagnosis_code, model_name) -> value
8
-
9
- try:
10
- with importlib.resources.open_text('hccinfhir.data', coefficients_file_default) as f:
11
- for line in f.readlines()[1:]: # Skip header
12
- try:
13
- coefficient, value, model_domain, model_version = line.strip().split(',')
14
- if model_domain == 'ESRD':
15
- model_name = f"CMS-HCC {model_domain} Model V{model_version[-2:]}"
16
- else:
17
- model_name = f"{model_domain} Model V{model_version[-2:]}"
18
-
19
- key = (coefficient.lower(), model_name)
20
- if key not in coefficients_default:
21
- coefficients_default[key] = float(value)
22
- else:
23
- coefficients_default[key] = float(value)
24
- except ValueError:
25
- continue # Skip malformed lines
26
- except Exception as e:
27
- print(f"Error loading mapping file: {e}")
28
- coefficients_default = {}
29
-
30
4
  def get_coefficent_prefix(demographics: Demographics,
31
5
  model_name: ModelName = "CMS-HCC Model V28") -> str:
32
6
 
@@ -91,10 +65,10 @@ def get_coefficent_prefix(demographics: Demographics,
91
65
 
92
66
 
93
67
  def apply_coefficients(demographics: Demographics,
94
- hcc_set: set[str],
68
+ hcc_set: Set[str],
95
69
  interactions: dict,
96
- model_name: ModelName = "CMS-HCC Model V28",
97
- coefficients: Dict[Tuple[str, ModelName], float] = coefficients_default,
70
+ model_name: ModelName,
71
+ coefficients: Dict[Tuple[str, ModelName], float],
98
72
  prefix_override: Optional[PrefixOverride] = None) -> dict:
99
73
  """Apply risk adjustment coefficients to HCCs and interactions.
100
74
 
@@ -108,7 +82,6 @@ def apply_coefficients(demographics: Demographics,
108
82
  interactions: Dictionary of interaction variables and their values (0 or 1)
109
83
  model_name: Name of the risk adjustment model to use (default: "CMS-HCC Model V28")
110
84
  coefficients: Dictionary mapping (variable, model) tuples to coefficient values
111
- (default: coefficients_default)
112
85
  prefix_override: Optional prefix to override auto-detected demographic prefix.
113
86
  Common values: 'DI_' (ESRD Dialysis), 'DNE_' (ESRD Dialysis New Enrollee),
114
87
  'INS_' (Institutionalized), 'CFA_' (Community Full Dual Aged), etc.
@@ -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,15 +1,10 @@
1
1
  from typing import List, Dict, Set, Tuple, Optional
2
2
  from hccinfhir.datamodels import ModelName
3
- from hccinfhir.utils import load_dx_to_cc_mapping
4
-
5
- # Load default mappings from csv file
6
- mapping_file_default = 'ra_dx_to_cc_2026.csv'
7
- dx_to_cc_default = load_dx_to_cc_mapping(mapping_file_default)
8
3
 
9
4
  def get_cc(
10
5
  diagnosis_code: str,
11
- model_name: ModelName = "CMS-HCC Model V28",
12
- dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default
6
+ model_name: ModelName,
7
+ dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]]
13
8
  ) -> Optional[Set[str]]:
14
9
  """
15
10
  Get CC for a single diagnosis code.
@@ -17,7 +12,7 @@ def get_cc(
17
12
  Args:
18
13
  diagnosis_code: ICD-10 diagnosis code
19
14
  model_name: HCC model name to use for mapping
20
- dx_to_cc_mapping: Optional custom mapping dictionary
15
+ dx_to_cc_mapping: Mapping dictionary of (diagnosis_code, model_name) to CC codes
21
16
 
22
17
  Returns:
23
18
  CC code if found, None otherwise
@@ -26,8 +21,8 @@ def get_cc(
26
21
 
27
22
  def apply_mapping(
28
23
  diagnoses: List[str],
29
- model_name: ModelName = "CMS-HCC Model V28",
30
- dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default
24
+ model_name: ModelName,
25
+ dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]]
31
26
  ) -> Dict[str, Set[str]]:
32
27
  """
33
28
  Apply ICD-10 to CC mapping for a list of diagnosis codes.
@@ -35,7 +30,7 @@ def apply_mapping(
35
30
  Args:
36
31
  diagnoses: List of ICD-10 diagnosis codes
37
32
  model_name: HCC model name to use for mapping
38
- dx_to_cc_mapping: Optional custom mapping dictionary
33
+ dx_to_cc_mapping: Mapping dictionary of (diagnosis_code, model_name) to CC codes
39
34
 
40
35
  Returns:
41
36
  Dictionary mapping CCs to lists of diagnosis codes that map to them
@@ -1,47 +1,18 @@
1
1
  from typing import Dict, Set, Tuple
2
- import importlib.resources
3
- from hccinfhir.datamodels import ModelName
4
-
5
- def load_hierarchies(hierarchies_file: str) -> Dict[Tuple[str, ModelName], Set[str]]:
6
- """Load hierarchies from a CSV file."""
7
- hierarchies = {}
8
- try:
9
- with importlib.resources.open_text('hccinfhir.data', hierarchies_file) as f:
10
- for line in f.readlines()[1:]: # Skip header
11
- try:
12
- cc_parent, cc_child, model_domain, model_version, _ = line.strip().split(',')
13
- if model_domain == 'ESRD':
14
- model_name = f"CMS-HCC {model_domain} Model {model_version}"
15
- else:
16
- model_name = f"{model_domain} Model {model_version}"
17
- key = (cc_parent, model_name)
18
- if key not in hierarchies:
19
- hierarchies[key] = {cc_child}
20
- else:
21
- hierarchies[key].add(cc_child)
22
- except ValueError:
23
- continue # Skip malformed lines
24
- except Exception as e:
25
- print(f"Error loading mapping file: {e}")
26
- hierarchies = {}
27
- return hierarchies
28
-
29
- # Load default mappings from csv file
30
- hierarchies_file_default = 'ra_hierarchies_2026.csv'
31
- hierarchies_default: Dict[Tuple[str, ModelName], Set[str]] = load_hierarchies(hierarchies_file_default)
2
+ from hccinfhir.datamodels import ModelName
32
3
 
33
4
  def apply_hierarchies(
34
- cc_set: Set[str], # Set of active CCs
35
- model_name: ModelName = "CMS-HCC Model V28",
36
- hierarchies: Dict[Tuple[str, ModelName], Set[str]] = hierarchies_default
5
+ cc_set: Set[str],
6
+ model_name: ModelName,
7
+ hierarchies: Dict[Tuple[str, ModelName], Set[str]]
37
8
  ) -> Set[str]:
38
9
  """
39
10
  Apply hierarchical rules to a set of CCs based on model version.
40
11
 
41
12
  Args:
42
- ccs: Set of current active CCs
13
+ cc_set: Set of current active CCs
43
14
  model_name: HCC model name to use for hierarchy rules
44
- hierarchies: Optional custom hierarchy dictionary
15
+ hierarchies: Mapping dictionary of (parent_cc, model_name) to child CCs
45
16
 
46
17
  Returns:
47
18
  Set of CCs after applying hierarchies
@@ -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
  """
hccinfhir/samples.py CHANGED
@@ -157,7 +157,36 @@ class SampleData:
157
157
  raise FileNotFoundError(f"Sample 837 case {case_num} not found")
158
158
 
159
159
  return output
160
-
160
+
161
+ @staticmethod
162
+ def get_834_sample(case_number: int = 1) -> str:
163
+ """
164
+ Retrieve a specific 834 enrollment sample by case number.
165
+
166
+ Args:
167
+ case_number: The case number (currently only 1 is available). Default is 1.
168
+
169
+ Returns:
170
+ A string containing the 834 X12 enrollment data
171
+
172
+ Raises:
173
+ ValueError: If case_number is not 1
174
+ FileNotFoundError: If the sample file cannot be found
175
+
176
+ Example:
177
+ >>> sample_834 = SampleData.get_834_sample(1)
178
+ >>> print("ISA" in sample_834)
179
+ True
180
+ """
181
+ if case_number != 1:
182
+ raise ValueError("case_number must be 1 (only one 834 sample currently available)")
183
+
184
+ try:
185
+ with importlib.resources.open_text('hccinfhir.sample_files', f'sample_834_0{case_number}.txt') as f:
186
+ return f.read()
187
+ except FileNotFoundError:
188
+ raise FileNotFoundError(f"Sample 834 case {case_number} not found")
189
+
161
190
  @staticmethod
162
191
  def list_available_samples() -> Dict[str, Any]:
163
192
  """
@@ -174,7 +203,7 @@ class SampleData:
174
203
  return {
175
204
  "eob_samples": [
176
205
  "sample_eob_1.json",
177
- "sample_eob_2.json",
206
+ "sample_eob_2.json",
178
207
  "sample_eob_3.json",
179
208
  "sample_eob_200.ndjson"
180
209
  ],
@@ -182,9 +211,12 @@ class SampleData:
182
211
  "eob_list_size": 200,
183
212
  "837_samples": [f"sample_837_{i}.txt" for i in range(13)],
184
213
  "837_case_numbers": list(range(13)),
214
+ "834_samples": ["sample_834_01.txt"],
215
+ "834_case_numbers": [1],
185
216
  "description": {
186
217
  "eob": "Explanation of Benefits (FHIR resources) for testing HCC calculations",
187
- "837": "X12 837 claim data for testing claim processing"
218
+ "837": "X12 837 claim data for testing claim processing",
219
+ "834": "X12 834 enrollment data for dual eligibility and demographics"
188
220
  }
189
221
  }
190
222
 
@@ -232,16 +264,29 @@ def get_837_sample(case_number: int = 0) -> str:
232
264
  def get_837_sample_list(case_numbers: Optional[List[int]] = None) -> List[str]:
233
265
  """
234
266
  Convenience function to get multiple 837 claim samples.
235
-
267
+
236
268
  Args:
237
269
  case_numbers: List of case numbers to retrieve. If None, returns all 12 samples.
238
-
270
+
239
271
  Returns:
240
272
  A list of 837 X12 claim data strings
241
273
  """
242
274
  return SampleData.get_837_sample_list(case_numbers)
243
275
 
244
276
 
277
+ def get_834_sample(case_number: int = 1) -> str:
278
+ """
279
+ Convenience function to get an 834 enrollment sample.
280
+
281
+ Args:
282
+ case_number: The case number (currently only 1 is available). Default is 1.
283
+
284
+ Returns:
285
+ A string containing the 834 X12 enrollment data
286
+ """
287
+ return SampleData.get_834_sample(case_number)
288
+
289
+
245
290
  def list_available_samples() -> Dict[str, Any]:
246
291
  """
247
292
  Convenience function to get information about available samples.