hccinfhir 0.2.7__tar.gz → 0.2.8__tar.gz

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.
Files changed (62) hide show
  1. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/PKG-INFO +1 -1
  2. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/datamodels.py +1 -1
  3. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/extractor_834.py +43 -5
  4. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/pyproject.toml +1 -1
  5. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/.gitignore +0 -0
  6. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/LICENSE +0 -0
  7. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/README.md +0 -0
  8. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/__init__.py +0 -0
  9. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/constants.py +0 -0
  10. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/__init__.py +0 -0
  11. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/hcc_is_chronic.csv +0 -0
  12. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/hcc_is_chronic_without_esrd_model.csv +0 -0
  13. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ph_race_and_ethnicity_cdc_v1.3.csv +0 -0
  14. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_coefficients_2025.csv +0 -0
  15. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_coefficients_2026.csv +0 -0
  16. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_dx_to_cc_2025.csv +0 -0
  17. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_dx_to_cc_2026.csv +0 -0
  18. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_eligible_cpt_hcpcs_2023.csv +0 -0
  19. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_eligible_cpt_hcpcs_2024.csv +0 -0
  20. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_eligible_cpt_hcpcs_2025.csv +0 -0
  21. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_eligible_cpt_hcpcs_2026.csv +0 -0
  22. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_hierarchies_2025.csv +0 -0
  23. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_hierarchies_2026.csv +0 -0
  24. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/data/ra_labels_2026.csv +0 -0
  25. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/defaults.py +0 -0
  26. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/extractor.py +0 -0
  27. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/extractor_837.py +0 -0
  28. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/extractor_fhir.py +0 -0
  29. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/filter.py +0 -0
  30. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/hccinfhir.py +0 -0
  31. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_calculate.py +0 -0
  32. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_coefficients.py +0 -0
  33. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_demographics.py +0 -0
  34. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_dx_to_cc.py +0 -0
  35. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_hierarchies.py +0 -0
  36. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/model_interactions.py +0 -0
  37. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/__init__.py +0 -0
  38. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_01.txt +0 -0
  39. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_02.txt +0 -0
  40. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_03.txt +0 -0
  41. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_04.txt +0 -0
  42. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_05.txt +0 -0
  43. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_834_06.txt +0 -0
  44. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_0.txt +0 -0
  45. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_1.txt +0 -0
  46. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_10.txt +0 -0
  47. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_11.txt +0 -0
  48. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_12.txt +0 -0
  49. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_2.txt +0 -0
  50. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_3.txt +0 -0
  51. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_4.txt +0 -0
  52. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_5.txt +0 -0
  53. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_6.txt +0 -0
  54. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_7.txt +0 -0
  55. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_8.txt +0 -0
  56. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_837_9.txt +0 -0
  57. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_eob_1.json +0 -0
  58. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_eob_2.json +0 -0
  59. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_eob_200.ndjson +0 -0
  60. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/sample_files/sample_eob_3.json +0 -0
  61. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/samples.py +0 -0
  62. {hccinfhir-0.2.7 → hccinfhir-0.2.8}/hccinfhir/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hccinfhir
3
- Version: 0.2.7
3
+ Version: 0.2.8
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
@@ -276,7 +276,7 @@ class EnrollmentData(BaseModel):
276
276
  is_partial_benefit_dual: Partial Benefit Dual (uses CPA_/CPD_ prefix)
277
277
  medicare_status_code: QMB, SLMB, QI, QDWI, etc.
278
278
  medi_cal_aid_code: California Medi-Cal aid code
279
- medi_cal_eligibility_status: Medi-Cal eligibility status from REF*6O
279
+ medi_cal_eligibility_status: Medi-Cal eligibility status (derived: "Active"/"Terminated"/None)
280
280
 
281
281
  # CA DHCS / FAME Specific
282
282
  fame_county_id: FAME county ID (REF*ZX or N4*CY)
