hccinfhir 0.0.4__py3-none-any.whl → 0.0.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
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, Field
2
- from typing import List, Optional, Literal, Dict, Any, TypedDict
2
+ from typing import List, Optional, Literal, Dict, Set, TypedDict
3
3
 
4
4
  # Define Model Name literal type
5
5
  ModelName = Literal[
@@ -10,6 +10,17 @@ ModelName = Literal[
10
10
  "CMS-HCC ESRD Model V24",
11
11
  "RxHCC Model V08"
12
12
  ]
13
+
14
+ ProcFilteringFilename = Literal[
15
+ "ra_eligible_cpt_hcpcs_2023.csv",
16
+ "ra_eligible_cpt_hcpcs_2024.csv",
17
+ "ra_eligible_cpt_hcpcs_2025.csv"
18
+ ]
19
+
20
+ DxCCMappingFilename = Literal[
21
+ "ra_dx_to_cc_2025.csv"
22
+ ]
23
+
13
24
  class ServiceLevelData(BaseModel):
14
25
  """
15
26
  Represents standardized service-level data extracted from healthcare claims.
@@ -56,29 +67,35 @@ class Demographics(BaseModel):
56
67
  """
57
68
  Response model for demographic categorization
58
69
  """
59
- age: int = Field(..., description="Beneficiary age")
60
- sex: Literal['M', 'F', '1', '2'] = Field(..., description="Beneficiary sex")
61
- dual_elgbl_cd: Literal[None, 'NA', '99', '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10'] = Field('NA', description="Dual status code")
62
- orec: Literal[None, '0', '1', '2', '3'] = Field('0', description="Original reason for entitlement")
63
- crec: Literal[None, '0', '1', '2', '3'] = Field('0', description="Current reason for entitlement")
64
- new_enrollee: bool = Field(False, description="True if beneficiary is a new enrollee")
65
- snp: bool = Field(False, description="True if beneficiary is in SNP")
66
- version: str = Field("V2", description="Version of categorization used (V2, V4, V6)")
67
- low_income: bool = Field(False, description="True if beneficiary is in low income; RxHCC only")
70
+ age: int = Field(..., description="[required] Beneficiary age")
71
+ sex: Literal['M', 'F', '1', '2'] = Field(..., description="[required] Beneficiary sex")
72
+ dual_elgbl_cd: Optional[Literal[None, 'NA', '99', '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10']] = Field('NA', description="Dual status code")
73
+ orec: Optional[Literal[None, '0', '1', '2', '3']] = Field('0', description="Original reason for entitlement")
74
+ crec: Optional[Literal[None, '0', '1', '2', '3']] = Field('0', description="Current reason for entitlement")
75
+ new_enrollee: Optional[bool] = Field(False, description="True if beneficiary is a new enrollee")
76
+ snp: Optional[bool] = Field(False, description="True if beneficiary is in SNP")
77
+ version: Optional[str] = Field("V2", description="Version of categorization used (V2, V4, V6)")
78
+ low_income: Optional[bool] = Field(False, description="True if beneficiary is in low income; RxHCC only")
68
79
  graft_months: Optional[int] = Field(None, description="Number of months since transplant; ESRD Model only")
69
- category: str = Field(..., description="[derived] Age-sex category code")
70
- non_aged: bool = Field(False, description="[derived] True if age <= 64")
71
- orig_disabled: bool = Field(False, description="[derived] True if originally disabled (OREC='1' and not currently disabled)")
72
- disabled: bool = Field(False, description="[derived] True if currently disabled (age < 65 and OREC != '0')")
73
- esrd: bool = Field(False, description="[derived] True if ESRD (ESRD Model)")
74
- lti: bool = Field(False, description="[derived] True if LTI (LTI Model)")
75
- fbd: bool = Field(False, description="[derived] True if FBD (FBD Model)")
76
- pbd: bool = Field(False, description="[derived] True if PBD (PBD Model)")
80
+ category: Optional[str] = Field(None, description="[derived] Age-sex category code")
81
+ non_aged: Optional[bool] = Field(False, description="[derived] True if age <= 64")
82
+ orig_disabled: Optional[bool] = Field(False, description="[derived] True if originally disabled (OREC='1' and not currently disabled)")
83
+ disabled: Optional[bool] = Field(False, description="[derived] True if currently disabled (age < 65 and OREC != '0')")
84
+ esrd: Optional[bool] = Field(False, description="[derived] True if ESRD (ESRD Model)")
85
+ lti: Optional[bool] = Field(False, description="[derived] True if LTI (LTI Model)")
86
+ fbd: Optional[bool] = Field(False, description="[derived] True if FBD (FBD Model)")
87
+ pbd: Optional[bool] = Field(False, description="[derived] True if PBD (PBD Model)")
77
88
 
78
89
 
79
90
  class RAFResult(TypedDict):
80
91
  """Type definition for RAF calculation results"""
81
92
  risk_score: float
82
93
  hcc_list: List[str]
83
- details: Dict[str, Any]
84
- service_level_data: List[ServiceLevelData]
94
+ cc_to_dx: Dict[str, Set[str]]
95
+ coefficients: Dict[str, float]
96
+ interactions: Dict[str, float]
97
+ demographics: Demographics
98
+ model_name: ModelName
99
+ version: str
100
+ diagnosis_codes: List[str]
101
+ service_level_data: Optional[List[ServiceLevelData]]
hccinfhir/filter.py CHANGED
@@ -1,12 +1,10 @@
1
1
  from typing import List, Set
2
2
  from hccinfhir.datamodels import ServiceLevelData
3
- import importlib.resources
3
+ from hccinfhir.utils import load_proc_filtering
4
4
 
5
5
  # use import importlib.resources to load the professional_cpt_fn file as a list of strings
6
6
  professional_cpt_default_fn = 'ra_eligible_cpt_hcpcs_2023.csv'
7
- professional_cpt_default = []
8
- with importlib.resources.open_text('hccinfhir.data', professional_cpt_default_fn) as f:
9
- professional_cpt_default = set(f.read().splitlines())
7
+ professional_cpt_default = load_proc_filtering(professional_cpt_default_fn)
10
8
 
11
9
  def apply_filter(
12
10
  data: List[ServiceLevelData],
hccinfhir/hccinfhir.py CHANGED
@@ -2,8 +2,8 @@ from typing import List, Dict, Any, Union
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
6
-
5
+ from hccinfhir.datamodels import Demographics, ServiceLevelData, RAFResult, ModelName, ProcFilteringFilename, DxCCMappingFilename
6
+ from hccinfhir.utils import load_proc_filtering, load_dx_to_cc_mapping
7
7
 
8
8
  class HCCInFHIR:
9
9
  """
@@ -15,17 +15,26 @@ class HCCInFHIR:
15
15
 
16
16
  def __init__(self,
17
17
  filter_claims: bool = True,
18
- model_name: ModelName = "CMS-HCC Model V28"):
18
+ model_name: ModelName = "CMS-HCC Model V28",
19
+ proc_filtering_filename: ProcFilteringFilename = "ra_eligible_cpt_hcpcs_2025.csv",
20
+ dx_cc_mapping_filename: DxCCMappingFilename = "ra_dx_to_cc_2025.csv"):
19
21
  """
20
22
  Initialize the HCCInFHIR processor.
21
23
 
22
24
  Args:
23
25
  filter_claims: Whether to apply filtering rules to claims. Default is True.
24
26
  model_name: The name of the model to use for the calculation. Default is "CMS-HCC Model V28".
27
+ proc_filtering_filename: The filename of the professional cpt filtering file. Default is "ra_eligible_cpt_hcpcs_2025.csv".
28
+ dx_cc_mapping_filename: The filename of the dx to cc mapping file. Default is "ra_dx_to_cc_2025.csv".
25
29
  """
