medicafe 0.250814.4__tar.gz → 0.250816.0__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 (79) hide show
  1. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot.py +69 -37
  2. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Preprocessor_lib.py +97 -7
  3. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_UI.py +73 -9
  4. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/__init__.py +1 -1
  5. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/MediLink_ConfigLoader.py +60 -13
  6. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/__init__.py +5 -2
  7. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/smart_import.py +12 -0
  8. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/__init__.py +2 -1
  9. {medicafe-0.250814.4 → medicafe-0.250816.0}/PKG-INFO +1 -1
  10. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/PKG-INFO +1 -1
  11. {medicafe-0.250814.4 → medicafe-0.250816.0}/setup.py +1 -1
  12. {medicafe-0.250814.4 → medicafe-0.250816.0}/LICENSE +0 -0
  13. {medicafe-0.250814.4 → medicafe-0.250816.0}/MANIFEST.in +0 -0
  14. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot.bat +0 -0
  15. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Charges.py +0 -0
  16. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
  17. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
  18. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Post.py +0 -0
  19. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_Preprocessor.py +0 -0
  20. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_dataformat_library.py +0 -0
  21. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_docx_decoder.py +0 -0
  22. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/MediBot_smart_import.py +0 -0
  23. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/get_medicafe_version.py +0 -0
  24. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/update_json.py +0 -0
  25. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediBot/update_medicafe.py +0 -0
  26. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/__main__.py +0 -0
  27. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/api_core.py +0 -0
  28. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/api_core_backup.py +0 -0
  29. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/api_factory.py +0 -0
  30. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/api_utils.py +0 -0
  31. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/core_utils.py +0 -0
  32. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/graphql_utils.py +0 -0
  33. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/logging_config.py +0 -0
  34. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/logging_demo.py +0 -0
  35. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/migration_helpers.py +0 -0
  36. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediCafe/submission_index.py +0 -0
  37. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/InsuranceTypeService.py +0 -0
  38. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_837p_cob_library.py +0 -0
  39. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_837p_encoder.py +0 -0
  40. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_837p_encoder_library.py +0 -0
  41. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_837p_utilities.py +0 -0
  42. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_API_Generator.py +0 -0
  43. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Azure.py +0 -0
  44. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_ClaimStatus.py +0 -0
  45. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_DataMgmt.py +0 -0
  46. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Decoder.py +0 -0
  47. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Deductible.py +0 -0
  48. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Deductible_Validator.py +0 -0
  49. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Display_Utils.py +0 -0
  50. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Down.py +0 -0
  51. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Gmail.py +0 -0
  52. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Mailer.py +0 -0
  53. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Parser.py +0 -0
  54. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_PatientProcessor.py +0 -0
  55. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Scan.py +0 -0
  56. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Scheduler.py +0 -0
  57. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_UI.py +0 -0
  58. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_Up.py +0 -0
  59. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_insurance_utils.py +0 -0
  60. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_main.py +0 -0
  61. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/MediLink_smart_import.py +0 -0
  62. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/Soumit_api.py +0 -0
  63. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/gmail_http_utils.py +0 -0
  64. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/gmail_oauth_utils.py +0 -0
  65. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/insurance_type_integration_test.py +0 -0
  66. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/openssl.cnf +0 -0
  67. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/test.py +0 -0
  68. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/test_cob_library.py +0 -0
  69. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/test_timing.py +0 -0
  70. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/test_validation.py +0 -0
  71. {medicafe-0.250814.4 → medicafe-0.250816.0}/MediLink/webapp.html +0 -0
  72. {medicafe-0.250814.4 → medicafe-0.250816.0}/README.md +0 -0
  73. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/SOURCES.txt +0 -0
  74. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/dependency_links.txt +0 -0
  75. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/entry_points.txt +0 -0
  76. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/not-zip-safe +0 -0
  77. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/requires.txt +0 -0
  78. {medicafe-0.250814.4 → medicafe-0.250816.0}/medicafe.egg-info/top_level.txt +0 -0
  79. {medicafe-0.250814.4 → medicafe-0.250816.0}/setup.cfg +0 -0
@@ -11,6 +11,7 @@ try:
11
11
  except ImportError:
12
12
  msvcrt = None # Not available on non-Windows systems
13
13
  from collections import OrderedDict
