medicafe 0.250813.2__tar.gz → 0.250814.4__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 (80) hide show
  1. medicafe-0.250814.4/MANIFEST.in +24 -0
  2. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot.bat +1 -1
  3. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot.py +134 -4
  4. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Preprocessor_lib.py +88 -22
  5. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_UI.py +60 -25
  6. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/__init__.py +1 -1
  7. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/update_medicafe.py +147 -24
  8. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/__init__.py +1 -1
  9. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_837p_encoder_library.py +72 -65
  10. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_DataMgmt.py +11 -9
  11. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/__init__.py +10 -2
  12. {medicafe-0.250813.2/medicafe.egg-info → medicafe-0.250814.4}/PKG-INFO +1 -1
  13. {medicafe-0.250813.2 → medicafe-0.250814.4/medicafe.egg-info}/PKG-INFO +1 -1
  14. {medicafe-0.250813.2 → medicafe-0.250814.4}/setup.py +2 -2
  15. medicafe-0.250813.2/MANIFEST.in +0 -4
  16. {medicafe-0.250813.2 → medicafe-0.250814.4}/LICENSE +0 -0
  17. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Charges.py +0 -0
  18. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Crosswalk_Library.py +0 -0
  19. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
  20. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Post.py +0 -0
  21. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_Preprocessor.py +0 -0
  22. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_dataformat_library.py +0 -0
  23. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_docx_decoder.py +0 -0
  24. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/MediBot_smart_import.py +0 -0
  25. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/get_medicafe_version.py +0 -0
  26. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediBot/update_json.py +0 -0
  27. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/MediLink_ConfigLoader.py +0 -0
  28. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/__main__.py +0 -0
  29. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/api_core.py +0 -0
  30. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/api_core_backup.py +0 -0
  31. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/api_factory.py +0 -0
  32. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/api_utils.py +0 -0
  33. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/core_utils.py +0 -0
  34. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/graphql_utils.py +0 -0
  35. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/logging_config.py +0 -0
  36. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/logging_demo.py +0 -0
  37. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/migration_helpers.py +0 -0
  38. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/smart_import.py +0 -0
  39. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediCafe/submission_index.py +0 -0
  40. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/InsuranceTypeService.py +0 -0
  41. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_837p_cob_library.py +0 -0
  42. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_837p_encoder.py +0 -0
  43. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_837p_utilities.py +0 -0
  44. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_API_Generator.py +0 -0
  45. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Azure.py +0 -0
  46. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_ClaimStatus.py +0 -0
  47. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Decoder.py +0 -0
  48. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Deductible.py +0 -0
  49. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Deductible_Validator.py +0 -0
  50. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Display_Utils.py +0 -0
  51. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Down.py +0 -0
  52. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Gmail.py +0 -0
  53. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Mailer.py +0 -0
  54. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Parser.py +0 -0
  55. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_PatientProcessor.py +0 -0
  56. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Scan.py +0 -0
  57. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Scheduler.py +0 -0
  58. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_UI.py +0 -0
  59. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_Up.py +0 -0
  60. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_insurance_utils.py +0 -0
  61. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_main.py +0 -0
  62. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/MediLink_smart_import.py +0 -0
  63. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/Soumit_api.py +0 -0
  64. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/gmail_http_utils.py +0 -0
  65. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/gmail_oauth_utils.py +0 -0
  66. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/insurance_type_integration_test.py +0 -0
  67. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/openssl.cnf +0 -0
  68. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/test.py +0 -0
  69. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/test_cob_library.py +0 -0
  70. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/test_timing.py +0 -0
  71. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/test_validation.py +0 -0
  72. {medicafe-0.250813.2 → medicafe-0.250814.4}/MediLink/webapp.html +0 -0
  73. {medicafe-0.250813.2 → medicafe-0.250814.4}/README.md +0 -0
  74. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/SOURCES.txt +0 -0
  75. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/dependency_links.txt +0 -0
  76. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/entry_points.txt +0 -0
  77. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/not-zip-safe +0 -0
  78. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/requires.txt +0 -0
  79. {medicafe-0.250813.2 → medicafe-0.250814.4}/medicafe.egg-info/top_level.txt +0 -0
  80. {medicafe-0.250813.2 → medicafe-0.250814.4}/setup.cfg +0 -0