26
30
  self.filter_claims = filter_claims
27
31
  self.model_name = model_name
28
-
32
+ self.proc_filtering_filename = proc_filtering_filename
33
+ self.dx_cc_mapping_filename = dx_cc_mapping_filename
34
+ self.professional_cpt = load_proc_filtering(proc_filtering_filename)
35
+ self.dx_to_cc_mapping = load_dx_to_cc_mapping(dx_cc_mapping_filename)
36
+
37
+
29
38
  def _ensure_demographics(self, demographics: Union[Demographics, Dict[str, Any]]) -> Demographics:
30
39
  """Convert demographics dict to Demographics object if needed."""
31
40
  if not isinstance(demographics, Demographics):
@@ -33,7 +42,7 @@ class HCCInFHIR:
33
42
  return demographics
34
43
 
35
44
  def _calculate_raf_from_demographics(self, diagnosis_codes: List[str],
36
- demographics: Demographics) -> Dict[str, Any]:
45
+ demographics: Demographics) -> RAFResult:
37
46
  """Calculate RAF score using demographics data."""
38
47
  return calculate_raf(
39
48
  diagnosis_codes=diagnosis_codes,
@@ -46,58 +55,40 @@ class HCCInFHIR:
46
55
  new_enrollee=demographics.new_enrollee,
47
56
  snp=demographics.snp,
48
57
  low_income=demographics.low_income,
49
- graft_months=demographics.graft_months
58
+ graft_months=demographics.graft_months,
59
+ dx_to_cc_mapping=self.dx_to_cc_mapping
50
60
  )