@@ -216,6 +216,36 @@ def is_new_enrollee(coverage_start_date: Optional[str], reference_date: Optional
216
216
  return False
217
217
 
218
218
 
219
+ def derive_medi_cal_eligibility_status(coverage_end_date: Optional[str], report_date: Optional[str]) -> Optional[str]:
220
+ """Derive Medi-Cal eligibility status from coverage end date and report date.
221
+
222
+ Args:
223
+ coverage_end_date: Coverage end date in YYYY-MM-DD format
224
+ report_date: Report date in YYYY-MM-DD format
225
+
226
+ Returns:
227
+ "Active" if coverage extends through or beyond report month
228
+ "Terminated" if coverage ended before report month
229
+ None if no coverage_end_date
230
+ """
231
+ if not coverage_end_date:
232
+ return None
233
+ try:
234
+ end_date = datetime.strptime(coverage_end_date, "%Y-%m-%d").date()
235
+ if report_date:
236
+ ref_date = datetime.strptime(report_date, "%Y-%m-%d").date()
237
+ else:
238
+ ref_date = date.today()
239
+ # Get first day of report month for comparison
240
+ first_of_report_month = ref_date.replace(day=1)
241
+ if end_date < first_of_report_month:
242
+ return "Terminated"
243
+ else:
244
+ return "Active"
245
+ except (ValueError, AttributeError):
246
+ return None
247
+
248
+
219
249
  def contains_any_keyword(text: str, keywords: set) -> bool:
220
250
  """Check if text contains any of the keywords"""
221
251
  text_upper = text.upper()
@@ -326,10 +356,15 @@ def parse_ref_dx(value: str, member: MemberContext) -> None:
326
356
 
327
357
 
328
358
  def parse_ref_17(value: str, member: MemberContext) -> None:
329
- """REF*17: YYYYMM;;... (FAME redetermination date)"""
359
+ """REF*17: YYYYMM;YYYYMMDD;YYYYMM (redetermination date; death date; reporting month)"""
360
+ # Position 0: FAME redetermination date (YYYYMM)
330
361
  yyyymm = get_composite_part(value, 0)
331
362
  if yyyymm and len(yyyymm) >= 6:
332
363
  member.fame_redetermination_date = f"{yyyymm[:4]}-{yyyymm[4:6]}-01"
364
+ # Position 1: FAME death date (YYYYMMDD)
365
+ death_date_str = get_composite_part(value, 1)
366
+ if death_date_str and len(death_date_str) == 8:
367
+ member.fame_death_date = parse_date(death_date_str)
333
368
 
334
369
 
335
370
  # ============================================================================
@@ -503,6 +538,7 @@ def _finalize_member(member: MemberContext, source: str, report_date: str) -> En
503
538
  dual_code = determine_dual_status(member)
504
539
  is_fbd, is_pbd = classify_dual_benefit_level(dual_code)
505
540
  new_enrollee = is_new_enrollee(member.coverage_start_date)
541
+ medi_cal_elig_status = derive_medi_cal_eligibility_status(member.coverage_end_date, report_date)
506
542
 
507
543
  hcp_history = [
508
544
  HCPCoveragePeriod(
@@ -530,6 +566,7 @@ def _finalize_member(member: MemberContext, source: str, report_date: str) -> En
530
566
  dual_elgbl_cd=dual_code, is_full_benefit_dual=is_fbd, is_partial_benefit_dual=is_pbd,
531
567
  medicare_status_code=member.medicare_status_code,
532
568
  medi_cal_aid_code=member.medi_cal_aid_code,
569
+ medi_cal_eligibility_status=medi_cal_elig_status,
533
570
  fame_county_id=member.fame_county_id, case_number=member.case_number,
534
571
  fame_card_issue_date=member.fame_card_issue_date,
535
572
  fame_redetermination_date=member.fame_redetermination_date,
@@ -583,6 +620,11 @@ def parse_834_enrollment(segments: List[List[str]], source: str = None, report_d
583
620
  member.maintenance_type = get_segment_value(segment, 3)
584
621
  member.maintenance_reason_code = get_segment_value(segment, 4)
585
622
  member.benefit_status_code = get_segment_value(segment, 5)
623
+ # INS12 is Member Death Date when INS11 = D8
624
+ if get_segment_value(segment, 11) == 'D8':
625
+ death_str = get_segment_value(segment, 12)
626
+ if death_str:
627
+ member.death_date = parse_date(death_str)
586
628
 
587
629
  # REF - Reference identifiers
588
630
  elif seg_id == 'REF' and len(segment) >= 3:
@@ -641,9 +683,6 @@ def parse_834_enrollment(segments: List[List[str]], source: str = None, report_d
641
683
  if sex in X12_SEX_CODE_MAPPING:
642
684
  member.sex = X12_SEX_CODE_MAPPING[sex]
643
685
  member.race = parse_race_code(get_segment_value(segment, 5))
644
- death_str = get_segment_value(segment, 6)
645
- if death_str and len(death_str) >= 8:
646
- member.death_date = parse_date(death_str[:8])
647
686
 
648
687
  # DTP - Dates
649
688
  elif seg_id == 'DTP' and len(segment) >= 4:
@@ -675,7 +714,6 @@ def parse_834_enrollment(segments: List[List[str]], source: str = None, report_d
675
714
  member.has_medicare = True
676
715
  elif qualifier == '435':
677
716
  member.death_date = parsed
678
- member.fame_death_date = parsed
679
717
 
680
718
  # HD - Health coverage
681
719
  elif seg_id == 'HD' and len(segment) >= 4:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hccinfhir"
7
- version = "0.2.7"
7
+ version = "0.2.8"
8
8
  authors = [
9
9
  { name="Yubin Park", email="yubin.park@mimilabs.ai" },
10
10
  ]
File without changes
File without changes
File without changes
File without changes
File without changes