14
+ from datetime import datetime # Added for primary surgery date logic
14
15
 
15
16
  # ============================================================================
16
17
  # MINIMAL PROTECTION: Import State Validation
@@ -171,6 +172,68 @@ def identify_field(header, field_mapping):
171
172
  # Add this print to a function that is calling identify_field
172
173
  #print("Warning: No matching field found for CSV header '{}'".format(header))
173
174
 
175
+ def create_patient_entries_from_row(row, reverse_mapping):
176
+ """
177
+ Helper function to create patient entries from a row with surgery date handling.
178
+
179
+ Args:
180
+ row: The CSV row containing patient data
181
+ reverse_mapping: The reverse mapping for field lookups
182
+
183
+ Returns:
184
+ list: List of tuples (surgery_date, patient_name, patient_id, diagnosis_code, row)
185
+ """
186
+ patient_id = row.get(reverse_mapping['Patient ID #2'])
187
+ patient_name = row.get(reverse_mapping['Patient Name'])
188
+
189
+ # Get all surgery dates for this patient
190
+ all_surgery_dates = row.get('_all_surgery_dates', [row.get('Surgery Date')])
191
+ surgery_date_to_diagnosis = row.get('_surgery_date_to_diagnosis', {})
192
+
193
+ patient_entries = []
194
+
195
+ # Sort surgery dates chronologically to ensure proper ordering
196
+ sorted_surgery_dates = []
197
+ for surgery_date in all_surgery_dates:
198
+ try:
199
+ if hasattr(surgery_date, 'strftime'):
200
+ # Already a datetime object
201
+ sorted_surgery_dates.append(surgery_date)
202
+ elif isinstance(surgery_date, str):
203
+ # Convert string to datetime for sorting
204
+ surgery_date_dt = datetime.strptime(surgery_date, '%m-%d-%Y')
205
+ sorted_surgery_dates.append(surgery_date_dt)
206
+ else:
207
+ # Fallback - use as is
208
+ sorted_surgery_dates.append(surgery_date)
209
+ except (ValueError, TypeError):
210
+ # If parsing fails, use the original value
211
+ sorted_surgery_dates.append(surgery_date)
212
+
213
+ # Sort the dates chronologically
214
+ sorted_surgery_dates.sort()
215
+
216
+ # Create entries for each surgery date in chronological order
217
+ # The enhanced table display will group by patient_id and show dashed lines for secondary dates
218
+ for surgery_date in sorted_surgery_dates:
219
+ try:
220
+ if hasattr(surgery_date, 'strftime'):
221
+ surgery_date_str = surgery_date.strftime('%m-%d-%Y')
222
+ elif isinstance(surgery_date, str):
223
+ surgery_date_str = surgery_date
224
+ else:
225
+ surgery_date_str = str(surgery_date)
226
+ except Exception:
227
+ surgery_date_str = str(surgery_date)
228
+
229
+ # Get the diagnosis code for this surgery date
230
+ diagnosis_code = surgery_date_to_diagnosis.get(surgery_date_str, 'N/A')
231
+
232
+ # Add entry for this surgery date
233
+ patient_entries.append((surgery_date, patient_name, patient_id, diagnosis_code, row))
234
+
235
+ return patient_entries
236
+
174
237
  # Global flag to control AHK execution method - set to True to use optimized stdin method
175
238
  USE_AHK_STDIN_OPTIMIZATION = True
176
239
 
@@ -695,23 +758,9 @@ if __name__ == "__main__":
695
758
  if patient_row is None:
696
759
  raise ValueError("Patient row not found for patient ID: {}".format(patient_id))
697
760
 
698
- # Get all surgery dates for this patient
699
- all_surgery_dates = patient_row.get('_all_surgery_dates', [patient_row.get('Surgery Date')])
700
- surgery_date_to_diagnosis = patient_row.get('_surgery_date_to_diagnosis', {})
701
-
702
- # Create entries for each surgery date with its specific diagnosis code
703
- for surgery_date in all_surgery_dates:
704
- # Convert surgery_date to string format for consistent lookup (XP SP3 + Py3.4.4 compatible)
705
- try:
706
- if hasattr(surgery_date, 'strftime'):
707
- surgery_date_str = surgery_date.strftime('%m-%d-%Y')
708
- else:
709
- surgery_date_str = str(surgery_date)
710
- except Exception:
711
- surgery_date_str = str(surgery_date)
712
-
713
- diagnosis_code = surgery_date_to_diagnosis.get(surgery_date_str, 'N/A')
714
- patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, patient_row))
761
+ # Use helper function to create patient entries
762
+ patient_entries = create_patient_entries_from_row(patient_row, reverse_mapping)
763
+ patient_info.extend(patient_entries)
715
764
 