@@ -0,0 +1,24 @@
1
+ include MediBot/MediBot.bat
2
+ include MediLink/openssl.cnf
3
+ include MediLink/*.html
4
+ include MediCafe/*.py
5
+
6
+ # Exclude CSV files and other data files that slow down the build
7
+ exclude *.csv
8
+ exclude *.log
9
+ exclude *.tmp
10
+ exclude *.bak
11
+ exclude __pycache__/*
12
+ exclude *.pyc
13
+ exclude *.pyo
14
+ exclude .git/*
15
+ exclude .gitignore
16
+ exclude .DS_Store
17
+ exclude Thumbs.db
18
+ exclude archive/*
19
+ exclude ERA_TEST_DUMP/*
20
+ exclude input/*
21
+ exclude tmpdl/*
22
+ exclude json/*
23
+ exclude docs/reports/*
24
+ exclude Installers/*
@@ -772,7 +772,7 @@ if "!internet_available!"=="0" (
772
772
  pause >nul
773
773
  goto troubleshooting_menu
774
774
  )
775
- set "rollback_version=0.250529.2"
775
+ set "rollback_version=0.250813.1"
776
776
  echo Forcing reinstall of %medicafe_package%==%rollback_version% with no dependencies...
777
777
  python -m pip install --no-deps --force-reinstall %medicafe_package%==%rollback_version%
778
778
  if errorlevel 1 (
@@ -12,6 +12,55 @@ except ImportError:
12
12
  msvcrt = None # Not available on non-Windows systems
13
13
  from collections import OrderedDict
14
14
 
15
+ # ============================================================================
16
+ # MINIMAL PROTECTION: Import State Validation
17
+ # ============================================================================
18
+
19
+ def validate_critical_imports():
20
+ """Validate that critical imports are in expected state before proceeding"""
21
+ critical_modules = {
22
+ 'MediBot_Preprocessor': None,
23
+ 'MediBot_Preprocessor_lib': None,
24
+ 'MediBot_UI': None,
25
+ 'MediBot_Crosswalk_Library': None
26
+ }
27
+
28
+ # Test imports and capture state
29
+ try:
30
+ print("Testing MediCafe.core_utils import...")
31
+ from MediCafe.core_utils import import_medibot_module_with_debug
32
+ print("MediCafe.core_utils import successful")
33
+
34
+ for module_name in critical_modules.keys():
35
+ print("Testing {} import...".format(module_name))
36
+ try:
37
+ module = import_medibot_module_with_debug(module_name)
38
+ critical_modules[module_name] = module
39
+ if module is None:
40
+ print(" WARNING: {} import returned None".format(module_name))
41
+ else:
42
+ print(" SUCCESS: {} import successful".format(module_name))
43
+ except Exception as e:
44
+ print(" ERROR: {} import failed with exception: {}".format(module_name, e))
45
+ critical_modules[module_name] = None
46
+ except Exception as e:
47
+ print("CRITICAL: Failed to import core utilities: {}".format(e))
48
+ return False, critical_modules
49
+
50
+ # Check for None imports (the specific failure pattern)
51
+ failed_imports = []
52
+ for module_name, module in critical_modules.items():
53
+ if module is None:
54
+ failed_imports.append(module_name)
55
+
56
+ if failed_imports:
57
+ print("CRITICAL: Import failures detected:")
58
+ for failed in failed_imports:
59
+ print(" - {}: Import returned None".format(failed))
60
+ return False, critical_modules
61
+
62
+ return True, critical_modules
63
+
15
64
  # Use core utilities for standardized imports
16
65
  from MediCafe.core_utils import (
17
66
  import_medibot_module_with_debug,
@@ -512,6 +561,19 @@ if __name__ == "__main__":
512
561
  try:
513
562
  if PERFORMANCE_LOGGING:
514
563
  print("Initializing. Loading configuration and preparing environment...")
564
+
565
+ # PROTECTION: Validate critical imports before proceeding
566
+ print("Validating critical imports...")
567
+ import_valid, import_state = validate_critical_imports()
568
+ if not import_valid:
569
+ print("CRITICAL: Import validation failed. Cannot continue.")
570
+ print("This indicates a fundamental system configuration issue.")
571
+ print("Please check:")
572
+ print(" 1. All MediBot modules exist in the MediBot directory")
573
+ print(" 2. Python path is correctly configured")
574
+ print(" 3. No syntax errors in MediBot modules")
575
+ sys.exit(1)
576
+
515
577
  # Use MediCafe configuration system
516
578
  try:
517
579
  config_loader = get_config_loader_with_fallback()
@@ -553,6 +615,22 @@ if __name__ == "__main__":
553
615
  print("Starting CSV preprocessing at: {}".format(time.strftime("%H:%M:%S")))
554
616
  MediLink_ConfigLoader.log("Starting CSV preprocessing at: {}".format(time.strftime("%H:%M:%S")), level="INFO")
555
617
 
618
+ # PROTECTION: Validate MediBot_Preprocessor before calling preprocess_csv_data
619
+ if MediBot_Preprocessor is None:
620
+ print("CRITICAL: MediBot_Preprocessor is None when trying to call preprocess_csv_data")
621
+ print("This indicates the import failed silently during execution.")
622
+ print("Import state at failure:")
623
+ for module_name, module in import_state.items():
624
+ status = "None" if module is None else "OK"
625
+ print(" - {}: {}".format(module_name, status))
626
+ print("Please check for syntax errors or missing dependencies in MediBot modules.")
627
+ sys.exit(1)
628
+
629
+ if not hasattr(MediBot_Preprocessor, 'preprocess_csv_data'):
630
+ print("CRITICAL: MediBot_Preprocessor missing preprocess_csv_data function")
631
+ print("Available functions: {}".format([attr for attr in dir(MediBot_Preprocessor) if not attr.startswith('_')]))
632
+ sys.exit(1)
633
+
556
634
  MediBot_Preprocessor.preprocess_csv_data(csv_data, e_state.crosswalk)
557
635
 
558
636
  # TIMING: End CSV preprocessing timing
@@ -623,7 +701,16 @@ if __name__ == "__main__":
623
701
 
624
702
  # Create entries for each surgery date with its specific diagnosis code
625
703
  for surgery_date in all_surgery_dates:
626
- diagnosis_code = surgery_date_to_diagnosis.get(surgery_date, 'N/A')
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')
627
714
  patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, patient_row))
628
715
 
629
716
  except Exception as e:
@@ -633,7 +720,7 @@ if __name__ == "__main__":
633
720
  # Display existing patients table using the enhanced display function
634
721
  MediBot_UI.display_enhanced_patient_table(
635
722
  patient_info,
636
- "NOTE: The following patient(s) already EXIST in the system but have new dates of service.\n Their diagnosis codes will need to be updated manually by the user to the following list:",
723
+ "NOTE: The following patient(s) already EXIST in the system but may have new dates of service.\n Their diagnosis codes may need to be updated manually by the user to the following list:",
637
724
  show_line_numbers=False
638
725
  )
639
726
 
@@ -655,7 +742,16 @@ if __name__ == "__main__":
655
742
 
656
743
  # Create entries for each surgery date with its specific diagnosis code
657
744
  for surgery_date in all_surgery_dates:
658
- diagnosis_code = surgery_date_to_diagnosis.get(surgery_date, 'N/A')
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')
659
755
  new_patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, row))
660
756
 
661
757
  # Display new patients table using the enhanced display function
@@ -695,7 +791,41 @@ if __name__ == "__main__":
695
791
  if e_state:
696
792
  interaction_mode = 'error' # Switch to error mode
697
793
  error_message = str(e) # Capture the error message
698
- print("An error occurred while running MediBot: {}".format(e))
794
+
795
+ # ENHANCED ERROR DIAGNOSTICS
796
+ print("=" * 60)
797
+ print("MEDIBOT EXECUTION FAILURE")
798
+ print("=" * 60)
799
+ print("Error: {}".format(e))
800
+ print("Error type: {}".format(type(e).__name__))
801
+
802
+ # Check for the specific failure pattern
803
+ if "'NoneType' object has no attribute" in str(e):
804
+ print("DIAGNOSIS: This is the import failure pattern.")
805
+ print("A module import returned None, causing a method call to fail.")
806
+ print("This typically indicates:")
807
+ print(" 1. Syntax error in a MediBot module")
808
+ print(" 2. Missing dependency")
809
+ print(" 3. Import path issue")
810
+ print(" 4. Circular import problem")
811
+
812
+ # Show current import state
813
+ print("Current import state:")
814
+ try:
815
+ import_state = {
816
+ 'MediBot_Preprocessor': MediBot_Preprocessor,
817
+ 'MediBot_Preprocessor_lib': MediBot_Preprocessor_lib,
818
+ 'MediBot_UI': MediBot_UI,
819
+ 'MediBot_Crosswalk_Library': MediBot_Crosswalk_Library
820
+ }
821
+ for module_name, module in import_state.items():
822
+ status = "None" if module is None else "OK"
823
+ print(" - {}: {}".format(module_name, status))
824
+ except Exception as diag_e:
825
+ print(" - Unable to diagnose import state: {}".format(diag_e))
826
+
827
+ print("=" * 60)
828
+
699
829
  # Handle the error by calling user interaction with the error information
700
830
  if 'identified_fields' in locals():
701
831
  _ = user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
@@ -547,7 +547,18 @@ def sort_and_deduplicate(csv_data):
547
547
 
548
548
  # Store the surgery dates information in the first row of each patient for later access
549
549
  for patient_id, row in unique_patients.items():
550
- row['_all_surgery_dates'] = sorted(patient_surgery_dates[patient_id])
550
+ # Convert surgery dates to strings for consistent storage
551
+ surgery_date_strings = []
552
+ for date in patient_surgery_dates[patient_id]:
553
+ if isinstance(date, datetime):
554
+ if date == datetime.min:
555
+ surgery_date_strings.append('MISSING')
556
+ else:
557
+ surgery_date_strings.append(date.strftime('%m-%d-%Y'))
558
+ else:
559
+ surgery_date_strings.append(str(date) if date else 'MISSING')
560
+
561
+ row['_all_surgery_dates'] = sorted(surgery_date_strings)
551
562
  row['_primary_surgery_date'] = row['Surgery Date'] # Keep track of which date has the demographics
552
563
 
553
564
  # Convert the unique_patients dictionary back to a list and sort it
@@ -565,7 +576,7 @@ def combine_fields(csv_data):
565
576
  if surgery_date == datetime.min:
566
577
  row['Surgery Date'] = 'MISSING'
567
578
  else:
568
- row['Surgery Date'] = surgery_date.strftime('%m/%d/%Y')
579
+ row['Surgery Date'] = surgery_date.strftime('%m-%d-%Y')
569
580
  elif surgery_date:
570
581
  # Already a non-empty string
571
582
  row['Surgery Date'] = str(surgery_date)
@@ -1149,30 +1160,69 @@ def update_diagnosis_codes(csv_data):
1149
1160
  MediLink_ConfigLoader.log("Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1150
1161
 
1151
1162
  if surgery_date_str in all_patient_data[patient_id]:
1152
- diagnosis_code, left_or_right_eye, femto_yes_or_no = all_patient_data[patient_id][surgery_date_str]
1163
+ diagnosis_data = all_patient_data[patient_id][surgery_date_str]
1164
+ # XP SP3 + Py3.4.4 compatible tuple unpacking with safety check
1165
+ try:
1166
+ if isinstance(diagnosis_data, (list, tuple)) and len(diagnosis_data) >= 3:
1167
+ diagnosis_code, left_or_right_eye, femto_yes_or_no = diagnosis_data
1168
+ else:
1169
+ # Handle case where diagnosis_data is not a proper tuple
1170
+ diagnosis_code = diagnosis_data if diagnosis_data else None
1171
+ left_or_right_eye = None
1172
+ femto_yes_or_no = None
1173
+ except Exception as e:
1174
+ MediLink_ConfigLoader.log("Error unpacking diagnosis data for Patient ID: {}, Surgery Date: {}: {}".format(
1175
+ patient_id, surgery_date_str, str(e)), level="WARNING")
1176
+ diagnosis_code = None
1177
+ left_or_right_eye = None
1178
+ femto_yes_or_no = None
1179
+
1153
1180
  MediLink_ConfigLoader.log("Found diagnosis data for Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1154
1181
 
1155
- # Convert diagnosis code to Medisoft shorthand format.
1156
- medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, None)
1157
- if medisoft_shorthand is None and diagnosis_code:
1158
- # Use fallback logic for missing mapping
1159
- defaulted_code = diagnosis_code.lstrip('H').lstrip('T8').replace('.', '')[-5:]
1160
- medisoft_shorthand = defaulted_code
1161
- MediLink_ConfigLoader.log("Missing diagnosis mapping for '{}', using fallback code '{}'".format(
1162
- diagnosis_code, medisoft_shorthand), level="WARNING")
1182
+ # Convert diagnosis code to Medisoft shorthand format.
1183
+ # XP SP3 + Py3.4.4 compatible null check
1184
+ if diagnosis_code is None:
1185
+ medisoft_shorthand = 'N/A'
1186
+ MediLink_ConfigLoader.log("Diagnosis code is None for Patient ID: {}, Surgery Date: {}".format(
1187
+ patient_id, surgery_date_str), level="WARNING")
1188
+ else:
1189
+ medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, None)
1190
+ if medisoft_shorthand is None and diagnosis_code:
1191
+ # Use fallback logic for missing mapping (XP SP3 + Py3.4.4 compatible)
1192
+ try:
1193
+ defaulted_code = diagnosis_code.lstrip('H').lstrip('T8').replace('.', '')[-5:]
1194
+ # Basic validation: ensure code is not empty and has reasonable length
1195
+ if defaulted_code and len(defaulted_code) >= 3:
1196
+ medisoft_shorthand = defaulted_code
1197
+ MediLink_ConfigLoader.log("Missing diagnosis mapping for '{}', using fallback code '{}'".format(
1198
+ diagnosis_code, medisoft_shorthand), level="WARNING")
1199
+ else:
1200
+ medisoft_shorthand = 'N/A'
1201
+ MediLink_ConfigLoader.log("Fallback diagnosis code validation failed for '{}', using 'N/A'".format(
1202
+ diagnosis_code), level="WARNING")
1203
+ except Exception as e:
1204
+ medisoft_shorthand = 'N/A'
1205
+ MediLink_ConfigLoader.log("Error in fallback diagnosis code generation for '{}': {}".format(
1206
+ diagnosis_code, str(e)), level="WARNING")
1207
+
1163
1208
  MediLink_ConfigLoader.log("Converted diagnosis code to Medisoft shorthand: {}".format(medisoft_shorthand), level="DEBUG")
1164
1209
 
1165
- surgery_date_to_diagnosis[surgery_date] = medisoft_shorthand
1210
+ surgery_date_to_diagnosis[surgery_date_str] = medisoft_shorthand
1166
1211
  else:
1167
1212
  MediLink_ConfigLoader.log("No matching surgery date found for Patient ID: {} on date {}.".format(patient_id, surgery_date_str), level="INFO")
1168
- surgery_date_to_diagnosis[surgery_date] = 'N/A'
1213
+ surgery_date_to_diagnosis[surgery_date_str] = 'N/A'
1169
1214
 
1170
1215
  # Store the diagnosis mapping for all surgery dates
1171
1216
  row['_surgery_date_to_diagnosis'] = surgery_date_to_diagnosis
1172
1217
 
1173
1218
  # Set the primary diagnosis code (for the main surgery date)
1174
1219
  primary_surgery_date = row.get('Surgery Date')
1175
- primary_diagnosis = surgery_date_to_diagnosis.get(primary_surgery_date, 'N/A')
1220
+ # Convert primary surgery date to string for lookup
1221
+ if isinstance(primary_surgery_date, datetime):
1222
+ primary_surgery_date_str = primary_surgery_date.strftime('%m-%d-%Y')
1223
+ else:
1224
+ primary_surgery_date_str = str(primary_surgery_date)
1225
+ primary_diagnosis = surgery_date_to_diagnosis.get(primary_surgery_date_str, 'N/A')
1176
1226
  row['Default Diagnosis #1'] = primary_diagnosis
1177
1227
 
1178
1228
  updated_count += 1
@@ -1472,12 +1522,28 @@ def capitalize_all_fields(csv_data):
1472
1522
  Returns:
1473
1523
  None: The function modifies the csv_data in place.
1474
1524
  """
1475
- # PERFORMANCE FIX: Optimize uppercase conversion using dict comprehension
1525
+ # PERFORMANCE FIX: Optimize uppercase conversion while preserving complex types
1476
1526
  for row in csv_data:
1477
- # Single-pass update using dict comprehension
1478
- row.update({
1479
- key: (value.upper() if isinstance(value, str)
1480
- else str(value).upper() if value is not None and not isinstance(value, datetime)
1481
- else value)
1482
- for key, value in row.items()
1483
- })
1527
+ updated_row = {}
1528
+ for key, value in row.items():
1529
+ # Preserve internal/derived fields intact (e.g., `_all_surgery_dates`, `_surgery_date_to_diagnosis`)
1530
+ if isinstance(key, str) and key.startswith('_'):
1531
+ updated_row[key] = value
1532
+ continue
1533
+ # Uppercase plain strings
1534
+ if isinstance(value, str):
1535
+ updated_row[key] = value.upper()
1536
+ continue
1537
+ # Preserve complex containers; optionally uppercase their string contents
1538
+ if isinstance(value, list):
1539
+ updated_row[key] = [elem.upper() if isinstance(elem, str) else elem for elem in value]
1540
+ continue
1541
+ if isinstance(value, dict):
1542
+ updated_row[key] = {k: (v.upper() if isinstance(v, str) else v) for k, v in value.items()}
1543
+ continue
1544
+ # Leave datetimes as-is; coerce simple scalars to string upper for consistency
1545
+ if isinstance(value, datetime):
1546
+ updated_row[key] = value
1547
+ else:
1548
+ updated_row[key] = str(value).upper() if value is not None else value
1549
+ row.update(updated_row)
@@ -2,6 +2,7 @@
2
2
  import ctypes, time, re
3
3
  from ctypes import wintypes
4
4
  from sys import exit
5
+ from datetime import datetime
5
6
 
6
7
  # Set up paths using core utilities
7
8
 
@@ -42,53 +43,87 @@ def display_enhanced_patient_table(patient_info, title, show_line_numbers=True):
42
43
  print(title)
43
44
  print()
44
45
 
45
- # Sort by surgery date first and then by patient name
46
- patient_info.sort(key=lambda x: (x[0], x[1]))
46
+ # Normalize data to avoid None and unexpected container types in sort key
47
+ normalized_info = []
48
+ for surgery_date, patient_name, patient_id, diagnosis_code, patient_row in patient_info:
49
+ # Normalize date into comparable key and display string
50
+ display_date = None
51
+ sort_key = None
52
+ try:
53
+ if hasattr(surgery_date, 'strftime'):
54
+ display_date = surgery_date.strftime('%m-%d')
55
+ sort_key = surgery_date
56
+ elif isinstance(surgery_date, str):
57
+ # Date strings may be MM-DD-YYYY or already MM-DD
58
+ parts = surgery_date.split('-') if surgery_date else []
59
+ if len(parts) == 3 and all(parts):
60
+ display_date = "{}-{}".format(parts[0], parts[1])
61
+ else:
62
+ display_date = surgery_date or 'Unknown Date'
63
+ # Use the raw string as sort key fallback
64
+ sort_key = surgery_date or ''
65
+ else:
66
+ display_date = str(surgery_date) if surgery_date is not None else 'Unknown Date'
67
+ sort_key = display_date
68
+ except Exception:
69
+ display_date = str(surgery_date) if surgery_date is not None else 'Unknown Date'
70
+ sort_key = display_date
71
+
72
+ # Normalize diagnosis display: only show "-Not Found-" when explicitly flagged as N/A
73
+ # XP SP3 + Py3.4.4 compatible error handling
74
+ display_diagnosis = diagnosis_code
75
+ try:
76
+ if diagnosis_code == "N/A":
77
+ display_diagnosis = "-Not Found-"
78
+ elif diagnosis_code is None:
79
+ display_diagnosis = "-Not Found-"
80
+ elif isinstance(diagnosis_code, str) and diagnosis_code.strip() == "":
81
+ display_diagnosis = "-Not Found-"
82
+ else:
83
+ display_diagnosis = str(diagnosis_code)
84
+ except (TypeError, ValueError) as e:
85
+ # Log the specific error for debugging (ASCII-only compatible)
86
+ try:
87
+ error_msg = "Error converting diagnosis code to string: {}".format(str(e))
88
+ MediLink_ConfigLoader.log(error_msg, level="WARNING")
89
+ except Exception:
90
+ # Fallback logging if string formatting fails
91
+ MediLink_ConfigLoader.log("Error converting diagnosis code to string", level="WARNING")
92
+ display_diagnosis = "-Not Found-"
93
+
94
+ normalized_info.append((sort_key, display_date, str(patient_name or ''), str(patient_id or ''), display_diagnosis))
95
+
96
+ # Sort by normalized sort key then patient name
97
+ normalized_info.sort(key=lambda x: (x[0], x[2]))
47
98
 
48
99
  # Calculate column widths for proper alignment
49
- max_patient_id_len = max(len(str(pid)) for _, _, pid, _, _ in patient_info)
50
- max_patient_name_len = max(len(pname) for _, pname, _, _, _ in patient_info)
51
- max_diagnosis_len = max(len(dcode) for _, _, _, dcode, _ in patient_info)
100
+ max_patient_id_len = max(len(pid) for _, _, _, pid, _ in normalized_info)
101
+ max_patient_name_len = max(len(pname) for _, _, pname, _, _ in normalized_info)
102
+ max_diagnosis_len = max(len(dcode) for _, _, _, _, dcode in normalized_info)
52
103
 
53
104
  # Ensure minimum widths for readability
54
105
  max_patient_id_len = max(max_patient_id_len, 5) # 5-digit ID max
55
106
  max_patient_name_len = max(max_patient_name_len, 12) # "Patient Name" header
56
107
  max_diagnosis_len = max(max_diagnosis_len, 10) # "Diagnosis" header
57
108
 
58
- # Print the sorted patient info with multiple surgery dates
59
109
  current_patient = None
60
110
  line_number = 1
61
111
 
62
- for surgery_date, patient_name, patient_id, diagnosis_code, patient_row in patient_info:
63
- # Format surgery_date safely whether it's a datetime/date or a string
64
- try:
65
- formatted_date = surgery_date.strftime('%m-%d')
66
- except Exception:
67
- formatted_date = str(surgery_date)
68
-
69
- # Transform diagnosis code display: show "-Not Found-" instead of "N/A"
70
- display_diagnosis = "-Not Found-" if diagnosis_code == "N/A" else diagnosis_code
71
-
72
- # Check if this is the same patient as the previous line
112
+ for sort_key, formatted_date, patient_name, patient_id, display_diagnosis in normalized_info:
73
113
  if current_patient == patient_id:
74
- # Secondary surgery date - indent and show dashes for patient info
75
- # Use exact character count matching for dashes
76
- patient_id_dashes = '-' * len(str(patient_id))
114
+ patient_id_dashes = '-' * len(patient_id)
77
115
  patient_name_dashes = '-' * len(patient_name)
78
-
79
116
  secondary_format = " {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
80
117
  print(secondary_format.format(formatted_date, patient_id_dashes, patient_name_dashes, display_diagnosis))
81
118
  else:
82
- # New patient - show full patient info
83
119
  current_patient = patient_id
84
120
  if show_line_numbers:
85
121
  primary_format = "{:03d}: {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
86
- print(primary_format.format(line_number, formatted_date, str(patient_id), patient_name, display_diagnosis))
122
+ print(primary_format.format(line_number, formatted_date, patient_id, patient_name, display_diagnosis))
87
123
  line_number += 1
88
124
  else:
89
- # For existing patients, don't show line numbers
90
125
  primary_format = " {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
91
- print(primary_format.format(formatted_date, str(patient_id), patient_name, display_diagnosis))
126
+ print(primary_format.format(formatted_date, patient_id, patient_name, display_diagnosis))
92
127
 
93
128
  # Function to check if a specific key is pressed
94
129
  def _get_vk_codes():
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.250728.9"
22
+ __version__ = "0.250814.4"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25