51
61
 
52
62
  def _get_unique_diagnosis_codes(self, service_data: List[ServiceLevelData]) -> List[str]:
53
63
  """Extract unique diagnosis codes from service level data."""
54
- all_dx_codes = []
55
- for sld in service_data:
56
- all_dx_codes.extend(sld.claim_diagnosis_codes)
57
- return list(set(all_dx_codes))
64
+ return list({code for sld in service_data for code in sld.claim_diagnosis_codes})
58
65
 
59
- def _format_result(self,
60
- raf_result: Union[Dict[str, Any], RAFResult],
61
- service_data: List[ServiceLevelData]) -> RAFResult:
62
- """
63
- Format RAF calculation results into a standardized RAFResult format.
66
+ def run(self, eob_list: List[Dict[str, Any]],
67
+ demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
68
+ """Process EOB resources and calculate RAF scores.
64
69
 
65
- Returns a dictionary conforming to the RAFResult TypedDict structure.
70
+ Args:
71
+ eob_list: List of EOB resources
72
+ demographics: Demographics information
73
+
74
+ Returns:
75
+ RAFResult object containing calculated scores and processed data
66
76
  """
77
+ if not isinstance(eob_list, list):
78
+ raise ValueError("eob_list must be a list; if no eob, pass empty list")
67
79
 
68
- # Check if raf_result already has the expected RAFResult structure
69
- if all(key in raf_result for key in ['risk_score', 'hcc_list', 'details']):
70
- # Already in RAFResult format, just ensure service data is set
71
- result = dict(raf_result) # Create a copy to avoid modifying the original
72
- result['service_level_data'] = service_data
73
- return result
74
-
75
- # Handle result from calculate_raf function
76
- if 'raf' in raf_result and 'coefficients' in raf_result:
77
- return {
78
- 'risk_score': raf_result['raf'],
79
- 'hcc_list': list(raf_result['coefficients'].keys()),
80
- 'details': raf_result['coefficients'],
81
- 'service_level_data': service_data
82
- }
83
-
84
- # Unrecognized format
85
- raise ValueError(f"Unrecognized RAF result format: {list(raf_result.keys())}")
86
-
87
- def run(self, eob_list: List[Dict[str, Any]],
88
- demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
89
80
  demographics = self._ensure_demographics(demographics)
90
81
 
91
82
  # Extract and filter service level data
92
83
  sld_list = extract_sld_list(eob_list)
93
84
  if self.filter_claims:
94
- sld_list = apply_filter(sld_list)
85
+ sld_list = apply_filter(sld_list, self.professional_cpt)
95
86
 
96
87
  # Calculate RAF score
97
88
  unique_dx_codes = self._get_unique_diagnosis_codes(sld_list)
98
89
  raf_result = self._calculate_raf_from_demographics(unique_dx_codes, demographics)
99
-
100
- return self._format_result(raf_result, sld_list)
90
+ raf_result['service_level_data'] = sld_list
91
+ return raf_result
101
92
 
102
93
  def run_from_service_data(self, service_data: List[Union[ServiceLevelData, Dict[str, Any]]],
103
94
  demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
@@ -126,17 +117,33 @@ class HCCInFHIR:
126
117
  )
127
118
 
128
119
  if self.filter_claims:
129
- standardized_data = apply_filter(standardized_data)
120
+ standardized_data = apply_filter(standardized_data,
121
+ professional_cpt=self.professional_cpt)
122
+
130
123
 
131
124
  # Calculate RAF score
132
125
  unique_dx_codes = self._get_unique_diagnosis_codes(standardized_data)
133
126
  raf_result = self._calculate_raf_from_demographics(unique_dx_codes, demographics)
134
-
135
- return self._format_result(raf_result, standardized_data)
127
+ raf_result['service_level_data'] = standardized_data
128
+
129
+ return raf_result
136
130
 
137
131
  def calculate_from_diagnosis(self, diagnosis_codes: List[str],
138
132
  demographics: Union[Demographics, Dict[str, Any]]) -> RAFResult:
133
+ """Calculate RAF scores from a list of diagnosis codes.
134
+
135
+ Args:
136
+ diagnosis_codes: List of diagnosis codes
137
+ demographics: Demographics information
138
+
139
+ Raises:
140
+ ValueError: If diagnosis_codes is empty or not a list
141
+ """
142
+ if not isinstance(diagnosis_codes, list):
143
+ raise ValueError("diagnosis_codes must be a list")
144
+ if not diagnosis_codes:
145
+ raise ValueError("diagnosis_codes list cannot be empty")
146
+
139
147
  demographics = self._ensure_demographics(demographics)
140
148
  raf_result = self._calculate_raf_from_demographics(diagnosis_codes, demographics)
141
- # Create an empty service level data list since we're calculating directly from diagnosis codes
142
- return self._format_result(raf_result, [])
149
+ return raf_result
@@ -1,10 +1,15 @@
1
- from typing import List, Union
2
- from hccinfhir.datamodels import ModelName
1
+ from typing import List, Union, Dict, Tuple, Set
2
+ from hccinfhir.datamodels import ModelName, RAFResult
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
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
9
+
10
+ # Load default mappings from csv file
11
+ mapping_file_default = 'ra_dx_to_cc_2025.csv'
12
+ dx_to_cc_default = load_dx_to_cc_mapping(mapping_file_default)
8
13
 
9
14
  def calculate_raf(diagnosis_codes: List[str],
10
15
  model_name: ModelName = "CMS-HCC Model V28",
@@ -16,14 +21,43 @@ def calculate_raf(diagnosis_codes: List[str],
16
21
  new_enrollee: bool = False,
17
22
  snp: bool = False,
18
23
  low_income: bool = False,
19
- graft_months: int = None):
24
+ graft_months: int = None,
25
+ dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default) -> RAFResult:
26
+ """
27
+ Calculate Risk Adjustment Factor (RAF) based on diagnosis codes and demographic information.
28
+
29
+ Args:
30
+ diagnosis_codes: List of ICD-10 diagnosis codes
31
+ model_name: Name of the HCC model to use
32
+ age: Patient's age
33
+ sex: Patient's sex ('M' or 'F')
34
+ dual_elgbl_cd: Dual eligibility code
35
+ orec: Original reason for entitlement code
36
+ crec: Current reason for entitlement code
37
+ new_enrollee: Whether the patient is a new enrollee
38
+ snp: Special Needs Plan indicator
39
+ low_income: Low income subsidy indicator
40
+ graft_months: Number of months since transplant
41
+
42
+ Returns:
43
+ Dictionary containing RAF score and coefficients used in calculation
44
+
45
+ Raises:
46
+ ValueError: If input parameters are invalid
47
+ """
48
+ # Input validation
49
+ if not isinstance(age, (int, float)) or age < 0:
50
+ raise ValueError("Age must be a non-negative number")
51
+
52
+ if sex not in ['M', 'F', '1', '2']:
53
+ raise ValueError("Sex must be 'M' or 'F' or '1' or '2'")
20
54
 
21
55
  version = 'V2'
22
56
  if 'RxHCC' in model_name:
23
57
  version = 'V4'
24
58
  elif 'HHS-HCC' in model_name: # not implemented yet
25
59
  version = 'V6'
26
-
60
+
27
61
  demographics = categorize_demographics(age,
28
62
  sex,
29
63
  dual_elgbl_cd,
@@ -35,16 +69,27 @@ def calculate_raf(diagnosis_codes: List[str],
35
69
  low_income,
36
70
  graft_months)
37
71
 
38
- cc_to_dx = apply_mapping(diagnosis_codes, model_name)
72
+ cc_to_dx = apply_mapping(diagnosis_codes,
73
+ model_name,
74
+ dx_to_cc_mapping=dx_to_cc_mapping)
39
75
  hcc_set = set(cc_to_dx.keys())
40
76
  hcc_set = apply_hierarchies(hcc_set, model_name)
41
77
  interactions = apply_interactions(demographics, hcc_set, model_name)
42
78
  coefficients = apply_coefficients(demographics, hcc_set, interactions, model_name)
43
79
 
44
- raf = sum(coefficients.values())
45
-
80
+ risk_score = sum(coefficients.values())
46
81
 
47
- return {'raf': raf, 'coefficients': coefficients}
82
+ return {
83
+ 'risk_score': risk_score,
84
+ 'hcc_list': list(hcc_set),
85
+ 'cc_to_dx': cc_to_dx,
86
+ 'coefficients': coefficients,
87
+ 'interactions': interactions,
88
+ 'demographics': demographics,
89
+ 'model_name': model_name,
90
+ 'version': version,
91
+ 'diagnosis_codes': diagnosis_codes,
92
+ }
48
93
 
49
94
 
50
95
 
@@ -1,31 +1,15 @@
1
1
  from typing import List, Dict, Set, Tuple, Optional
2
- import importlib.resources
3
2
  from hccinfhir.datamodels import ModelName
3
+ from hccinfhir.utils import load_dx_to_cc_mapping
4
4
 
5
5
  # Load default mappings from csv file
6
6
  mapping_file_default = 'ra_dx_to_cc_2025.csv'
7
- dx_to_cc_default: Dict[Tuple[str, ModelName], Set[str]] = {} # (diagnosis_code, model_name) -> cc
8
-
9
- try:
10
- with importlib.resources.open_text('hccinfhir.data', mapping_file_default) as f:
11
- for line in f.readlines()[1:]: # Skip header
12
- try:
13
- diagnosis_code, cc, model_name = line.strip().split(',')
14
- key = (diagnosis_code, model_name)
15
- if key not in dx_to_cc_default:
16
- dx_to_cc_default[key] = {cc}
17
- else:
18
- dx_to_cc_default[key].add(cc)
19
- except ValueError:
20
- continue # Skip malformed lines
21
- except Exception as e:
22
- print(f"Error loading mapping file: {e}")
23
- dx_to_cc_default = {}
7
+ dx_to_cc_default = load_dx_to_cc_mapping(mapping_file_default)
24
8
 
25
9
  def get_cc(
26
10
  diagnosis_code: str,
27
11
  model_name: ModelName = "CMS-HCC Model V28",
28
- dx_to_cc: Dict[Tuple[str, str], Set[str]] = dx_to_cc_default
12
+ dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default
29
13
  ) -> Optional[Set[str]]:
30
14
  """
