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.
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_UI.py +35 -7
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/MediLink_ConfigLoader.py +32 -15
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/core_utils.py +157 -6
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Gmail.py +57 -308
- medicafe-0.250811.0/MediLink/gmail_http_utils.py +88 -0
- medicafe-0.250811.0/MediLink/gmail_oauth_utils.py +98 -0
- {medicafe-0.250810.7/medicafe.egg-info → medicafe-0.250811.0}/PKG-INFO +1 -1
- {medicafe-0.250810.7 → medicafe-0.250811.0/medicafe.egg-info}/PKG-INFO +1 -1
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/SOURCES.txt +2 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/setup.py +1 -1
- {medicafe-0.250810.7 → medicafe-0.250811.0}/LICENSE +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MANIFEST.in +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot.bat +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_Preprocessor_lib.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_dataformat_library.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_docx_decoder.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/MediBot_smart_import.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/__init__.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/get_medicafe_version.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/update_json.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/__init__.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/__main__.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_core.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_core_backup.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_factory.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/api_utils.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/graphql_utils.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/logging_config.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/logging_demo.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/migration_helpers.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediCafe/smart_import.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_encoder_library.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_ClaimStatus.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_DataMgmt.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Decoder.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Deductible.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Deductible_Validator.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Display_Utils.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Parser.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_PatientProcessor.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_UI.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_Up.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_insurance_utils.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_main.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/MediLink_smart_import.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/__init__.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/insurance_type_integration_test.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_cob_library.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_timing.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/test_validation.py +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/MediLink/webapp.html +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/entry_points.txt +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250810.7 → medicafe-0.250811.0}/medicafe.egg-info/top_level.txt +0 -0
- {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
|
-
|
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
|
-
|
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
|
-
|
109
|
-
|
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
|
142
|
+
while ac.get_pause_status():
|
115
143
|
if is_key_pressed(VK_END):
|
116
|
-
|
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
|
-
|
108
|
+
raise
|
109
109
|
except KeyError as e:
|
110
110
|
print("Critical configuration is missing: {}".format(e))
|
111
|
-
|
111
|
+
raise
|
112
112
|
except Exception as e:
|
113
113
|
print("An unexpected error occurred while loading the configuration: {}".format(e))
|
114
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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 =
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
#
|
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
|
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
|
525
|
-
#
|
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
|