medicafe 0.250728.9__py3-none-any.whl → 0.250805.0__py3-none-any.whl
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.
Potentially problematic release.
This version of medicafe might be problematic. Click here for more details.
- MediBot/MediBot.bat +233 -19
- MediBot/MediBot.py +138 -46
- MediBot/MediBot_Crosswalk_Library.py +127 -623
- MediBot/MediBot_Crosswalk_Utils.py +618 -0
- MediBot/MediBot_Preprocessor.py +72 -17
- MediBot/MediBot_Preprocessor_lib.py +470 -76
- MediBot/MediBot_UI.py +32 -17
- MediBot/MediBot_dataformat_library.py +68 -20
- MediBot/MediBot_docx_decoder.py +120 -19
- MediBot/MediBot_smart_import.py +180 -0
- MediBot/__init__.py +89 -0
- MediBot/get_medicafe_version.py +25 -0
- MediBot/update_json.py +35 -6
- MediBot/update_medicafe.py +19 -1
- MediCafe/MediLink_ConfigLoader.py +160 -0
- MediCafe/__init__.py +171 -0
- MediCafe/__main__.py +222 -0
- MediCafe/api_core.py +1098 -0
- MediCafe/api_core_backup.py +427 -0
- MediCafe/api_factory.py +306 -0
- MediCafe/api_utils.py +356 -0
- MediCafe/core_utils.py +450 -0
- MediCafe/graphql_utils.py +445 -0
- MediCafe/logging_config.py +123 -0
- MediCafe/logging_demo.py +61 -0
- MediCafe/migration_helpers.py +463 -0
- MediCafe/smart_import.py +436 -0
- MediLink/MediLink_837p_cob_library.py +28 -28
- MediLink/MediLink_837p_encoder.py +33 -34
- MediLink/MediLink_837p_encoder_library.py +226 -150
- MediLink/MediLink_837p_utilities.py +129 -5
- MediLink/MediLink_API_Generator.py +83 -60
- MediLink/MediLink_API_v3.py +1 -1
- MediLink/MediLink_ClaimStatus.py +177 -31
- MediLink/MediLink_DataMgmt.py +378 -63
- MediLink/MediLink_Decoder.py +20 -1
- MediLink/MediLink_Deductible.py +155 -28
- MediLink/MediLink_Display_Utils.py +72 -0
- MediLink/MediLink_Down.py +127 -5
- MediLink/MediLink_Gmail.py +712 -653
- MediLink/MediLink_PatientProcessor.py +257 -0
- MediLink/MediLink_UI.py +85 -71
- MediLink/MediLink_Up.py +28 -4
- MediLink/MediLink_insurance_utils.py +227 -230
- MediLink/MediLink_main.py +248 -0
- MediLink/MediLink_smart_import.py +264 -0
- MediLink/__init__.py +93 -1
- MediLink/insurance_type_integration_test.py +13 -3
- MediLink/test.py +1 -1
- MediLink/test_timing.py +59 -0
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/METADATA +1 -1
- medicafe-0.250805.0.dist-info/RECORD +81 -0
- medicafe-0.250805.0.dist-info/entry_points.txt +2 -0
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/top_level.txt +1 -0
- medicafe-0.250728.9.dist-info/RECORD +0 -59
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/LICENSE +0 -0
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/WHEEL +0 -0
|
@@ -1,78 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if project_dir not in sys.path:
|
|
6
|
-
sys.path.append(project_dir)
|
|
7
|
-
|
|
8
|
-
# Attempt to import the MediLink_ConfigLoader module, falling back to an alternative import if necessary
|
|
9
|
-
try:
|
|
10
|
-
import MediLink_ConfigLoader
|
|
11
|
-
except ImportError:
|
|
12
|
-
from MediLink import MediLink_ConfigLoader
|
|
13
|
-
|
|
14
|
-
# Attempt to import the fetch_payer_name_from_api function from MediLink_API_v3, with a fallback
|
|
15
|
-
try:
|
|
16
|
-
from MediLink_API_v3 import fetch_payer_name_from_api
|
|
17
|
-
except ImportError:
|
|
18
|
-
from MediLink import MediLink_API_v3
|
|
19
|
-
fetch_payer_name_from_api = MediLink_API_v3.fetch_payer_name_from_api
|
|
20
|
-
|
|
21
|
-
# Attempt to import the MediBot_Preprocessor_lib module, with a fallback
|
|
22
|
-
try:
|
|
23
|
-
from MediBot import MediBot_Preprocessor_lib
|
|
24
|
-
except ImportError:
|
|
25
|
-
import MediBot_Preprocessor_lib
|
|
26
|
-
|
|
1
|
+
# MediBot_Crosswalk_Library.py
|
|
2
|
+
"""
|
|
3
|
+
Core crosswalk library for MediBot
|
|
4
|
+
Handles crosswalk operations and API interactions.
|
|
27
5
|
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import csv
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
# Use core utilities for standardized imports
|
|
15
|
+
from MediCafe.core_utils import (
|
|
16
|
+
import_medibot_module,
|
|
17
|
+
get_config_loader_with_fallback
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Initialize configuration loader with fallback
|
|
21
|
+
MediLink_ConfigLoader = get_config_loader_with_fallback()
|
|
22
|
+
|
|
23
|
+
# Import MediBot modules using centralized import functions
|
|
24
|
+
MediBot_Preprocessor_lib = import_medibot_module('MediBot_Preprocessor_lib')
|
|
25
|
+
|
|
26
|
+
# Import utility functions from MediBot_Crosswalk_Utils.py using centralized import
|
|
27
|
+
MediBot_Crosswalk_Utils = import_medibot_module('MediBot_Crosswalk_Utils')
|
|
28
|
+
if MediBot_Crosswalk_Utils:
|
|
29
|
+
check_crosswalk_health = getattr(MediBot_Crosswalk_Utils, 'check_crosswalk_health', None)
|
|
30
|
+
prompt_user_for_api_calls = getattr(MediBot_Crosswalk_Utils, 'prompt_user_for_api_calls', None)
|
|
31
|
+
select_endpoint = getattr(MediBot_Crosswalk_Utils, 'select_endpoint', None)
|
|
32
|
+
ensure_full_config_loaded = getattr(MediBot_Crosswalk_Utils, 'ensure_full_config_loaded', None)
|
|
33
|
+
save_crosswalk = getattr(MediBot_Crosswalk_Utils, 'save_crosswalk', None)
|
|
34
|
+
update_crosswalk_with_corrected_payer_id = getattr(MediBot_Crosswalk_Utils, 'update_crosswalk_with_corrected_payer_id', None)
|
|
35
|
+
update_crosswalk_with_new_payer_id = getattr(MediBot_Crosswalk_Utils, 'update_crosswalk_with_new_payer_id', None)
|
|
36
|
+
load_and_parse_z_data = getattr(MediBot_Crosswalk_Utils, 'load_and_parse_z_data', None)
|
|
37
|
+
else:
|
|
38
|
+
# Set all functions to None if import fails completely
|
|
39
|
+
check_crosswalk_health = None
|
|
40
|
+
prompt_user_for_api_calls = None
|
|
41
|
+
select_endpoint = None
|
|
42
|
+
ensure_full_config_loaded = None
|
|
43
|
+
save_crosswalk = None
|
|
44
|
+
update_crosswalk_with_corrected_payer_id = None
|
|
45
|
+
update_crosswalk_with_new_payer_id = None
|
|
46
|
+
load_and_parse_z_data = None
|
|
47
|
+
|
|
48
|
+
# Module-level cache to prevent redundant API calls
|
|
49
|
+
_api_cache = {}
|
|
55
50
|
|
|
51
|
+
"""
|
|
52
|
+
# RESOLVED: Redundant API calls have been eliminated through module-level caching.
|
|
53
|
+
# The _api_cache prevents duplicate API calls for the same payer_id within a session.
|
|
56
54
|
"""
|
|
57
55
|
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# CORE CROSSWALK OPERATIONS - Main Library Functions
|
|
58
|
+
# =============================================================================
|
|
59
|
+
# These functions handle the primary crosswalk operations and are kept in the main
|
|
60
|
+
# library because they are frequently called and contain the core business logic.
|
|
61
|
+
# Utility functions have been moved to MediBot_Crosswalk_Utils.py to reduce
|
|
62
|
+
# the main library size and improve maintainability.
|
|
58
63
|
|
|
59
|
-
def fetch_and_store_payer_name(client, payer_id, crosswalk, config):
|
|
64
|
+
def fetch_and_store_payer_name(client, payer_id, crosswalk, config, api_cache=None):
|
|
60
65
|
"""
|
|
61
66
|
Fetches the payer name for a given payer ID and stores it in the crosswalk.
|
|
67
|
+
Now with optional API cache to prevent redundant calls.
|
|
62
68
|
|
|
63
69
|
Args:
|
|
64
70
|
payer_id (str): The ID of the payer to fetch.
|
|
65
71
|
crosswalk (dict): The crosswalk dictionary to store the payer name.
|
|
66
72
|
config (dict): Configuration settings for logging.
|
|
73
|
+
api_cache (dict, optional): Cache to prevent redundant API calls. Uses module-level cache if None.
|
|
67
74
|
|
|
68
75
|
Returns:
|
|
69
76
|
bool: True if the payer name was fetched and stored successfully, False otherwise.
|
|
70
77
|
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
global _api_cache
|
|
79
|
+
|
|
80
|
+
# Use provided cache or module-level cache
|
|
81
|
+
if api_cache is None:
|
|
82
|
+
api_cache = _api_cache
|
|
83
|
+
|
|
84
|
+
# Check if we already have this payer_id in cache
|
|
85
|
+
if payer_id in api_cache:
|
|
86
|
+
payer_name = api_cache[payer_id]
|
|
87
|
+
MediLink_ConfigLoader.log("Using cached payer name for Payer ID: {}".format(payer_id), config, level="DEBUG")
|
|
88
|
+
else:
|
|
89
|
+
MediLink_ConfigLoader.log("Attempting to fetch payer name for Payer ID: {}".format(payer_id), config, level="DEBUG")
|
|
90
|
+
try:
|
|
91
|
+
# Fetch the payer name from the API
|
|
92
|
+
payer_name = fetch_payer_name_from_api(client, payer_id, config, primary_endpoint=None)
|
|
93
|
+
# Cache the result
|
|
94
|
+
api_cache[payer_id] = payer_name
|
|
95
|
+
MediLink_ConfigLoader.log("Fetched and cached payer name: {} for Payer ID: {}".format(payer_name, payer_id), config, level="DEBUG")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Log any errors encountered during the fetching process
|
|
98
|
+
MediLink_ConfigLoader.log("Failed to fetch name for Payer ID {}: {}".format(payer_id, e), config, level="WARNING")
|
|
99
|
+
payer_name = "Unknown"
|
|
100
|
+
api_cache[payer_id] = payer_name
|
|
101
|
+
return False
|
|
76
102
|
|
|
77
103
|
# Ensure the 'payer_id' key exists in the crosswalk
|
|
78
104
|
if 'payer_id' not in crosswalk:
|
|
@@ -90,13 +116,8 @@ def fetch_and_store_payer_name(client, payer_id, crosswalk, config):
|
|
|
90
116
|
MediLink_ConfigLoader.log(message, config, level="INFO")
|
|
91
117
|
print(message)
|
|
92
118
|
return True
|
|
93
|
-
except Exception as e:
|
|
94
|
-
# Log any errors encountered during the fetching process
|
|
95
|
-
MediLink_ConfigLoader.log("Failed to fetch name for Payer ID {}: {}".format(payer_id, e), config, level="WARNING")
|
|
96
|
-
crosswalk['payer_id'][payer_id]['name'] = "Unknown"
|
|
97
|
-
return False
|
|
98
119
|
|
|
99
|
-
def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False):
|
|
120
|
+
def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False, api_cache=None):
|
|
100
121
|
"""
|
|
101
122
|
Validates and corrects payer IDs in the crosswalk. If a payer ID is invalid, it prompts the user for correction.
|
|
102
123
|
|
|
@@ -104,6 +125,7 @@ def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False
|
|
|
104
125
|
crosswalk (dict): The crosswalk dictionary containing payer IDs.
|
|
105
126
|
config (dict): Configuration settings for logging.
|
|
106
127
|
auto_correct (bool): If True, automatically corrects invalid payer IDs to 'Unknown'.
|
|
128
|
+
api_cache (dict, optional): Cache to prevent redundant API calls.
|
|
107
129
|
"""
|
|
108
130
|
processed_payer_ids = set() # Track processed payer IDs
|
|
109
131
|
payer_ids = list(crosswalk.get('payer_id', {}).keys()) # Static list to prevent modification issues
|
|
@@ -113,7 +135,7 @@ def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False
|
|
|
113
135
|
continue # Skip already processed payer IDs
|
|
114
136
|
|
|
115
137
|
# Validate the payer ID by fetching its name
|
|
116
|
-
is_valid = fetch_and_store_payer_name(client, payer_id, crosswalk, config)
|
|
138
|
+
is_valid = fetch_and_store_payer_name(client, payer_id, crosswalk, config, api_cache)
|
|
117
139
|
|
|
118
140
|
if not is_valid:
|
|
119
141
|
if auto_correct:
|
|
@@ -150,9 +172,9 @@ def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False
|
|
|
150
172
|
|
|
151
173
|
if corrected_payer_id:
|
|
152
174
|
# Validate the corrected payer ID
|
|
153
|
-
if fetch_and_store_payer_name(client, corrected_payer_id, crosswalk, config):
|
|
175
|
+
if fetch_and_store_payer_name(client, corrected_payer_id, crosswalk, config, api_cache):
|
|
154
176
|
# Replace the old payer ID with the corrected one in the crosswalk
|
|
155
|
-
success = update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk)
|
|
177
|
+
success = update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk, api_cache)
|
|
156
178
|
if success:
|
|
157
179
|
print("Payer ID '{}' has been successfully replaced with '{}'.".format(
|
|
158
180
|
payer_id, corrected_payer_id))
|
|
@@ -211,184 +233,12 @@ def initialize_crosswalk_from_mapat(client, config, crosswalk):
|
|
|
211
233
|
|
|
212
234
|
return crosswalk['payer_id']
|
|
213
235
|
|
|
214
|
-
def load_and_parse_z_data(config):
|
|
215
|
-
"""
|
|
216
|
-
Loads and parses Z data for patient to insurance name mappings from the specified directory.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
config (dict): Configuration settings for logging.
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
dict: A mapping of patient IDs to insurance names.
|
|
223
|
-
"""
|
|
224
|
-
patient_id_to_insurance_name = {}
|
|
225
|
-
try:
|
|
226
|
-
z_dat_path = config['MediLink_Config']['Z_DAT_PATH']
|
|
227
|
-
MediLink_ConfigLoader.log("Z_DAT_PATH is set to: {}".format(z_dat_path), config, level="DEBUG")
|
|
228
|
-
|
|
229
|
-
# Get the directory of the Z_DAT_PATH
|
|
230
|
-
directory = os.path.dirname(z_dat_path)
|
|
231
|
-
MediLink_ConfigLoader.log("Looking for .DAT files in directory: {}".format(directory), config, level="DEBUG")
|
|
232
|
-
|
|
233
|
-
# List all .DAT files in the directory, case insensitive
|
|
234
|
-
dat_files = [f for f in os.listdir(directory) if f.lower().endswith('.dat')]
|
|
235
|
-
MediLink_ConfigLoader.log("Found {} .DAT files in the directory.".format(len(dat_files)), config, level="DEBUG")
|
|
236
|
-
|
|
237
|
-
# Load processed files tracking
|
|
238
|
-
processed_files_path = os.path.join(directory, 'processed_files.txt')
|
|
239
|
-
if os.path.exists(processed_files_path):
|
|
240
|
-
with open(processed_files_path, 'r') as f:
|
|
241
|
-
processed_files = set(line.strip() for line in f)
|
|
242
|
-
MediLink_ConfigLoader.log("Loaded processed files: {}.".format(processed_files), config, level="DEBUG")
|
|
243
|
-
else:
|
|
244
|
-
processed_files = set()
|
|
245
|
-
MediLink_ConfigLoader.log("No processed files found, starting fresh.", config, level="DEBUG")
|
|
246
|
-
|
|
247
|
-
# Filter for new .DAT files that haven't been processed yet, but always include Z.DAT and ZM.DAT
|
|
248
|
-
new_dat_files = [f for f in dat_files if f not in processed_files or f.lower() in ['z.dat', 'zm.dat']]
|
|
249
|
-
MediLink_ConfigLoader.log("Identified {} new .DAT files to process.".format(len(new_dat_files)), config, level="INFO")
|
|
250
|
-
|
|
251
|
-
for dat_file in new_dat_files:
|
|
252
|
-
file_path = os.path.join(directory, dat_file)
|
|
253
|
-
MediLink_ConfigLoader.log("Parsing .DAT file: {}".format(file_path), config, level="DEBUG")
|
|
254
|
-
# Parse each .DAT file and accumulate results
|
|
255
|
-
insurance_name_mapping = MediBot_Preprocessor_lib.parse_z_dat(file_path, config['MediLink_Config'])
|
|
256
|
-
if insurance_name_mapping: # Ensure insurance_name_mapping is not empty
|
|
257
|
-
patient_id_to_insurance_name.update(insurance_name_mapping)
|
|
258
|
-
|
|
259
|
-
# Mark this file as processed
|
|
260
|
-
with open(processed_files_path, 'a') as f:
|
|
261
|
-
f.write(dat_file + '\n')
|
|
262
|
-
MediLink_ConfigLoader.log("Marked file as processed: {}".format(dat_file), config, level="DEBUG")
|
|
263
|
-
|
|
264
|
-
if not patient_id_to_insurance_name: # Check if the result is empty
|
|
265
|
-
raise ValueError("Parsed Z data is empty, possibly indicating an error in parsing or all files already processed.") # TODO Add differentiator here because this is dumb.
|
|
266
|
-
MediLink_ConfigLoader.log("Successfully parsed Z data with {} mappings found.".format(len(patient_id_to_insurance_name)), config, level="INFO")
|
|
267
|
-
return patient_id_to_insurance_name # Ensure the function returns the mapping
|
|
268
|
-
except Exception as e:
|
|
269
|
-
MediLink_ConfigLoader.log("Error loading and parsing Z data: {}".format(e), config, level="ERROR")
|
|
270
|
-
return {}
|
|
271
|
-
|
|
272
|
-
def check_crosswalk_health(crosswalk):
|
|
273
|
-
"""
|
|
274
|
-
Simple health check for crosswalk - checks if payers have names and at least one medisoft ID.
|
|
275
|
-
A payer is considered healthy if it has a name (not "Unknown") and at least one medisoft ID,
|
|
276
|
-
which can exist in either 'medisoft_id' OR 'medisoft_medicare_id'. It is NOT required to have both.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
crosswalk (dict): The crosswalk dictionary to check.
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
tuple: (is_healthy, missing_names_count, missing_medisoft_ids_count, missing_names_list, missing_medisoft_ids_list)
|
|
283
|
-
"""
|
|
284
|
-
if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
|
|
285
|
-
return False, 0, 0, [], []
|
|
286
|
-
|
|
287
|
-
missing_names = 0
|
|
288
|
-
missing_medisoft_ids = 0
|
|
289
|
-
missing_names_list = []
|
|
290
|
-
missing_medisoft_ids_list = []
|
|
291
|
-
|
|
292
|
-
for payer_id, details in crosswalk['payer_id'].items():
|
|
293
|
-
# Check if name is missing or "Unknown"
|
|
294
|
-
name = details.get('name', '')
|
|
295
|
-
if not name or name == 'Unknown':
|
|
296
|
-
missing_names += 1
|
|
297
|
-
missing_names_list.append(payer_id)
|
|
298
|
-
|
|
299
|
-
# Check if at least one medisoft ID exists in either field
|
|
300
|
-
medisoft_id = details.get('medisoft_id', [])
|
|
301
|
-
medisoft_medicare_id = details.get('medisoft_medicare_id', [])
|
|
302
236
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if isinstance(medisoft_medicare_id, set):
|
|
307
|
-
medisoft_medicare_id = list(medisoft_medicare_id)
|
|
308
|
-
|
|
309
|
-
# If both are empty, count as missing; if either has at least one, it's healthy
|
|
310
|
-
if not medisoft_id and not medisoft_medicare_id:
|
|
311
|
-
missing_medisoft_ids += 1
|
|
312
|
-
missing_medisoft_ids_list.append(payer_id)
|
|
313
|
-
|
|
314
|
-
# Consider healthy if no missing names and no missing medisoft IDs
|
|
315
|
-
is_healthy = (missing_names == 0 and missing_medisoft_ids == 0)
|
|
316
|
-
return is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list
|
|
317
|
-
|
|
318
|
-
def prompt_user_for_api_calls(crosswalk, config):
|
|
319
|
-
"""
|
|
320
|
-
Prompts user with a 3-second timeout to skip API calls if crosswalk looks healthy.
|
|
321
|
-
Windows XP compatible version using threading instead of select.
|
|
322
|
-
|
|
323
|
-
Args:
|
|
324
|
-
crosswalk (dict): The crosswalk dictionary to check.
|
|
325
|
-
config (dict): Configuration settings for logging.
|
|
326
|
-
|
|
327
|
-
Returns:
|
|
328
|
-
bool: True if should proceed with API calls, False if should skip
|
|
329
|
-
"""
|
|
330
|
-
|
|
331
|
-
is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list = check_crosswalk_health(crosswalk)
|
|
332
|
-
total_payers = len(crosswalk.get('payer_id', {}))
|
|
333
|
-
|
|
334
|
-
if is_healthy:
|
|
335
|
-
print("\nCrosswalk appears healthy:")
|
|
336
|
-
print(" - {} payers found".format(total_payers))
|
|
337
|
-
print(" - All payers have names")
|
|
338
|
-
print(" - All payers have medisoft IDs")
|
|
339
|
-
print("\nPress ENTER to run API validation, or wait 2 seconds to skip...")
|
|
340
|
-
|
|
341
|
-
# Use threading for timeout on Windows
|
|
342
|
-
user_input = [None] # Use list to store result from thread
|
|
343
|
-
|
|
344
|
-
def get_input():
|
|
345
|
-
try:
|
|
346
|
-
user_input[0] = input()
|
|
347
|
-
except (EOFError, KeyboardInterrupt):
|
|
348
|
-
user_input[0] = ""
|
|
349
|
-
|
|
350
|
-
# Start input thread
|
|
351
|
-
input_thread = threading.Thread(target=get_input)
|
|
352
|
-
input_thread.daemon = True
|
|
353
|
-
input_thread.start()
|
|
354
|
-
|
|
355
|
-
# Wait for 2 seconds or until input is received
|
|
356
|
-
input_thread.join(timeout=2.0)
|
|
357
|
-
|
|
358
|
-
if user_input[0] is not None:
|
|
359
|
-
print("Running API validation calls...")
|
|
360
|
-
MediLink_ConfigLoader.log("User pressed ENTER - proceeding with API calls", config, level="INFO")
|
|
361
|
-
return True
|
|
362
|
-
else:
|
|
363
|
-
print("Timed out - skipping API calls")
|
|
364
|
-
MediLink_ConfigLoader.log("Timeout - skipping API calls", config, level="INFO")
|
|
365
|
-
return False
|
|
366
|
-
else:
|
|
367
|
-
print("\nCrosswalk needs attention:")
|
|
368
|
-
print(" - {} payers found".format(total_payers))
|
|
369
|
-
|
|
370
|
-
# Show detailed information about missing names
|
|
371
|
-
if missing_names > 0:
|
|
372
|
-
print(" - {} payers missing names: {}".format(missing_names, ", ".join(missing_names_list)))
|
|
373
|
-
|
|
374
|
-
# Show detailed information about missing medisoft IDs
|
|
375
|
-
if missing_medisoft_ids > 0:
|
|
376
|
-
print(" - {} payers missing medisoft IDs: {}".format(missing_medisoft_ids, ", ".join(missing_medisoft_ids_list)))
|
|
377
|
-
# API validation CANNOT resolve missing medisoft IDs
|
|
378
|
-
print(" TODO: Need user interface to manually input medisoft IDs for these payers")
|
|
379
|
-
|
|
380
|
-
# Only proceed with API calls if there are missing names (API can help with those)
|
|
381
|
-
if missing_names > 0:
|
|
382
|
-
print("Proceeding with API validation calls to resolve missing names...")
|
|
383
|
-
MediLink_ConfigLoader.log("Crosswalk has missing names - proceeding with API calls", config, level="INFO")
|
|
384
|
-
return True
|
|
385
|
-
else:
|
|
386
|
-
print("No missing names to resolve via API. Skipping API validation calls.")
|
|
387
|
-
print("TODO: Manual intervention needed for missing medisoft IDs")
|
|
388
|
-
MediLink_ConfigLoader.log("Crosswalk has missing medisoft IDs but no missing names - skipping API calls", config, level="INFO")
|
|
389
|
-
return False
|
|
237
|
+
# =============================================================================
|
|
238
|
+
# MAIN CROSSWALK UPDATE FUNCTION
|
|
239
|
+
# =============================================================================
|
|
390
240
|
|
|
391
|
-
def crosswalk_update(client, config, crosswalk
|
|
241
|
+
def crosswalk_update(client, config, crosswalk): # Upstream of this is only MediBot_Preprocessor.py and MediBot.py
|
|
392
242
|
"""
|
|
393
243
|
Updates the crosswalk with insurance data and historical mappings.
|
|
394
244
|
It loads insurance data, historical payer mappings, and updates the crosswalk accordingly.
|
|
@@ -396,36 +246,56 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
|
|
|
396
246
|
Args:
|
|
397
247
|
config (dict): Configuration settings for logging.
|
|
398
248
|
crosswalk (dict): The crosswalk dictionary to update.
|
|
399
|
-
skip_known_payers (bool): If True, skips records with 'name' not equal to 'Unknown'.
|
|
400
249
|
|
|
401
250
|
Returns:
|
|
402
251
|
bool: True if the crosswalk was updated successfully, False otherwise.
|
|
403
252
|
"""
|
|
404
253
|
MediLink_ConfigLoader.log("Starting crosswalk update process...", config, level="INFO")
|
|
405
254
|
|
|
406
|
-
#
|
|
255
|
+
# Initialize API cache for this session to prevent redundant calls
|
|
256
|
+
api_cache = {}
|
|
257
|
+
|
|
258
|
+
# Load insurance data from MAINS (optional - continue if not available)
|
|
259
|
+
insurance_name_to_id = {}
|
|
407
260
|
try:
|
|
408
261
|
MediLink_ConfigLoader.log("Attempting to load insurance data from MAINS...", config, level="DEBUG")
|
|
409
262
|
insurance_name_to_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config)
|
|
410
263
|
MediLink_ConfigLoader.log("Loaded insurance data from MAINS with {} entries.".format(len(insurance_name_to_id)), config, level="INFO")
|
|
411
264
|
except Exception as e:
|
|
412
|
-
MediLink_ConfigLoader.log("Error loading insurance data from MAINS: {}".format(e), config, level="
|
|
413
|
-
|
|
265
|
+
MediLink_ConfigLoader.log("Error loading insurance data from MAINS: {}. Continuing without MAINS data.".format(e), config, level="WARNING")
|
|
266
|
+
print("Warning: MAINS data not available. Some crosswalk features may be limited.")
|
|
267
|
+
# Continue without MAINS data - don't return False
|
|
414
268
|
|
|
415
|
-
# Load historical payer to patient mappings
|
|
269
|
+
# Load historical payer to patient mappings (optional - continue if not available)
|
|
270
|
+
patient_id_to_payer_id = {}
|
|
416
271
|
try:
|
|
417
272
|
MediLink_ConfigLoader.log("Attempting to load historical payer to patient mappings...", config, level="DEBUG")
|
|
418
273
|
patient_id_to_payer_id = MediBot_Preprocessor_lib.load_historical_payer_to_patient_mappings(config)
|
|
419
274
|
MediLink_ConfigLoader.log("Loaded historical mappings with {} entries.".format(len(patient_id_to_payer_id)), config, level="INFO")
|
|
420
275
|
except Exception as e:
|
|
421
|
-
MediLink_ConfigLoader.log("Error loading historical mappings: {}".format(e), config, level="
|
|
422
|
-
|
|
276
|
+
MediLink_ConfigLoader.log("Error loading historical mappings: {}. Continuing without historical data.".format(e), config, level="WARNING")
|
|
277
|
+
print("Warning: Historical mappings not available. Some crosswalk features may be limited.")
|
|
278
|
+
# Continue without historical data - don't return False
|
|
423
279
|
|
|
424
280
|
# Parse Z data for patient to insurance name mappings
|
|
425
281
|
try:
|
|
426
|
-
patient_id_to_insurance_name = load_and_parse_z_data(config)
|
|
282
|
+
patient_id_to_insurance_name, z_data_status = load_and_parse_z_data(config)
|
|
427
283
|
mapping_count = len(patient_id_to_insurance_name) if patient_id_to_insurance_name is not None else 0
|
|
428
|
-
MediLink_ConfigLoader.log("Parsed Z data with {} mappings found.".format(mapping_count), config, level="INFO")
|
|
284
|
+
MediLink_ConfigLoader.log("Parsed Z data with {} mappings found. Status: {}".format(mapping_count, z_data_status), config, level="INFO")
|
|
285
|
+
|
|
286
|
+
# Handle different Z data statuses
|
|
287
|
+
if z_data_status == "error":
|
|
288
|
+
MediLink_ConfigLoader.log("Error occurred during Z data parsing.", config, level="ERROR")
|
|
289
|
+
return False
|
|
290
|
+
elif z_data_status == "success_no_new_files":
|
|
291
|
+
MediLink_ConfigLoader.log("No new Z data files to process - this is normal if all files have been processed.", config, level="INFO")
|
|
292
|
+
# Continue with crosswalk update even if no new Z data
|
|
293
|
+
elif z_data_status == "success_empty_files":
|
|
294
|
+
MediLink_ConfigLoader.log("Z data files were processed but contained no valid mappings - this may indicate empty or malformed files.", config, level="WARNING")
|
|
295
|
+
# Continue with crosswalk update even if Z data is empty
|
|
296
|
+
elif z_data_status == "success_with_data":
|
|
297
|
+
MediLink_ConfigLoader.log("Successfully processed Z data with new mappings.", config, level="INFO")
|
|
298
|
+
# Normal case - continue with crosswalk update
|
|
429
299
|
except Exception as e:
|
|
430
300
|
MediLink_ConfigLoader.log("Error parsing Z data in crosswalk update: {}".format(e), config, level="ERROR")
|
|
431
301
|
return False
|
|
@@ -454,22 +324,6 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
|
|
|
454
324
|
# Continue with existing crosswalk update logic...
|
|
455
325
|
# Update the crosswalk with new payer IDs and insurance IDs
|
|
456
326
|
for patient_id, payer_id in patient_id_to_payer_id.items():
|
|
457
|
-
""" TODO this needs to be implemented at some point so we can skip known entities.
|
|
458
|
-
# Skip known payers if the flag is set
|
|
459
|
-
if skip_known_payers:
|
|
460
|
-
payer_id_str = next(iter(payer_id)) # Extract the single payer_id from the set
|
|
461
|
-
MediLink_ConfigLoader.log("Checking if payer_id '{}' is known...".format(payer_id_str), config, level="DEBUG")
|
|
462
|
-
payer_info = crosswalk['payer_id'].get(payer_id_str, {})
|
|
463
|
-
payer_name = payer_info.get('name', "Unknown")
|
|
464
|
-
MediLink_ConfigLoader.log("Retrieved payer name: '{}' for payer_id '{}'.".format(payer_name, payer_id), config, level="DEBUG")
|
|
465
|
-
|
|
466
|
-
if payer_name != "Unknown":
|
|
467
|
-
MediLink_ConfigLoader.log("Skipping known payer_id: '{}' as it is already in the crosswalk.".format(payer_id), config, level="DEBUG")
|
|
468
|
-
continue # Skip this payer_id
|
|
469
|
-
MediLink_ConfigLoader.log("Skipping known payer_id: {} as it is already in the crosswalk.".format(payer_id), config, level="DEBUG")
|
|
470
|
-
continue # Skip this payer_id
|
|
471
|
-
"""
|
|
472
|
-
|
|
473
327
|
insurance_name = patient_id_to_insurance_name.get(patient_id)
|
|
474
328
|
if insurance_name and insurance_name in insurance_name_to_id:
|
|
475
329
|
insurance_id = insurance_name_to_id[insurance_name]
|
|
@@ -519,12 +373,12 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
|
|
|
519
373
|
|
|
520
374
|
# Fetch and store the payer name
|
|
521
375
|
MediLink_ConfigLoader.log("Fetching and storing payer name for payer_id: {}".format(payer_id), config, level="DEBUG")
|
|
522
|
-
fetch_and_store_payer_name(client, payer_id, crosswalk, config)
|
|
376
|
+
fetch_and_store_payer_name(client, payer_id, crosswalk, config, api_cache)
|
|
523
377
|
MediLink_ConfigLoader.log("Successfully fetched and stored payer name for payer_id: {}".format(payer_id), config, level="INFO")
|
|
524
378
|
|
|
525
379
|
# Validate and correct payer IDs in the crosswalk
|
|
526
380
|
MediLink_ConfigLoader.log("Validating and correcting payer IDs in the crosswalk.", config, level="DEBUG")
|
|
527
|
-
validate_and_correct_payer_ids(client, crosswalk, config)
|
|
381
|
+
validate_and_correct_payer_ids(client, crosswalk, config, api_cache=api_cache)
|
|
528
382
|
|
|
529
383
|
# Check for any entries marked as "Unknown" and validate them
|
|
530
384
|
unknown_payers = [
|
|
@@ -534,7 +388,7 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
|
|
|
534
388
|
MediLink_ConfigLoader.log("Found {} unknown payer(s) to validate.".format(len(unknown_payers)), config, level="INFO")
|
|
535
389
|
for payer_id in unknown_payers:
|
|
536
390
|
MediLink_ConfigLoader.log("Fetching and storing payer name for unknown payer_id: {}".format(payer_id), config, level="DEBUG")
|
|
537
|
-
fetch_and_store_payer_name(client, payer_id, crosswalk, config)
|
|
391
|
+
fetch_and_store_payer_name(client, payer_id, crosswalk, config, api_cache)
|
|
538
392
|
MediLink_ConfigLoader.log("Successfully fetched and stored payer name for unknown payer_id: {}".format(payer_id), config, level="INFO")
|
|
539
393
|
|
|
540
394
|
# PERFORMANCE FIX: Optimized list management - avoid redundant set/list conversions
|
|
@@ -559,359 +413,9 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
|
|
|
559
413
|
crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = list(dict.fromkeys(medicare_id))
|
|
560
414
|
|
|
561
415
|
MediLink_ConfigLoader.log("Crosswalk update process completed. Processed {} payer IDs.".format(len(patient_id_to_payer_id)), config, level="INFO")
|
|
562
|
-
return save_crosswalk(client, config, crosswalk)
|
|
416
|
+
return save_crosswalk(client, config, crosswalk, api_cache=api_cache)
|
|
563
417
|
|
|
564
|
-
def update_crosswalk_with_corrected_payer_id(client, old_payer_id, corrected_payer_id, config=None, crosswalk=None):
|
|
565
|
-
"""
|
|
566
|
-
Updates the crosswalk by replacing an old payer ID with a corrected payer ID.
|
|
567
|
-
|
|
568
|
-
Args:
|
|
569
|
-
old_payer_id (str): The old payer ID to be replaced.
|
|
570
|
-
corrected_payer_id (str): The new payer ID to replace the old one.
|
|
571
|
-
config (dict, optional): Configuration settings for logging.
|
|
572
|
-
crosswalk (dict, optional): The crosswalk dictionary to update.
|
|
573
|
-
|
|
574
|
-
Returns:
|
|
575
|
-
bool: True if the crosswalk was updated successfully, False otherwise.
|
|
576
|
-
"""
|
|
577
|
-
# Ensure full configuration and crosswalk are loaded
|
|
578
|
-
config, crosswalk = ensure_full_config_loaded(config, crosswalk)
|
|
579
|
-
|
|
580
|
-
# Convert to a regular dict if crosswalk['payer_id'] is an OrderedDict
|
|
581
|
-
if isinstance(crosswalk['payer_id'], dict) and hasattr(crosswalk['payer_id'], 'items'):
|
|
582
|
-
crosswalk['payer_id'] = dict(crosswalk['payer_id'])
|
|
583
|
-
|
|
584
|
-
MediLink_ConfigLoader.log("Checking if old Payer ID {} exists in crosswalk.".format(old_payer_id), config, level="DEBUG")
|
|
585
|
-
|
|
586
|
-
MediLink_ConfigLoader.log("Attempting to replace old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="DEBUG")
|
|
587
|
-
|
|
588
|
-
# Check if the old payer ID exists before attempting to replace
|
|
589
|
-
if old_payer_id in crosswalk['payer_id']:
|
|
590
|
-
MediLink_ConfigLoader.log("Old Payer ID {} found. Proceeding with replacement.".format(old_payer_id), config, level="DEBUG")
|
|
591
|
-
|
|
592
|
-
# Store the details of the old payer ID
|
|
593
|
-
old_payer_details = crosswalk['payer_id'][old_payer_id]
|
|
594
|
-
MediLink_ConfigLoader.log("Storing details of old Payer ID {}: {}".format(old_payer_id, old_payer_details), config, level="DEBUG")
|
|
595
|
-
|
|
596
|
-
# Replace the old payer ID with the corrected one
|
|
597
|
-
crosswalk['payer_id'][corrected_payer_id] = old_payer_details
|
|
598
|
-
MediLink_ConfigLoader.log("Replaced old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
|
|
599
|
-
|
|
600
|
-
# Remove the old payer ID from the crosswalk
|
|
601
|
-
del crosswalk['payer_id'][old_payer_id]
|
|
602
|
-
MediLink_ConfigLoader.log("Removed old Payer ID {} from crosswalk.".format(old_payer_id), config, level="DEBUG")
|
|
603
|
-
|
|
604
|
-
# Fetch and store the payer name for the corrected ID
|
|
605
|
-
if fetch_and_store_payer_name(client, corrected_payer_id, crosswalk, config):
|
|
606
|
-
MediLink_ConfigLoader.log("Successfully fetched and stored payer name for corrected Payer ID {}.".format(corrected_payer_id), config, level="INFO")
|
|
607
|
-
else:
|
|
608
|
-
MediLink_ConfigLoader.log("Corrected Payer ID {} updated without a valid name.".format(corrected_payer_id), config, level="WARNING")
|
|
609
|
-
|
|
610
|
-
# Update csv_replacements
|
|
611
|
-
crosswalk.setdefault('csv_replacements', {})[old_payer_id] = corrected_payer_id
|
|
612
|
-
MediLink_ConfigLoader.log("Updated csv_replacements: {} -> {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
|
|
613
|
-
print("csv_replacements updated: '{}' -> '{}'.".format(old_payer_id, corrected_payer_id))
|
|
614
|
-
|
|
615
|
-
return save_crosswalk(client, config, crosswalk)
|
|
616
|
-
else:
|
|
617
|
-
MediLink_ConfigLoader.log("Failed to update crosswalk: old Payer ID {} not found.".format(old_payer_id), config, level="ERROR")
|
|
618
|
-
print("Failed to update crosswalk: could not find old Payer ID '{}'.".format(old_payer_id))
|
|
619
|
-
return False
|
|
620
418
|
|
|
621
|
-
def update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config, crosswalk):
|
|
622
|
-
"""
|
|
623
|
-
Updates the crosswalk with a new payer ID for a given insurance name.
|
|
624
|
-
|
|
625
|
-
Args:
|
|
626
|
-
insurance_name (str): The name of the insurance to associate with the new payer ID.
|
|
627
|
-
payer_id (str): The new payer ID to be added.
|
|
628
|
-
config (dict): Configuration settings for logging.
|
|
629
|
-
"""
|
|
630
|
-
# Ensure full configuration and crosswalk are loaded
|
|
631
|
-
config, crosswalk = ensure_full_config_loaded(config, crosswalk)
|
|
632
|
-
|
|
633
|
-
try:
|
|
634
|
-
# Check if 'payer_id' is present in the crosswalk
|
|
635
|
-
if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
|
|
636
|
-
# Reload the crosswalk if 'payer_id' is missing or empty
|
|
637
|
-
_, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
|
|
638
|
-
MediLink_ConfigLoader.log("Reloaded crosswalk configuration from {}.".format(config.get('crosswalkPath', 'crosswalk.json')), config, level="DEBUG")
|
|
639
|
-
except KeyError as e: # Handle KeyError for crosswalk
|
|
640
|
-
MediLink_ConfigLoader.log("KeyError while checking or reloading crosswalk: {}".format(e), config, level="ERROR")
|
|
641
|
-
print("KeyError while checking or reloading crosswalk in update_crosswalk_with_new_payer_id: {}".format(e))
|
|
642
|
-
return False
|
|
643
|
-
except Exception as e:
|
|
644
|
-
MediLink_ConfigLoader.log("Error while checking or reloading crosswalk: {}".format(e), config, level="ERROR")
|
|
645
|
-
print("Error while checking or reloading crosswalk in update_crosswalk_with_new_payer_id: {}".format(e))
|
|
646
|
-
return False
|
|
647
|
-
|
|
648
|
-
# Load the Medisoft ID for the given insurance name
|
|
649
|
-
try:
|
|
650
|
-
medisoft_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config).get(insurance_name)
|
|
651
|
-
except KeyError as e: # Handle KeyError for config
|
|
652
|
-
MediLink_ConfigLoader.log("KeyError while loading Medisoft ID: {}".format(e), config, level="ERROR")
|
|
653
|
-
print("KeyError while loading Medisoft ID for insurance name {}: {}".format(insurance_name, e))
|
|
654
|
-
return False
|
|
655
419
|
|
|
656
|
-
MediLink_ConfigLoader.log("Retrieved Medisoft ID for insurance name {}: {}.".format(insurance_name, medisoft_id), config, level="DEBUG")
|
|
657
|
-
# print("DEBUG: Retrieved Medisoft ID for insurance name {}: {}.".format(insurance_name, medisoft_id))
|
|
658
|
-
|
|
659
|
-
if medisoft_id:
|
|
660
|
-
medisoft_id_str = str(medisoft_id)
|
|
661
|
-
MediLink_ConfigLoader.log("Processing to update crosswalk with new payer ID: {} for insurance name: {}.".format(payer_id, insurance_name), config, level="DEBUG")
|
|
662
|
-
|
|
663
|
-
# Initialize the payer ID entry if it doesn't exist
|
|
664
|
-
if payer_id not in crosswalk['payer_id']:
|
|
665
|
-
selected_endpoint = select_endpoint(config) # Use the helper function to select the endpoint
|
|
666
|
-
|
|
667
|
-
# Ensure the 'payer_id' key exists in the crosswalk
|
|
668
|
-
crosswalk['payer_id'][payer_id] = {
|
|
669
|
-
'endpoint': selected_endpoint,
|
|
670
|
-
'medisoft_id': [], # PERFORMANCE FIX: Use list instead of set to avoid conversions
|
|
671
|
-
'medisoft_medicare_id': []
|
|
672
|
-
}
|
|
673
|
-
MediLink_ConfigLoader.log("Initialized payer ID {} in crosswalk with endpoint '{}'.".format(payer_id, selected_endpoint), config, level="DEBUG")
|
|
674
|
-
else:
|
|
675
|
-
# Check if the existing endpoint is valid
|
|
676
|
-
current_endpoint = crosswalk['payer_id'][payer_id].get('endpoint', None)
|
|
677
|
-
if current_endpoint and current_endpoint not in config['MediLink_Config']['endpoints']:
|
|
678
|
-
print("WARNING: The current endpoint '{}' for payer ID '{}' is not valid.".format(current_endpoint, payer_id))
|
|
679
|
-
MediLink_ConfigLoader.log("Current endpoint '{}' for payer ID '{}' is not valid. Prompting for selection.".format(current_endpoint, payer_id), config, level="WARNING")
|
|
680
|
-
selected_endpoint = select_endpoint(config, current_endpoint) # Prompt user to select a valid endpoint
|
|
681
|
-
crosswalk['payer_id'][payer_id]['endpoint'] = selected_endpoint # Update the endpoint in the crosswalk
|
|
682
|
-
MediLink_ConfigLoader.log("Updated payer ID {} with new endpoint '{}'.".format(payer_id, selected_endpoint), config, level="INFO")
|
|
683
|
-
else:
|
|
684
|
-
selected_endpoint = current_endpoint # Use the existing valid endpoint
|
|
685
|
-
|
|
686
|
-
# Add the insurance ID to the payer ID entry - with error handling for the .add() operation
|
|
687
|
-
try:
|
|
688
|
-
if not isinstance(crosswalk['payer_id'][payer_id]['medisoft_id'], set):
|
|
689
|
-
# Convert to set if it's not already one
|
|
690
|
-
crosswalk['payer_id'][payer_id]['medisoft_id'] = set(crosswalk['payer_id'][payer_id]['medisoft_id'])
|
|
691
|
-
MediLink_ConfigLoader.log("Converted medisoft_id to set for payer ID {}.".format(payer_id), config, level="DEBUG")
|
|
692
|
-
|
|
693
|
-
crosswalk['payer_id'][payer_id]['medisoft_id'].add(str(medisoft_id_str)) # Ensure IDs are strings
|
|
694
|
-
MediLink_ConfigLoader.log(
|
|
695
|
-
"Added new insurance ID {} to payer ID {}.".format(medisoft_id_str, payer_id),
|
|
696
|
-
config,
|
|
697
|
-
level="INFO"
|
|
698
|
-
)
|
|
699
|
-
except AttributeError as e:
|
|
700
|
-
MediLink_ConfigLoader.log("AttributeError while adding medisoft_id: {}".format(e), config, level="ERROR")
|
|
701
|
-
print("Error adding medisoft_id for payer ID {}: {}".format(payer_id, e))
|
|
702
|
-
return False
|
|
703
|
-
|
|
704
|
-
# Fetch and store the payer name for the new payer ID
|
|
705
|
-
if fetch_and_store_payer_name(client, payer_id, crosswalk, config):
|
|
706
|
-
MediLink_ConfigLoader.log("Successfully fetched and stored payer name for new payer ID {}.".format(payer_id), config, level="INFO")
|
|
707
|
-
MediLink_ConfigLoader.log("Updated crosswalk with new payer ID {} for insurance name {}.".format(payer_id, insurance_name), config, level="INFO")
|
|
708
|
-
else:
|
|
709
|
-
MediLink_ConfigLoader.log("Added new payer ID {} without a valid name for insurance name {}.".format(payer_id, insurance_name), config, level="WARNING")
|
|
710
|
-
|
|
711
|
-
# Save the updated crosswalk
|
|
712
|
-
save_crosswalk(client, config, crosswalk)
|
|
713
|
-
MediLink_ConfigLoader.log("Crosswalk saved successfully after updating payer ID {}.".format(payer_id), config, level="DEBUG")
|
|
714
|
-
else:
|
|
715
|
-
message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}.".format(insurance_name)
|
|
716
|
-
print(message)
|
|
717
|
-
MediLink_ConfigLoader.log(message, config, level="ERROR")
|
|
718
|
-
|
|
719
|
-
def save_crosswalk(client, config, crosswalk, skip_api_operations=False):
|
|
720
|
-
"""
|
|
721
|
-
Saves the crosswalk to a JSON file. Ensures that all necessary keys are present and logs the outcome.
|
|
722
|
-
|
|
723
|
-
Args:
|
|
724
|
-
client (APIClient): API client for fetching payer names (ignored if skip_api_operations=True).
|
|
725
|
-
config (dict): Configuration settings for logging.
|
|
726
|
-
crosswalk (dict): The crosswalk dictionary to save.
|
|
727
|
-
skip_api_operations (bool): If True, skips API calls and user prompts for faster saves.
|
|
728
|
-
|
|
729
|
-
Returns:
|
|
730
|
-
bool: True if the crosswalk was saved successfully, False otherwise.
|
|
731
|
-
"""
|
|
732
|
-
try:
|
|
733
|
-
# Determine the path to save the crosswalk
|
|
734
|
-
crosswalk_path = config['MediLink_Config']['crosswalkPath']
|
|
735
|
-
MediLink_ConfigLoader.log("Determined crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
|
|
736
|
-
except KeyError:
|
|
737
|
-
crosswalk_path = config.get('crosswalkPath', 'crosswalk.json')
|
|
738
|
-
MediLink_ConfigLoader.log("Using default crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
|
|
739
|
-
|
|
740
|
-
# Validate endpoints for each payer ID in the crosswalk
|
|
741
|
-
for payer_id, details in crosswalk.get('payer_id', {}).items():
|
|
742
|
-
current_endpoint = details.get('endpoint', None)
|
|
743
|
-
if current_endpoint and current_endpoint not in config['MediLink_Config']['endpoints']:
|
|
744
|
-
if skip_api_operations:
|
|
745
|
-
# Log warning but don't prompt user during API-bypass mode
|
|
746
|
-
MediLink_ConfigLoader.log("WARNING: Invalid endpoint '{}' for payer ID '{}' - skipping correction due to API bypass mode".format(current_endpoint, payer_id), config, level="WARNING")
|
|
747
|
-
else:
|
|
748
|
-
print("WARNING: The current endpoint '{}' for payer ID '{}' is not valid.".format(current_endpoint, payer_id))
|
|
749
|
-
MediLink_ConfigLoader.log("Current endpoint '{}' for payer ID '{}' is not valid. Prompting for selection.".format(current_endpoint, payer_id), config, level="WARNING")
|
|
750
|
-
selected_endpoint = select_endpoint(config, current_endpoint) # Prompt user to select a valid endpoint
|
|
751
|
-
crosswalk['payer_id'][payer_id]['endpoint'] = selected_endpoint # Update the endpoint in the crosswalk
|
|
752
|
-
MediLink_ConfigLoader.log("Updated payer ID {} with new endpoint '{}'.".format(payer_id, selected_endpoint), config, level="INFO")
|
|
753
|
-
|
|
754
|
-
try:
|
|
755
|
-
# Log API bypass mode if enabled
|
|
756
|
-
if skip_api_operations:
|
|
757
|
-
MediLink_ConfigLoader.log("save_crosswalk running in API bypass mode - skipping API calls and user prompts", config, level="INFO")
|
|
758
|
-
|
|
759
|
-
# Initialize the 'payer_id' key if it doesn't exist
|
|
760
|
-
if 'payer_id' not in crosswalk:
|
|
761
|
-
print("save_crosswalk is initializing 'payer_id' key...")
|
|
762
|
-
crosswalk['payer_id'] = {}
|
|
763
|
-
MediLink_ConfigLoader.log("Initialized 'payer_id' key in crosswalk.", config, level="INFO")
|
|
764
|
-
|
|
765
|
-
# Ensure all payer IDs have a name and initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
|
|
766
|
-
for payer_id in crosswalk['payer_id']:
|
|
767
|
-
if 'name' not in crosswalk['payer_id'][payer_id]:
|
|
768
|
-
if skip_api_operations:
|
|
769
|
-
# Set placeholder name and log for MediBot to handle later
|
|
770
|
-
crosswalk['payer_id'][payer_id]['name'] = 'Unknown'
|
|
771
|
-
MediLink_ConfigLoader.log("Set placeholder name for payer ID {} - will be resolved by MediBot health check".format(payer_id), config, level="INFO")
|
|
772
|
-
else:
|
|
773
|
-
fetch_and_store_payer_name(client, payer_id, crosswalk, config)
|
|
774
|
-
MediLink_ConfigLoader.log("Fetched and stored payer name for payer ID: {}.".format(payer_id), config, level="DEBUG")
|
|
775
|
-
|
|
776
|
-
# Check for the endpoint key
|
|
777
|
-
if 'endpoint' not in crosswalk['payer_id'][payer_id]:
|
|
778
|
-
if skip_api_operations:
|
|
779
|
-
# Set default endpoint and log
|
|
780
|
-
crosswalk['payer_id'][payer_id]['endpoint'] = 'AVAILITY'
|
|
781
|
-
MediLink_ConfigLoader.log("Set default endpoint for payer ID {} - can be adjusted via MediBot if needed".format(payer_id), config, level="INFO")
|
|
782
|
-
else:
|
|
783
|
-
crosswalk['payer_id'][payer_id]['endpoint'] = select_endpoint(config) # Use the helper function to set the endpoint
|
|
784
|
-
MediLink_ConfigLoader.log("Initialized 'endpoint' for payer ID {}.".format(payer_id), config, level="DEBUG")
|
|
785
|
-
|
|
786
|
-
# Initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
|
|
787
|
-
crosswalk['payer_id'][payer_id].setdefault('medisoft_id', [])
|
|
788
|
-
crosswalk['payer_id'][payer_id].setdefault('medisoft_medicare_id', []) # does this work in 3.4.4?
|
|
789
|
-
MediLink_ConfigLoader.log("Ensured 'medisoft_id' and 'medisoft_medicare_id' for payer ID {} are initialized.".format(payer_id), config, level="DEBUG")
|
|
790
|
-
|
|
791
|
-
# Convert sets to sorted lists for JSON serialization
|
|
792
|
-
for payer_id, details in crosswalk.get('payer_id', {}).items():
|
|
793
|
-
if isinstance(details.get('medisoft_id'), set):
|
|
794
|
-
crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(details['medisoft_id']))
|
|
795
|
-
MediLink_ConfigLoader.log("Converted medisoft_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
|
|
796
|
-
if isinstance(details.get('medisoft_medicare_id'), set):
|
|
797
|
-
crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(details['medisoft_medicare_id']))
|
|
798
|
-
MediLink_ConfigLoader.log("Converted medisoft_medicare_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
|
|
799
|
-
|
|
800
|
-
# Write the crosswalk to the specified file
|
|
801
|
-
with open(crosswalk_path, 'w') as file:
|
|
802
|
-
json.dump(crosswalk, file, indent=4)
|
|
803
|
-
|
|
804
|
-
MediLink_ConfigLoader.log(
|
|
805
|
-
"Crosswalk saved successfully to {}.".format(crosswalk_path),
|
|
806
|
-
config,
|
|
807
|
-
level="INFO"
|
|
808
|
-
)
|
|
809
|
-
print("Crosswalk saved successfully to {}.".format(crosswalk_path))
|
|
810
|
-
return True
|
|
811
|
-
except KeyError as e:
|
|
812
|
-
print("Key Error: A required key is missing in the crosswalk data - {}.".format(e))
|
|
813
|
-
MediLink_ConfigLoader.log("Key Error while saving crosswalk: {}.".format(e), config, level="ERROR")
|
|
814
|
-
return False
|
|
815
|
-
except TypeError as e:
|
|
816
|
-
print("Type Error: There was a type issue with the data being saved in the crosswalk - {}.".format(e))
|
|
817
|
-
MediLink_ConfigLoader.log("Type Error while saving crosswalk: {}.".format(e), config, level="ERROR")
|
|
818
|
-
return False
|
|
819
|
-
except IOError as e:
|
|
820
|
-
print("I/O Error: An error occurred while writing to the crosswalk file - {}.".format(e))
|
|
821
|
-
MediLink_ConfigLoader.log("I/O Error while saving crosswalk: {}.".format(e), config, level="ERROR")
|
|
822
|
-
return False
|
|
823
|
-
except Exception as e:
|
|
824
|
-
print("Unexpected crosswalk error: {}.".format(e))
|
|
825
|
-
MediLink_ConfigLoader.log("Unexpected error while saving crosswalk: {}.".format(e), config, level="ERROR")
|
|
826
|
-
return False
|
|
827
|
-
|
|
828
|
-
def select_endpoint(config, current_endpoint=None):
|
|
829
|
-
# BUG Check upstream for the config. One of these is not being passed correctly so we're having to do this check here.
|
|
830
|
-
"""
|
|
831
|
-
Prompts the user to select an endpoint from the available options or returns the default endpoint.
|
|
832
|
-
Validates the current endpoint against the available options.
|
|
833
|
-
|
|
834
|
-
Args:
|
|
835
|
-
config (dict): Configuration settings for logging. Can be either the full config or config['MediLink_Config'].
|
|
836
|
-
current_endpoint (str, optional): The current endpoint to validate.
|
|
837
|
-
|
|
838
|
-
Returns:
|
|
839
|
-
str: The selected endpoint key.
|
|
840
|
-
|
|
841
|
-
Raises:
|
|
842
|
-
ValueError: If the config does not contain valid endpoint information.
|
|
843
|
-
"""
|
|
844
|
-
# Determine the effective MediLink_Config
|
|
845
|
-
if 'MediLink_Config' in config:
|
|
846
|
-
medi_link_config = config['MediLink_Config']
|
|
847
|
-
MediLink_ConfigLoader.log("Using 'MediLink_Config' from the provided configuration.", config, level="DEBUG")
|
|
848
|
-
else:
|
|
849
|
-
medi_link_config = config
|
|
850
|
-
MediLink_ConfigLoader.log("Using the provided configuration directly as 'MediLink_Config'.", config, level="DEBUG")
|
|
851
|
-
|
|
852
|
-
# Attempt to retrieve endpoint options
|
|
853
|
-
try:
|
|
854
|
-
endpoint_options = list(medi_link_config['endpoints'].keys())
|
|
855
|
-
MediLink_ConfigLoader.log("Successfully retrieved endpoint options.", config, level="DEBUG")
|
|
856
|
-
except KeyError:
|
|
857
|
-
MediLink_ConfigLoader.log("Failed to retrieve endpoint options due to KeyError.", config, level="ERROR")
|
|
858
|
-
raise ValueError("Invalid configuration: 'endpoints' not found in config.")
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
# Ensure there are available endpoints
|
|
862
|
-
if not endpoint_options:
|
|
863
|
-
MediLink_ConfigLoader.log("No endpoints available in the configuration.", config, level="ERROR")
|
|
864
|
-
raise ValueError("No endpoints available in the configuration.")
|
|
865
|
-
else:
|
|
866
|
-
MediLink_ConfigLoader.log("Available endpoints found in the configuration.", config, level="DEBUG")
|
|
867
|
-
|
|
868
|
-
print("Available endpoints:")
|
|
869
|
-
for idx, key in enumerate(endpoint_options):
|
|
870
|
-
# Safely retrieve the endpoint name
|
|
871
|
-
endpoint_name = medi_link_config['endpoints'].get(key, {}).get('name', key)
|
|
872
|
-
print("{0}: {1}".format(idx + 1, endpoint_name))
|
|
873
|
-
|
|
874
|
-
# Validate the current endpoint if provided
|
|
875
|
-
if current_endpoint and current_endpoint not in endpoint_options:
|
|
876
|
-
print("WARNING: The current endpoint '{}' is not valid.".format(current_endpoint))
|
|
877
|
-
MediLink_ConfigLoader.log("Current endpoint '{}' is not valid. Prompting for selection.".format(current_endpoint), config, level="WARNING")
|
|
878
|
-
|
|
879
|
-
user_choice = input("Select an endpoint by number (or press Enter to use the default): ").strip()
|
|
880
|
-
|
|
881
|
-
if user_choice.isdigit() and 1 <= int(user_choice) <= len(endpoint_options):
|
|
882
|
-
selected_endpoint = endpoint_options[int(user_choice) - 1] # Use the key instead of the name
|
|
883
|
-
else:
|
|
884
|
-
selected_endpoint = endpoint_options[0] # Default to the first key
|
|
885
|
-
MediLink_ConfigLoader.log("User opted for default endpoint: " + selected_endpoint, config, level="INFO")
|
|
886
|
-
|
|
887
|
-
return selected_endpoint
|
|
888
|
-
|
|
889
|
-
def ensure_full_config_loaded(config=None, crosswalk=None):
|
|
890
|
-
"""
|
|
891
|
-
Ensures that the full base configuration and crosswalk are loaded.
|
|
892
|
-
If the base config is not valid or the crosswalk is None, reloads them.
|
|
893
|
-
|
|
894
|
-
Args:
|
|
895
|
-
config (dict, optional): The current configuration.
|
|
896
|
-
crosswalk (dict, optional): The current crosswalk.
|
|
897
|
-
|
|
898
|
-
Returns:
|
|
899
|
-
tuple: The loaded base configuration and crosswalk.
|
|
900
|
-
"""
|
|
901
|
-
MediLink_ConfigLoader.log("Ensuring full configuration and crosswalk are loaded.", level="DEBUG")
|
|
902
|
-
|
|
903
|
-
# Reload configuration if necessary
|
|
904
|
-
if config is None or 'MediLink_Config' not in config:
|
|
905
|
-
MediLink_ConfigLoader.log("Base config is missing or invalid. Reloading configuration.", level="WARNING")
|
|
906
|
-
config, crosswalk = MediLink_ConfigLoader.load_configuration()
|
|
907
|
-
MediLink_ConfigLoader.log("Base configuration and crosswalk reloaded.", level="INFO")
|
|
908
|
-
else:
|
|
909
|
-
MediLink_ConfigLoader.log("Base config was correctly passed.", level="DEBUG")
|
|
910
420
|
|
|
911
|
-
# Reload crosswalk if necessary
|
|
912
|
-
if crosswalk is None:
|
|
913
|
-
MediLink_ConfigLoader.log("Crosswalk is None. Reloading crosswalk.", level="WARNING")
|
|
914
|
-
_, crosswalk = MediLink_ConfigLoader.load_configuration() # Reloading to get the crosswalk
|
|
915
|
-
MediLink_ConfigLoader.log("Crosswalk reloaded.", level="INFO")
|
|
916
421
|
|
|
917
|
-
return config, crosswalk
|