31
15
  Get CC for a single diagnosis code.
@@ -33,17 +17,17 @@ def get_cc(
33
17
  Args:
34
18
  diagnosis_code: ICD-10 diagnosis code
35
19
  model_name: HCC model name to use for mapping
36
- dx_to_cc: Optional custom mapping dictionary
20
+ dx_to_cc_mapping: Optional custom mapping dictionary
37
21
 
38
22
  Returns:
39
23
  CC code if found, None otherwise
40
24
  """
41
- return dx_to_cc.get((diagnosis_code, model_name))
25
+ return dx_to_cc_mapping.get((diagnosis_code, model_name))
42
26
 
43
27
  def apply_mapping(
44
28
  diagnoses: List[str],
45
29
  model_name: ModelName = "CMS-HCC Model V28",
46
- dx_to_cc: Dict[Tuple[str, str], Set[str]] = dx_to_cc_default
30
+ dx_to_cc_mapping: Dict[Tuple[str, ModelName], Set[str]] = dx_to_cc_default
47
31
  ) -> Dict[str, Set[str]]:
48
32
  """
49
33
  Apply ICD-10 to CC mapping for a list of diagnosis codes.
@@ -51,7 +35,7 @@ def apply_mapping(
51
35
  Args:
52
36
  diagnoses: List of ICD-10 diagnosis codes
53
37
  model_name: HCC model name to use for mapping
54
- dx_to_cc: Optional custom mapping dictionary
38
+ dx_to_cc_mapping: Optional custom mapping dictionary
55
39
 
56
40
  Returns:
57
41
  Dictionary mapping CCs to lists of diagnosis codes that map to them
@@ -60,7 +44,7 @@ def apply_mapping(
60
44
 
61
45
  for dx in set(diagnoses):
62
46
  dx = dx.upper().replace('.', '')
63
- ccs = get_cc(dx, model_name, dx_to_cc)
47
+ ccs = get_cc(dx, model_name, dx_to_cc_mapping)
64
48
  if ccs is not None:
65
49
  for cc in ccs:
66
50
  if cc not in cc_to_dx:
hccinfhir/utils.py ADDED
@@ -0,0 +1,51 @@
1
+ from typing import Set, Dict, Tuple
2
+ import importlib.resources
3
+ from hccinfhir.datamodels import ModelName, ProcFilteringFilename, DxCCMappingFilename
4
+
5
+ def load_proc_filtering(filename: ProcFilteringFilename) -> Set[str]:
6
+ """
7
+ Load a single-column CSV file into a set of strings.
8
+
9
+ Args:
10
+ filename: Name of the CSV file in the hccinfhir.data package
11
+
12
+ Returns:
13
+ Set of strings from the CSV file
14
+ """
15
+ try:
16
+ with importlib.resources.open_text('hccinfhir.data', filename) as f:
17
+ return set(f.read().splitlines())
18
+ except Exception as e:
19
+ print(f"Error loading {filename}: {e}")
20
+ return set()
21
+
22
+ def load_dx_to_cc_mapping(filename: DxCCMappingFilename) -> Dict[Tuple[str, ModelName], Set[str]]:
23
+ """
24
+ Load diagnosis to CC mapping from a CSV file.
25
+ Expected format: diagnosis_code,cc,model_name
26
+
27
+ Args:
28
+ filename: Name of the CSV file in the hccinfhir.data package
29
+
30
+ Returns:
31
+ Dictionary mapping (diagnosis_code, model_name) to a set of CC codes
32
+ """
33
+ mapping: Dict[Tuple[str, ModelName], Set[str]] = {}
34
+
35
+ try:
36
+ with importlib.resources.open_text('hccinfhir.data', filename) as f:
37
+ for line in f.readlines()[1:]: # Skip header
38
+ try:
39
+ diagnosis_code, cc, model_name = line.strip().split(',')
40
+ key = (diagnosis_code, model_name)
41
+ if key not in mapping:
42
+ mapping[key] = {cc}
43
+ else:
44
+ mapping[key].add(cc)
45
+ except ValueError:
46
+ continue # Skip malformed lines
47
+ except Exception as e:
48
+ print(f"Error loading mapping file: {e}")
49
+ return {}
50
+
51
+ return mapping
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hccinfhir
3
- Version: 0.0.4
3
+ Version: 0.0.6
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
@@ -20,6 +20,9 @@ A Python library for extracting standardized service-level data from FHIR Explan
20
20
  - Support for both BCDA (Blue Button 2.0) and standard FHIR R4 formats
21
21
  - Pydantic models for type safety and data validation
22
22
  - Standardized Service Level Data (SLD) output format
23
+ - Multiple HCC model support (V22, V24, V28, ESRD V21, ESRD V24, RxHCC V08)
24
+ - Flexible input options: FHIR EOBs, service data, or direct diagnosis codes
25
+
23
26
 
24
27
  ## Installation
25
28
  ```bash
@@ -109,16 +112,75 @@ result = calculate_raf(
109
112
  )
110
113
  ```
111
114
 
112
- ### 4. Running HCC on FHIR data
115
+ ### 4. HCCInFHIR Class
116
+ The main processor class that integrates extraction, filtering, and calculation components:
113
117
 
114
118
  ```python
115
- from hccinfhir import HCCInFHIR
119
+ from hccinfhir.hccinfhir import HCCInFHIR
120
+ from hccinfhir.datamodels import Demographics
121
+
122
+ # Initialize with custom configuration
123
+ hcc_processor = HCCInFHIR(
124
+ filter_claims=True, # Enable claim filtering
125
+ model_name="CMS-HCC Model V28", # Choose HCC model version
126
+ proc_filtering_filename="ra_eligible_cpt_hcpcs_2025.csv", # CPT/HCPCS filtering rules
127
+ dx_cc_mapping_filename="ra_dx_to_cc_2025.csv" # Diagnosis to CC mapping
128
+ )
129
+
130
+ # Define beneficiary demographics
131
+ demographics = {
132
+ age=67,
133
+ sex='F'
134
+ }
116
135
 
117
- hcc_processor = HCCInFHIR()
136
+ # Method 1: Process FHIR EOB resources
137
+ raf_result = hcc_processor.run(eob_list, demographics)
118
138
 
119
- result = hcc_processor.run(eob_list, demographic_data)
139
+ # Method 2: Process service level data
140
+ service_data = [{
141
+ "procedure_code": "99214",
142
+ "claim_diagnosis_codes": ["E11.9", "I10"],
143
+ "claim_type": "71",
144
+ "service_date": "2024-01-15"
145
+ }]
146
+ raf_result = hcc_processor.run_from_service_data(service_data, demographics)
147
+
148
+ # Method 3: Direct diagnosis processing
149
+ diagnosis_codes = ['E119', 'I509']
150
+ raf_result = hcc_processor.calculate_from_diagnosis(diagnosis_codes, demographics)
151
+
152
+ # RAF Result contains:
153
+ print(f"Risk Score: {raf_result['risk_score']}")
154
+ print(f"HCC List: {raf_result['hcc_list']}")
155
+ print(f"CC to Diagnosis Mapping: {raf_result['cc_to_dx']}")
156
+ print(f"Applied Coefficients: {raf_result['coefficients']}")
157
+ print(f"Applied Interactions: {raf_result['interactions']}")
120
158
  ```
121
159
 
160
+ The HCCInFHIR class provides three main processing methods:
161
+
162
+ 1. `run(eob_list, demographics)`: Process FHIR ExplanationOfBenefit resources
163
+ - Extracts service data from FHIR resources
164
+ - Applies filtering rules if enabled
165
+ - Calculates RAF scores using the specified model
166
+
167
+ 2. `run_from_service_data(service_data, demographics)`: Process standardized service data
168
+ - Accepts pre-formatted service level data
169
+ - Validates data structure using Pydantic models
170
+ - Applies filtering and calculates RAF scores
171
+
172
+ 3. `calculate_from_diagnosis(diagnosis_codes, demographics)`: Direct diagnosis processing
173
+ - Processes raw diagnosis codes without service context
174
+ - Useful for quick RAF calculations or validation
175
+ - Bypasses service-level filtering
176
+
177
+ Each method returns a RAFResult containing:
178
+ - Final risk score
179
+ - List of HCCs
180
+ - Mapping of condition categories to diagnosis codes
181
+ - Applied coefficients and interactions
182
+ - Processed service level data (when applicable)
183
+
122
184
  ## Testing
123
185
  ```bash
124
186
  $ python3 -m hatch shell
@@ -230,8 +292,21 @@ FROM ra_coefficients
230
292
  WHERE eff_last_date > '2025-01-01';
231
293
  ```
232
294
 
295
+ `ra_eligible_cpt_hcpcs_2025.csv`
296
+ ```sql
297
+ SELECT DISTINCT cpt_hcpcs_code
298
+ FROM mimi_ws_1.cmspayment.ra_eligible_cpt_hcpcs
299
+ WHERE is_included = 'yes' AND YEAR(mimi_src_file_date) = 2024;
300
+ ```
301
+
233
302
  ## Contributing
234
303
  Join us at [mimilabs](https://mimilabs.ai/signup). Reference data available in MIMILabs data lakehouse.
235
304
 
305
+ ## Publishing (only for those maintainers...)
306
+ ```bash
307
+ $ python3 -m hatch build
308
+ $ python3 -m hatch publish
309
+ ```
310
+
236
311
  ## License
237
312
  Apache License 2.0
@@ -1,20 +1,23 @@
1
1
  hccinfhir/__init__.py,sha256=OCyYCv4jTOlYHZbTw2DTks3e6_YT1N2JXAOuyR03KNE,43
2
- hccinfhir/datamodels.py,sha256=S-wQQXEyya_RbrMLmHCPWouaFvEflV8762mciwhrtSk,4270
2
+ hccinfhir/datamodels.py,sha256=ZohKX_LqtcEkGW3-tvtj9AWMepMooNtWpchSv9bOXuQ,4845
3
3
  hccinfhir/extractor.py,sha256=-jHVCIJqFAqvrI9GxkkXZVDQjKDa-7vF7v3PGMGAMnA,1801
4
4
  hccinfhir/extractor_837.py,sha256=vkTBCd0WBaJoTrUd-Z-zCIaoLk7KV2n4AGqIORhONIk,7147
5
5
  hccinfhir/extractor_fhir.py,sha256=Rg_L0Vg5tz_L2VJ_jvZwWz6RMlPAkHwj4LiK-OWQvrQ,8458
6
- hccinfhir/filter.py,sha256=tyaK0Ss0rafkDfcCJWUZOAzzFOIYiuf_SuJ4BBiHJ38,2272
7
- hccinfhir/hccinfhir.py,sha256=IS3Hvm8wnFL785XkANva6qB9kkc54OCFn-KkwuW5BX0,6450
8
- hccinfhir/model_calculate.py,sha256=EA5JvbB36wrFAt6kD79E8rn9NKntVMJqXHAq_p7duEE,1947
6
+ hccinfhir/filter.py,sha256=8uYThN0-AqwVAKyti29WGiFwQKDiremMhYd_m6QcXhM,2193
7
+ hccinfhir/hccinfhir.py,sha256=YdooDAezKPF1VXoAeW8tsvQwm9SZ6u2SO-a0rhoX0iY,6933
8
+ hccinfhir/model_calculate.py,sha256=6cjWMtayC9fBxhTCnbqSJSlctblYz4Mn9vQYDXx-Iu8,3725
9
9
  hccinfhir/model_coefficients.py,sha256=UrDAEWBoqooSC8hy9YSUsLMmmfgIO0YGtVkui6ruOkE,5528
10
10
  hccinfhir/model_demographics.py,sha256=LZLlPQOtxPh3Md6q9xwztQ9PwrUo_gloNCYSa2bDirY,6578
11
- hccinfhir/model_dx_to_cc.py,sha256=foKn2A0ly2TOVKq5qA92CuZSxHPX00cqt5D-DjBm9x4,2348
11
+ hccinfhir/model_dx_to_cc.py,sha256=jCFlnAOBkfI9FrCX6tZIh-Sp_DW0HwpY7QrPXGtwInI,1765
12
12
  hccinfhir/model_hierarchies.py,sha256=e8QtSayTrfPv2wh149FjK7ToiEmU1ISYMA1Pi38iVk0,2700
13
13
  hccinfhir/model_interactions.py,sha256=ZLiKJepPjPkYceKDf7dLXoYE0p44I7t9y2sTOlrxojo,20264
14
+ hccinfhir/utils.py,sha256=5tPwf_neqSJXlerhHbrPPihC0nMKsU40HOBoNCEkGco,1791
14
15
  hccinfhir/data/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
15
16
  hccinfhir/data/ra_coefficients_2025.csv,sha256=I0S2hoJlfig-D0oSFxy0b3Piv7m9AzOGo2CwR6bcQ9w,215191
16
17
  hccinfhir/data/ra_dx_to_cc_2025.csv,sha256=4N7vF6VZndkl7d3Fo0cGsbAPAZdCjAizSH8BOKsZNAo,1618924
17
18
  hccinfhir/data/ra_eligible_cpt_hcpcs_2023.csv,sha256=VVoA4s0hsFmcRIugyFdbvSoeLcn7M7z0DITT6l4YqL8,39885
19
+ hccinfhir/data/ra_eligible_cpt_hcpcs_2024.csv,sha256=CawKImfCb8fFMDbWwqvNLRyRAda_u9N8Q3ne8QAAe54,40191
20
+ hccinfhir/data/ra_eligible_cpt_hcpcs_2025.csv,sha256=-tMvv2su5tsSbGUh6fZZCMUEkXInBpcTtbUCi2o_UwI,40359
18
21
  hccinfhir/data/ra_hierarchies_2025.csv,sha256=HQSPNloe6mvvwMgv8ZwYAfWKkT2b2eUvm4JQy6S_mVQ,13045
19
22
  hccinfhir/samples/__init__.py,sha256=SGiSkpGrnxbvtEFMMlk82NFHOE50hFXcgKwKUSuVZUg,45
20
23
  hccinfhir/samples/sample_837_0.txt,sha256=eggrD259uHa05z2dfxWBpUDseSDp_AQcLyN_adpHyTw,5295
@@ -33,7 +36,7 @@ hccinfhir/samples/sample_eob_1.json,sha256=_NGSVR2ysFpx-DcTvyga6dFCzhQ8Vi9fNzQEM
33
36
  hccinfhir/samples/sample_eob_2.json,sha256=FcnJcx0ApOczxjJ_uxVLzCep9THfNf4xs9Yf7hxk8e4,1769
34
37
  hccinfhir/samples/sample_eob_200.ndjson,sha256=CxpjeQ1DCMUzZILaM68UEhfxO0p45YGhDDoCZeq8PxU,1917986
35
38
  hccinfhir/samples/sample_eob_3.json,sha256=4BW4wOMBEEU9RDfJR15rBEvk0KNHyuMEh3e055y87Hc,2306
36
- hccinfhir-0.0.4.dist-info/METADATA,sha256=_A0qRJoRtyxs2NHjypr8DP-DSIK_bg_sRgBoOMrtSig,8763
37
- hccinfhir-0.0.4.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
38
- hccinfhir-0.0.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
39
- hccinfhir-0.0.4.dist-info/RECORD,,
39
+ hccinfhir-0.0.6.dist-info/METADATA,sha256=ZrsvQOjZWL2E5LMv4-H6VIBr0OiTTFL7SVsmq8ivfdk,11567
40
+ hccinfhir-0.0.6.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
41
+ hccinfhir-0.0.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
42
+ hccinfhir-0.0.6.dist-info/RECORD,,