716
765
  except Exception as e:
717
766
  MediLink_ConfigLoader.log("Warning: Error retrieving data for patient ID '{}': {}".format(patient_id, e), level="WARNING")
@@ -733,26 +782,9 @@ if __name__ == "__main__":
733
782
  # Collect surgery dates and patient info for NEW patients
734
783
  new_patient_info = []
735
784
  for row in csv_data:
736
- patient_id = row.get(reverse_mapping['Patient ID #2'])
737
- patient_name = row.get(reverse_mapping['Patient Name'])
738
-
739
- # Get all surgery dates for this patient
740
- all_surgery_dates = row.get('_all_surgery_dates', [row.get('Surgery Date')])
741
- surgery_date_to_diagnosis = row.get('_surgery_date_to_diagnosis', {})
742
-
743
- # Create entries for each surgery date with its specific diagnosis code
744
- for surgery_date in all_surgery_dates:
745
- # Convert surgery_date to string format for consistent lookup (XP SP3 + Py3.4.4 compatible)
746
- try:
747
- if hasattr(surgery_date, 'strftime'):
748
- surgery_date_str = surgery_date.strftime('%m-%d-%Y')
749
- else:
750
- surgery_date_str = str(surgery_date)
751
- except Exception:
752
- surgery_date_str = str(surgery_date)
753
-
754
- diagnosis_code = surgery_date_to_diagnosis.get(surgery_date_str, 'N/A')
755
- new_patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, row))
785
+ # Use helper function to create patient entries
786
+ patient_entries = create_patient_entries_from_row(row, reverse_mapping)
787
+ new_patient_info.extend(patient_entries)
756
788
 
757
789
  # Display new patients table using the enhanced display function
