medicafe 0.250810.7__tar.gz → 0.250811.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 (76) hide show
  1. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_UI.py +35 -7
  2. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/MediLink_ConfigLoader.py +32 -15
  3. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/core_utils.py +157 -6
  4. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Gmail.py +57 -308
  5. medicafe-0.250811.0/MediLink/gmail_http_utils.py +88 -0
  6. medicafe-0.250811.0/MediLink/gmail_oauth_utils.py +98 -0
  7. {medicafe-0.250810.7/medicafe.egg-info → medicafe-0.250811.0}/PKG-INFO +1 -1
  8. {medicafe-0.250810.7 → medicafe-0.250811.0/medicafe.egg-info}/PKG-INFO +1 -1
  9. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/SOURCES.txt +2 -0
  10. {medicafe-0.250810.7 → medicafe-0.250811.0}/setup.py +1 -1
  11. {medicafe-0.250810.7 → medicafe-0.250811.0}/LICENSE +0 -0
  12. {medicafe-0.250810.7 → medicafe-0.250811.0}/MANIFEST.in +0 -0
  13. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot.bat +0 -0
  14. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot.py +0 -0
  15. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Charges.py +0 -0
  16. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
  17. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
  18. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Post.py +0 -0
  19. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Preprocessor.py +0 -0
  20. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Preprocessor_lib.py +0 -0
  21. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_dataformat_library.py +0 -0
  22. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_docx_decoder.py +0 -0
  23. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_smart_import.py +0 -0
  24. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/__init__.py +0 -0
  25. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/get_medicafe_version.py +0 -0
  26. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/update_json.py +0 -0
  27. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/update_medicafe.py +0 -0
  28. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/__init__.py +0 -0
  29. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/__main__.py +0 -0
  30. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_core.py +0 -0
  31. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_core_backup.py +0 -0
  32. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_factory.py +0 -0
  33. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_utils.py +0 -0
  34. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/graphql_utils.py +0 -0
  35. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/logging_config.py +0 -0
  36. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/logging_demo.py +0 -0
  37. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/migration_helpers.py +0 -0
  38. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/smart_import.py +0 -0
  39. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_cob_library.py +0 -0
  40. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_encoder.py +0 -0
  41. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_encoder_library.py +0 -0
  42. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_utilities.py +0 -0
  43. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_API_Generator.py +0 -0
  44. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Azure.py +0 -0
  45. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_ClaimStatus.py +0 -0
  46. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_DataMgmt.py +0 -0
  47. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Decoder.py +0 -0
  48. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Deductible.py +0 -0
  49. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Deductible_Validator.py +0 -0
  50. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Display_Utils.py +0 -0
  51. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Down.py +0 -0
  52. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Mailer.py +0 -0
  53. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Parser.py +0 -0
  54. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_PatientProcessor.py +0 -0
  55. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Scan.py +0 -0
  56. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Scheduler.py +0 -0
  57. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_UI.py +0 -0
  58. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Up.py +0 -0
  59. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_insurance_utils.py +0 -0
  60. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_main.py +0 -0
  61. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_smart_import.py +0 -0
  62. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/Soumit_api.py +0 -0
  63. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/__init__.py +0 -0
  64. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/insurance_type_integration_test.py +0 -0
  65. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/openssl.cnf +0 -0
  66. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test.py +0 -0
  67. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_cob_library.py +0 -0
  68. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_timing.py +0 -0
  69. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_validation.py +0 -0
  70. {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/webapp.html +0 -0
  71. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/dependency_links.txt +0 -0
  72. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/entry_points.txt +0 -0
  73. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/not-zip-safe +0 -0
  74. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/requires.txt +0 -0
  75. {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/top_level.txt +0 -0
  76. {medicafe-0.250810.7 → medicafe-0.250811.0}/setup.cfg +0 -0
@@ -45,7 +45,13 @@ class AppControl:
45
45
  # PERFORMANCE FIX: Add configuration caching to reduce lookup overhead
46
46
  self._config_cache = {} # Cache for Medicare vs Private configuration lookups
47
47
  # Load initial paths from config when instance is created
48
- self.load_paths_from_config()
48
+ try:
49
+ self.load_paths_from_config()
50
+ except Exception:
51
+ # Defer configuration loading until first access if config is unavailable
52
+ self._deferred_load = True
53
+ else:
54
+ self._deferred_load = False
49
55
 
50
56
  def get_pause_status(self):
51
57
  return self.script_paused
@@ -91,8 +97,29 @@ class AppControl:
91
97
  self.mapat_med_path = cached['mapat_path']
92
98
  self.medisoft_shortcut = cached['shortcut']
93
99
 
94
- app_control = AppControl()
95
-
100
+ def _get_app_control():
101
+ global app_control
102
+ try:
103
+ ac = app_control
104
+ except NameError:
105
+ ac = None
106
+ if ac is None:
107
+ ac = AppControl()
108
+ # If deferred, attempt first load now
109
+ try:
110
+ if getattr(ac, '_deferred_load', False):
111
+ ac.load_paths_from_config()
112
+ ac._deferred_load = False
113
+ except Exception:
114
+ pass
115
+ globals()['app_control'] = ac
116
+ return ac
117
+
118
+ # Lazily initialize app_control to avoid config load at import time
119
+ try:
120
+ app_control
121
+ except NameError:
122
+ app_control = None
96
123
 
97
124
 
98
125
  def is_key_pressed(key_code):
@@ -105,15 +132,16 @@ def manage_script_pause(csv_data, error_message, reverse_mapping):
105
132
  user_action = 0 # initialize as 'continue'
106
133
  VK_END, VK_PAUSE = _get_vk_codes()
107
134
 
108
- if not app_control.get_pause_status() and is_key_pressed(VK_PAUSE):
109
- app_control.set_pause_status(True)
135
+ ac = _get_app_control()
136
+ if not ac.get_pause_status() and is_key_pressed(VK_PAUSE):
137
+ ac.set_pause_status(True)
110
138
  print("Script paused. Opening menu...")
111
139
  interaction_mode = 'normal' # Assuming normal interaction mode for script pause
112
140
  user_action = user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
113
141
 
114
- while app_control.get_pause_status():
142
+ while ac.get_pause_status():
115
143
  if is_key_pressed(VK_END):
116
- app_control.set_pause_status(False)
144
+ ac.set_pause_status(False)
117
145
  print("Continuing...")
118
146
  elif is_key_pressed(VK_PAUSE):
119
147
  user_action = user_interaction(csv_data, 'normal', error_message, reverse_mapping)
@@ -105,13 +105,13 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
105
105
  sys.exit(1) # Exit the script due to a critical error in configuration loading
106
106
  except FileNotFoundError:
107
107
  print("One or both configuration files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
108
- sys.exit(1) # Exit the script due to a critical error in configuration loading
108
+ raise
109
109
  except KeyError as e:
110
110
  print("Critical configuration is missing: {}".format(e))
111
- sys.exit(1) # Exit the script due to a critical error in configuration loading
111
+ raise
112
112
  except Exception as e:
113
113
  print("An unexpected error occurred while loading the configuration: {}".format(e))
114
- sys.exit(1) # Exit the script due to a critical error in configuration loading
114
+ raise
115
115
 
116
116
  def clear_config_cache():
117
117
  """Clear the configuration cache to force reloading on next call."""
@@ -124,14 +124,23 @@ def log(message, config=None, level="INFO", error_type=None, claim=None, verbose
124
124
 
125
125
  # If config is not provided, use cached config or load it
126
126
  if config is None:
127
- if _CONFIG_CACHE is None:
128
- config, _ = load_configuration()
129
- else:
130
- config = _CONFIG_CACHE
127
+ try:
128
+ if _CONFIG_CACHE is None:
129
+ config, _ = load_configuration()
130
+ else:
131
+ config = _CONFIG_CACHE
132
+ except BaseException:
133
+ # Configuration unavailable; fall back to minimal console logging
134
+ config = {}
131
135
 
132
136
  # Setup logger if not already configured
133
137
  if not logging.root.handlers:
134
- local_storage_path = config['MediLink_Config'].get('local_storage_path', '.') if isinstance(config, dict) else '.'
138
+ local_storage_path = '.'
139
+ if isinstance(config, dict):
140
+ try:
141
+ local_storage_path = config.get('MediLink_Config', {}).get('local_storage_path', '.')
142
+ except Exception:
143
+ local_storage_path = '.'
135
144
  log_filename = datetime.now().strftime("Log_%m%d%Y.log")
136
145
  log_filepath = os.path.join(local_storage_path, log_filename)
137
146
 
@@ -141,12 +150,15 @@ def log(message, config=None, level="INFO", error_type=None, claim=None, verbose
141
150
  # Create formatter
142
151
  formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
143
152
 
144
- # Create file handler
145
- file_handler = logging.FileHandler(log_filepath, mode='a')
146
- file_handler.setFormatter(formatter)
147
-
148
- # Create handlers list - always include file handler
149
- handlers = [file_handler]
153
+ handlers = []
154
+ try:
155
+ # Create file handler when path is usable
156
+ file_handler = logging.FileHandler(log_filepath, mode='a')
157
+ file_handler.setFormatter(formatter)
158
+ handlers.append(file_handler)
159
+ except Exception:
160
+ # Fall back to console-only if file handler cannot be created
161
+ pass
150
162
 
151
163
  # Add console handler only if console_output is True
152
164
  if console_output:
@@ -154,7 +166,12 @@ def log(message, config=None, level="INFO", error_type=None, claim=None, verbose
154
166
  console_handler.setFormatter(formatter)
155
167
  handlers.append(console_handler)
156
168
 
157
- # Configure root logger
169
+ # If no handlers could be added (e.g., file path invalid and console_output False), add a console handler
170
+ if not handlers:
171
+ console_handler = logging.StreamHandler()
172
+ console_handler.setFormatter(formatter)
173
+ handlers.append(console_handler)
174
+
158
175
  logging.basicConfig(level=logging_level, handlers=handlers)
159
176
 
160
177
  # Prepare log message
@@ -513,16 +513,17 @@ def get_api_client_factory():
513
513
  config_loader = get_shared_config_loader()
514
514
  if config_loader:
515
515
  try:
516
+ # Be resilient to SystemExit raised inside loaders
516
517
  config, _ = config_loader.load_configuration()
517
- factory_config = config.get('API_Factory_Config', {})
518
+ factory_config = config.get('API_Factory_Config', {}) if isinstance(config, dict) else {}
518
519
  return APIClientFactory(factory_config)
519
- except Exception:
520
- # Fall back to default configuration
520
+ except BaseException:
521
+ # Fall back to default configuration on any loader failure (including SystemExit)
521
522
  return APIClientFactory()
522
523
  else:
523
524
  return APIClientFactory()
524
- except Exception as e:
525
- # Don't log error here - just return None silently
525
+ except BaseException:
526
+ # Do not allow API client factory acquisition to crash callers during import time
526
527
  return None
527
528
 
528
529
  def get_api_client(**kwargs):
@@ -555,4 +556,154 @@ def get_api_core_client(**kwargs):
555
556
  return APIClient(**kwargs)
556
557
  except ImportError:
557
558
  # Don't log error here - just return None silently
558
- return None
559
+ return None
560
+
561
+ # --- Compatibility & Process Utilities (Python 3.4.4 / Windows XP friendly) ---
562
+
563
+ def is_python_34_compatible(version_info=None):
564
+ """
565
+ Return True if the interpreter is Python 3.4.4+ (as required in XP env).
566
+ This consolidates scattered version checks into a single function so
567
+ call sites remain concise and intention-revealing.
568
+ """
569
+ try:
570
+ if version_info is None:
571
+ version_info = sys.version_info
572
+ major = getattr(version_info, 'major', version_info[0])
573
+ minor = getattr(version_info, 'minor', version_info[1])
574
+ micro = getattr(version_info, 'micro', version_info[2])
575
+ return (major, minor, micro) >= (3, 4, 4)
576
+ except Exception:
577
+ # Be conservative if we cannot determine
578
+ return False
579
+
580
+ def format_py34(template, *args, **kwargs):
581
+ """
582
+ Safe stand-in for f-strings (not available in Python 3.4).
583
+ Centralized to avoid ad-hoc "format_string" helpers scattered around.
584
+ """
585
+ try:
586
+ return template.format(*args, **kwargs)
587
+ except Exception:
588
+ # If formatting fails, return template unmodified to avoid crashes
589
+ return template
590
+
591
+ def _decode_bytes(data, encoding_list=None):
592
+ """
593
+ Decode bytes to text using a tolerant strategy.
594
+ We avoid relying on platform defaults (XP may vary) and try multiple
595
+ encodings to reduce boilerplate at call sites.
596
+ """
597
+ if data is None:
598
+ return ''
599
+ if isinstance(data, str):
600
+ return data
601
+ if encoding_list is None:
602
+ encoding_list = ['utf-8', 'latin-1', 'ascii']
603
+ for enc in encoding_list:
604
+ try:
605
+ return data.decode(enc, 'ignore')
606
+ except Exception:
607
+ continue
608
+ # Last resort: str() on bytes
609
+ try:
610
+ return str(data)
611
+ except Exception:
612
+ return ''
613
+
614
+ def run_cmd(cmd, timeout=None, input_text=None, shell=False, cwd=None, env=None):
615
+ """
616
+ Cross-version subprocess runner for Python 3.4/XP environments.
617
+ - Uses subprocess.Popen (subprocess.run is 3.5+)
618
+ - Returns (returncode, stdout_text, stderr_text)
619
+ - Accepts optional timeout (best-effort with polling for 3.4)
620
+ - Accepts optional input_text (string) which will be encoded to bytes
621
+ This consolidates repetitive Popen/communicate/decode blocks.
622
+ """
623
+ import subprocess
624
+ try:
625
+ # Prepare stdin if input is provided
626
+ stdin_pipe = subprocess.PIPE if input_text is not None else None
627
+ p = subprocess.Popen(
628
+ cmd,
629
+ stdin=stdin_pipe,
630
+ stdout=subprocess.PIPE,
631
+ stderr=subprocess.PIPE,
632
+ shell=shell,
633
+ cwd=cwd,
634
+ env=env
635
+ )
636
+ if timeout is None:
637
+ out_bytes, err_bytes = p.communicate(
638
+ input=input_text.encode('utf-8') if isinstance(input_text, str) else input_text
639
+ )
640
+ return p.returncode, _decode_bytes(out_bytes), _decode_bytes(err_bytes)
641
+ # Implement simple timeout loop compatible with 3.4
642
+ import time
643
+ start_time = time.time()
644
+ while True:
645
+ if p.poll() is not None:
646
+ out_bytes, err_bytes = p.communicate()
647
+ return p.returncode, _decode_bytes(out_bytes), _decode_bytes(err_bytes)
648
+ if time.time() - start_time > timeout:
649
+ try:
650
+ p.kill()
651
+ except Exception:
652
+ try:
653
+ p.terminate()
654
+ except Exception:
655
+ pass
656
+ out_bytes, err_bytes = p.communicate()
657
+ return 124, _decode_bytes(out_bytes), _decode_bytes(err_bytes)
658
+ time.sleep(0.05)
659
+ except Exception as e:
660
+ # Standardize failure surface
661
+ return 1, '', str(e)
662
+
663
+ def file_is_ascii(file_path):
664
+ """
665
+ Return True if the given file can be read as ASCII-only.
666
+ Consolidates repeated try/open/decode blocks into one helper.
667
+ """
668
+ try:
669
+ if not os.path.exists(file_path):
670
+ return False
671
+ # Use strict ASCII to detect any non-ASCII characters
672
+ f = open(file_path, 'r')
673
+ try:
674
+ # Explicit codec arg not supported in some XP Python builds for text mode
675
+ # so we emulate by checking ordinals while reading
676
+ data = f.read()
677
+ finally:
678
+ try:
679
+ f.close()
680
+ except Exception:
681
+ pass
682
+ try:
683
+ data.encode('ascii')
684
+ return True
685
+ except Exception:
686
+ return False
687
+ except Exception:
688
+ return False
689
+
690
+ def check_ascii_files(paths):
691
+ """
692
+ Given a list of paths (absolute or relative), return a list of those that
693
+ contain non-ASCII characters or could not be checked. This replaces
694
+ duplicated loops in various verification scripts.
695
+ """
696
+ problematic = []
697
+ try:
698
+ for p in paths:
699
+ # Normalize relative to current working directory for consistency
700
+ if not os.path.isabs(p):
701
+ abs_p = os.path.abspath(p)
702
+ else:
703
+ abs_p = p
704
+ if not file_is_ascii(abs_p):
705
+ problematic.append(p)
706
+ except Exception:
707
+ # If a failure occurs mid-scan, return what we have so far
708
+ return problematic
709
+ return problematic