758
790
  MediBot_UI.display_enhanced_patient_table(
@@ -531,17 +531,41 @@ def sort_and_deduplicate(csv_data):
531
531
  existing_row = unique_patients[patient_id]
532
532
  existing_date = existing_row['Surgery Date']
533
533
 
534
+ # Ensure both dates are comparable by converting to datetime objects
535
+ def normalize_date_for_comparison(date_value):
536
+ if isinstance(date_value, datetime):
537
+ return date_value
538
+ elif isinstance(date_value, str) and date_value.strip():
539
+ try:
540
+ # Try to parse the string as a date
541
+ return datetime.strptime(date_value, '%m/%d/%Y')
542
+ except ValueError:
543
+ try:
544
+ return datetime.strptime(date_value, '%m-%d-%Y')
545
+ except ValueError:
546
+ # If parsing fails, return minimum datetime
547
+ return datetime.min
548
+ else:
549
+ # Empty or invalid values get minimum datetime
550
+ return datetime.min
551
+
552
+ normalized_surgery_date = normalize_date_for_comparison(surgery_date)
553
+ normalized_existing_date = normalize_date_for_comparison(existing_date)
554
+
534
555
  # Keep the most current demographic data (later surgery date takes precedence)
535
- if surgery_date > existing_date:
556
+ if normalized_surgery_date > normalized_existing_date:
536
557
  # Store the old row's surgery date before replacing
537
558
  old_date = existing_row['Surgery Date']
538
- patient_surgery_dates[patient_id].append(old_date)
559
+ # Add the old date to the list if it's not already there
560
+ if old_date not in patient_surgery_dates[patient_id]:
561
+ patient_surgery_dates[patient_id].append(old_date)
539
562
  # Replace with newer row (better demographics)
540
563
  unique_patients[patient_id] = row
541
- # Update the surgery dates list to reflect the new primary date
542
- patient_surgery_dates[patient_id] = [surgery_date] + [d for d in patient_surgery_dates[patient_id] if d != surgery_date]
564
+ # Add the new surgery date to the list if it's not already there
565
+ if surgery_date not in patient_surgery_dates[patient_id]:
566
+ patient_surgery_dates[patient_id].append(surgery_date)
543
567
  else:
544
- # Add this surgery date to the list for this patient
568
+ # Add this surgery date to the list for this patient if it's not already there
545
569
  if surgery_date not in patient_surgery_dates[patient_id]:
546
570
  patient_surgery_dates[patient_id].append(surgery_date)
547
571
 
@@ -558,11 +582,77 @@ def sort_and_deduplicate(csv_data):
558
582
  else:
559
583
  surgery_date_strings.append(str(date) if date else 'MISSING')
560
584
 
561
- row['_all_surgery_dates'] = sorted(surgery_date_strings)
585
+ # Remove duplicates and sort
586
+ unique_surgery_dates = list(set(surgery_date_strings))
587
+ sorted_surgery_dates = sorted(unique_surgery_dates, key=lambda x: datetime.strptime(x, '%m-%d-%Y') if x != 'MISSING' else datetime.min)
588
+ row['_all_surgery_dates'] = sorted_surgery_dates
562
589
  row['_primary_surgery_date'] = row['Surgery Date'] # Keep track of which date has the demographics
590
+ # Compute and store earliest surgery date for emission sort
591
+ earliest_dt = None
592
+ earliest_str = None
593
+ for d in sorted_surgery_dates:
594
+ if d and d != 'MISSING':
595
+ try:
596
+ earliest_dt = datetime.strptime(d, '%m-%d-%Y')
597
+ earliest_str = d
598
+ break
599
+ except Exception:
600
+ pass
601
+ # Fallback to demographics date if earliest could not be determined
602
+ if earliest_str is None:
603
+ try:
604
+ sd = row.get('Surgery Date')
605
+ if isinstance(sd, datetime) and sd != datetime.min:
606
+ earliest_dt = sd
607
+ earliest_str = sd.strftime('%m-%d-%Y')
608
+ elif isinstance(sd, str) and sd.strip():
609
+ try:
610
+ earliest_dt = datetime.strptime(sd, '%m/%d/%Y')
611
+ except Exception:
612
+ try:
613
+ earliest_dt = datetime.strptime(sd, '%m-%d-%Y')
614
+ except Exception:
615
+ earliest_dt = None
616
+ earliest_str = sd
617
+ except Exception:
618
+ earliest_dt = None
619
+ earliest_str = None
620
+ row['_earliest_surgery_date'] = earliest_str
621
+
622
+
563
623
 
564
624
  # Convert the unique_patients dictionary back to a list and sort it
565
- csv_data[:] = sorted(unique_patients.values(), key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip())) # TODO Does this need to be sorted twice? once before and once after?
625
+ # Use the same normalization function for consistent sorting
626
+ def sort_key(row):
627
+ # Prefer earliest surgery date across all known dates for the patient
628
+ earliest = row.get('_earliest_surgery_date')
629
+ if isinstance(earliest, str) and earliest and earliest != 'MISSING':
630
+ try:
631
+ normalized_date = datetime.strptime(earliest, '%m-%d-%Y')
632
+ except Exception:
633
+ normalized_date = datetime.min
634
+ else:
635
+ # Fallback to the single Surgery Date field
636
+ surgery_date = row.get('Surgery Date')
637
+ if isinstance(surgery_date, datetime):
638
+ normalized_date = surgery_date
639
+ elif isinstance(surgery_date, str) and surgery_date.strip():
640
+ try:
641
+ normalized_date = datetime.strptime(surgery_date, '%m/%d/%Y')
642
+ except ValueError:
643
+ try:
644
+ normalized_date = datetime.strptime(surgery_date, '%m-%d-%Y')
645
+ except ValueError:
646
+ normalized_date = datetime.min
647
+ else:
648
+ normalized_date = datetime.min
649
+ # Tie-break per requirement: last name (case-insensitive), then first name, then patient id
650
+ last_name = ((row.get('Patient Last') or '')).strip().upper()
651
+ first_name = ((row.get('Patient First') or '')).strip().upper()
652
+ patient_id_tiebreak = str(row.get('Patient ID') or '')
653
+ return (normalized_date, last_name, first_name, patient_id_tiebreak)
654
+
655
+ csv_data[:] = sorted(unique_patients.values(), key=sort_key) # TODO Does this need to be sorted twice? once before and once after?
566
656
 
567
657
  # TODO: Consider adding an option in the config to sort based on Surgery Schedules when available.
568
658
  # If no schedule is available, the current sorting strategy will be used.
@@ -48,26 +48,29 @@ def display_enhanced_patient_table(patient_info, title, show_line_numbers=True):
48
48
  for surgery_date, patient_name, patient_id, diagnosis_code, patient_row in patient_info:
49
49
  # Normalize date into comparable key and display string
50
50
  display_date = None
51
- sort_key = None
51
+ current_date_dt = None
52
52
  try:
53
53
  if hasattr(surgery_date, 'strftime'):
54
54
  display_date = surgery_date.strftime('%m-%d')
55
- sort_key = surgery_date
55
+ current_date_dt = surgery_date
56
56
  elif isinstance(surgery_date, str):
57
57
  # Date strings may be MM-DD-YYYY or already MM-DD
58
58
  parts = surgery_date.split('-') if surgery_date else []
59
59
  if len(parts) == 3 and all(parts):
60
60
  display_date = "{}-{}".format(parts[0], parts[1])
61
+ try:
62
+ current_date_dt = datetime.strptime(surgery_date, '%m-%d-%Y')
63
+ except Exception:
64
+ current_date_dt = None
61
65
  else:
62
66
  display_date = surgery_date or 'Unknown Date'
63
- # Use the raw string as sort key fallback
64
- sort_key = surgery_date or ''
67
+ current_date_dt = None
65
68
  else:
66
69
  display_date = str(surgery_date) if surgery_date is not None else 'Unknown Date'
67
- sort_key = display_date
70
+ current_date_dt = None
68
71
  except Exception:
69
72
  display_date = str(surgery_date) if surgery_date is not None else 'Unknown Date'
70
- sort_key = display_date
73
+ current_date_dt = None
71
74
 
72
75
  # Normalize diagnosis display: only show "-Not Found-" when explicitly flagged as N/A
73
76
  # XP SP3 + Py3.4.4 compatible error handling
@@ -90,11 +93,72 @@ def display_enhanced_patient_table(patient_info, title, show_line_numbers=True):
90
93
  # Fallback logging if string formatting fails
91
94
  MediLink_ConfigLoader.log("Error converting diagnosis code to string", level="WARNING")
92
95
  display_diagnosis = "-Not Found-"
96
+
97
+ # Grouping: place all dates for a patient together under their earliest date
98
+ primary_date_dt = None
99
+ within_index = 0
100
+ last_name_key = ''
101
+ first_name_key = ''
102
+ try:
103
+ all_dates = []
104
+ if patient_row is not None:
105
+ raw_dates = patient_row.get('_all_surgery_dates', [])
106
+ # Convert to datetime list and find primary
107
+ for d in raw_dates:
108
+ try:
109
+ if hasattr(d, 'strftime'):
110
+ all_dates.append(d)
111
+ elif isinstance(d, str):
112
+ all_dates.append(datetime.strptime(d, '%m-%d-%Y'))
113
+ except Exception:
114
+ pass
115
+ if all_dates:
116
+ all_dates.sort()
117
+ primary_date_dt = all_dates[0]
118
+ # Determine within-patient index of current date
119
+ if current_date_dt is not None:
120
+ # Find matching index by exact date
121
+ for idx, ad in enumerate(all_dates):
122
+ if current_date_dt == ad:
123
+ within_index = idx
124
+ break
125
+ # Prefer explicit last/first from row for sorting
126
+ try:
127
+ ln = patient_row.get('Patient Last')
128
+ fn = patient_row.get('Patient First')
129
+ if isinstance(ln, str):
130
+ last_name_key = ln.strip().upper()
131
+ if isinstance(fn, str):
132
+ first_name_key = fn.strip().upper()
133
+ except Exception:
134
+ pass
135
+ except Exception:
136
+ primary_date_dt = None
137
+ within_index = 0
138
+
139
+ # Fallbacks if parsing failed
140
+ if primary_date_dt is None:
141
+ primary_date_dt = current_date_dt
142
+ # If last/first not available from row, parse from display name "LAST, FIRST ..."
143
+ if not last_name_key and isinstance(patient_name, str):
144
+ try:
145
+ parts = [p.strip() for p in patient_name.split(',')]
146
+ if len(parts) >= 1:
147
+ last_name_key = parts[0].upper()
148
+ if len(parts) >= 2:
149
+ first_name_key = parts[1].split()[0].upper() if parts[1] else ''
150
+ except Exception:
151
+ last_name_key = ''
152
+ first_name_key = ''
153
+
154
+ # Build composite sort key per requirement: by earliest date, then last name within date,
155
+ # while keeping same patient's additional dates directly under the first line
156
+ composite_sort_key = (primary_date_dt, last_name_key, first_name_key, str(patient_id or ''), within_index)
93
157
 
94
- normalized_info.append((sort_key, display_date, str(patient_name or ''), str(patient_id or ''), display_diagnosis))
158
+ normalized_info.append((composite_sort_key, display_date, str(patient_name or ''), str(patient_id or ''), display_diagnosis))
95
159
 
96
- # Sort by normalized sort key then patient name
97
- normalized_info.sort(key=lambda x: (x[0], x[2]))
160
+ # Sort so that all entries for a patient are grouped under their earliest date
161
+ normalized_info.sort(key=lambda x: x[0])
98
162
 
99
163
  # Calculate column widths for proper alignment
100
164
  max_patient_id_len = max(len(pid) for _, _, _, pid, _ in normalized_info)
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.250814.4"
22
+ __version__ = "0.250816.0"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25
 
@@ -18,6 +18,32 @@ except ImportError:
18
18
  # Fallback to local flag if centralized config is not available
19
19
  PERFORMANCE_LOGGING = False
20
20
 
21
+ def get_default_config():
22
+ """Provide a default configuration when config files are missing."""
23
+ return {
24
+ 'MediLink_Config': {
25
+ 'local_storage_path': '.',
26
+ 'receiptsRoot': './receipts',
27
+ 'api_endpoints': {
28
+ 'base_url': 'https://api.example.com',
29
+ 'timeout': 30
30
+ },
31
+ 'logging': {
32
+ 'level': 'INFO',
33
+ 'console_output': True
34
+ }
35
+ }
36
+ }
37
+
38
+ def get_default_crosswalk():
39
+ """Provide a default crosswalk when crosswalk files are missing."""
40
+ return {
41
+ 'insurance_types': {},
42
+ 'diagnosis_codes': {},
43
+ 'procedure_codes': {},
44
+ 'payer_mappings': {}
45
+ }
46
+
21
47
  """
22
48
  This function should be generalizable to have a initialization script over all the Medi* functions
23
49
  """
@@ -62,6 +88,10 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
62
88
  if PERFORMANCE_LOGGING:
63
89
  print("Path resolution completed in {:.2f} seconds".format(path_check_end - path_check_start))
64
90
 
91
+ # Load configuration with graceful fallback
92
+ config = None
93
+ crosswalk = None
94
+
65
95
  try:
66
96
  config_load_start = time.time()
67
97
  with open(config_path, 'r') as config_file:
@@ -88,30 +118,47 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
88
118
  if PERFORMANCE_LOGGING:
89
119
  print("Crosswalk file loading completed in {:.2f} seconds".format(crosswalk_load_end - crosswalk_load_start))
90
120
 
91
- config_end = time.time()
92
- if PERFORMANCE_LOGGING:
93
- print("Total configuration loading completed in {:.2f} seconds".format(config_end - config_start))
121
+ except FileNotFoundError:
122
+ # Graceful fallback to default configurations
123
+ print("Configuration files not found. Using default configurations.")
124
+ print("Config path: {}, Crosswalk path: {}".format(config_path, crosswalk_path))
125
+ print("To use custom configurations, create the 'json' directory and add config.json and crosswalk.json files.")
94
126
 
95
- # Cache the loaded configuration
96
- _CONFIG_CACHE = config
97
- _CROSSWALK_CACHE = crosswalk
127
+ config = get_default_config()
128
+ crosswalk = get_default_crosswalk()
98
129
 
99
- return config, crosswalk
130
+ # Log the fallback for debugging
131
+ if PERFORMANCE_LOGGING:
132
+ print("Using default configurations due to missing files")
133
+
100
134
  except ValueError as e:
101
135
  if isinstance(e, UnicodeDecodeError):
102
136
  print("Error decoding file: {}".format(e))
103
137
  else:
104
138
  print("Error parsing file: {}".format(e))
105
- sys.exit(1) # Exit the script due to a critical error in configuration loading
106
- except FileNotFoundError:
107
- print("One or both configuration files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
108
- raise
139
+ print("Falling back to default configurations...")
140
+ config = get_default_config()
141
+ crosswalk = get_default_crosswalk()
109
142
  except KeyError as e:
110
143
  print("Critical configuration is missing: {}".format(e))
111
- raise
144
+ print("Falling back to default configurations...")
145
+ config = get_default_config()
146
+ crosswalk = get_default_crosswalk()
112
147
  except Exception as e:
113
148
  print("An unexpected error occurred while loading the configuration: {}".format(e))
114
- raise
149
+ print("Falling back to default configurations...")
150
+ config = get_default_config()
151
+ crosswalk = get_default_crosswalk()
152
+
153
+ config_end = time.time()
154
+ if PERFORMANCE_LOGGING:
155
+ print("Total configuration loading completed in {:.2f} seconds".format(config_end - config_start))
156
+
157
+ # Cache the loaded configuration
158
+ _CONFIG_CACHE = config
159
+ _CROSSWALK_CACHE = crosswalk
160
+
161
+ return config, crosswalk
115
162
 
116
163
  def clear_config_cache():
117
164
  """Clear the configuration cache to force reloading on next call."""
@@ -27,7 +27,7 @@ Smart Import System:
27
27
  api_suite = get_api_access()
28
28
  """
29
29
 
30
- __version__ = "0.250814.4"
30
+ __version__ = "0.250816.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33
 
@@ -83,8 +83,11 @@ try:
83
83
  from . import logging_config
84
84
  from . import core_utils
85
85
  __legacy_imports_available__ = True
86
- except Exception:
86
+ except Exception as e:
87
87
  __legacy_imports_available__ = False
88
+ # Don't fail the entire package import if legacy imports fail
89
+ print("Warning: Some legacy imports failed: {}".format(e))
90
+ print("This is expected when configuration files are missing. The package will continue to function.")
88
91
 
89
92
  # Package information
90
93
  __all__ = [
@@ -162,6 +162,18 @@ class ComponentRegistry:
162
162
  module_path = component_mappings[name]
163
163
  try:
164
164
  module = importlib.import_module(module_path)
165
+
166
+ # Special handling for components that may fail due to missing config files
167
+ if name in ['api_core', 'logging_config', 'core_utils']:
168
+ try:
169
+ # Test if the module can initialize properly
170
+ if hasattr(module, 'load_configuration'):
171
+ # This will use our new graceful fallback
172
+ module.load_configuration()
173
+ except Exception as config_error:
174
+ # Log the config error but don't fail the import
175
+ warnings.warn("Component '{}' loaded but configuration failed: {}".format(name, config_error))
176
+
165
177
  return module
166
178
  except ImportError as e:
167
179
  raise ImportError("Failed to load component '{}' from '{}': {}".format(name, module_path, e))
@@ -22,7 +22,7 @@ Smart Import Integration:
22
22
  datamgmt = get_components('medilink_datamgmt')
23
23
  """
24
24
 
25
- __version__ = "0.250814.4"
25
+ __version__ = "0.250816.0"
26
26
  __author__ = "Daniel Vidaud"
27
27
  __email__ = "daniel@personalizedtransformation.com"
28
28
 
@@ -98,6 +98,7 @@ except Exception as e:
98
98
  # Handle any other import errors
99
99
  import sys
100
100
  print("Warning: Unexpected error importing MediLink_insurance_utils: {}".format(e))
101
+ print("This may be due to missing configuration files. MediLink will continue to function.")
101
102
  MediLink_insurance_utils = None
102
103
 
103
104
  # Optional: Show guide on import (can be disabled)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250814.4
3
+ Version: 0.250816.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250814.4
3
+ Version: 0.250816.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -54,7 +54,7 @@ if long_description_text is None:
54
54
 
55
55
  setup(
56
56
  name='medicafe',
57
- version="0.250814.4",
57
+ version="0.250816.0",
58
58
  description='MediCafe',
59
59
  long_description=long_description_text,
60
60
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes