medicafe 0.240419.2__py3-none-any.whl → 0.240517.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 +166 -38
- MediBot/MediBot.py +74 -44
- MediBot/MediBot_Crosswalk_Library.py +280 -0
- MediBot/MediBot_Preprocessor.py +155 -191
- MediBot/MediBot_Preprocessor_lib.py +357 -0
- MediBot/MediBot_UI.py +80 -30
- MediBot/MediBot_dataformat_library.py +88 -35
- MediBot/MediBot_docx_decoder.py +80 -0
- MediBot/update_medicafe.py +46 -8
- MediLink/MediLink.py +138 -34
- MediLink/MediLink_837p_encoder.py +319 -209
- MediLink/MediLink_837p_encoder_library.py +453 -242
- MediLink/MediLink_API_v2.py +174 -0
- MediLink/MediLink_APIs.py +137 -0
- MediLink/MediLink_ConfigLoader.py +44 -32
- MediLink/MediLink_DataMgmt.py +85 -33
- MediLink/MediLink_Down.py +12 -35
- MediLink/MediLink_ERA_decoder.py +4 -4
- MediLink/MediLink_Gmail.py +99 -3
- MediLink/MediLink_Mailer.py +7 -0
- MediLink/MediLink_Scheduler.py +41 -0
- MediLink/MediLink_UI.py +19 -17
- MediLink/MediLink_Up.py +297 -31
- MediLink/MediLink_batch.bat +1 -1
- MediLink/test.py +74 -0
- medicafe-0.240517.0.dist-info/METADATA +53 -0
- medicafe-0.240517.0.dist-info/RECORD +39 -0
- {medicafe-0.240419.2.dist-info → medicafe-0.240517.0.dist-info}/WHEEL +5 -5
- medicafe-0.240419.2.dist-info/METADATA +0 -19
- medicafe-0.240419.2.dist-info/RECORD +0 -32
- {medicafe-0.240419.2.dist-info → medicafe-0.240517.0.dist-info}/LICENSE +0 -0
- {medicafe-0.240419.2.dist-info → medicafe-0.240517.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Add parent directory of the project to the Python path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
sys.path.append(project_dir)
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import MediLink_ConfigLoader
|
|
13
|
+
except ImportError:
|
|
14
|
+
from MediLink import MediLink_ConfigLoader
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from MediLink_API_v2 import fetch_payer_name_from_api
|
|
18
|
+
except ImportError:
|
|
19
|
+
from MediLink import MediLink_API_v2
|
|
20
|
+
fetch_payer_name_from_api = MediLink_API_v2.fetch_payer_name_from_api
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from MediBot import MediBot_Preprocessor_lib
|
|
24
|
+
except ImportError:
|
|
25
|
+
import MediBot_Preprocessor_lib
|
|
26
|
+
|
|
27
|
+
def check_and_initialize_crosswalk(config):
|
|
28
|
+
"""
|
|
29
|
+
Checks if the 'payer_id' key exists in the crosswalk. If not, prompts the user
|
|
30
|
+
to initialize the crosswalk.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
config (dict): Configuration settings.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
boolean: True if succeeded.
|
|
37
|
+
"""
|
|
38
|
+
# Reload for safety
|
|
39
|
+
config, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# Attempt to access the 'payer_id' key to ensure it exists
|
|
43
|
+
if 'payer_id' not in crosswalk:
|
|
44
|
+
raise KeyError("Missing 'payer_id' key in crosswalk.")
|
|
45
|
+
except KeyError:
|
|
46
|
+
error_message = "The 'payer_id' key does not exist in the crosswalk configuration. \n" \
|
|
47
|
+
"This could be because the crosswalk is not initialized. \n" \
|
|
48
|
+
"Consider running the Crosswalk initializer."
|
|
49
|
+
print(error_message)
|
|
50
|
+
MediLink_ConfigLoader.log(error_message, config, level="ERROR")
|
|
51
|
+
|
|
52
|
+
# Prompt user to initialize crosswalk
|
|
53
|
+
initialize_choice = input("\nADVANCED OPTION: The crosswalk may not be initialized. \nType 'yes' to initialize it now: ").strip().lower()
|
|
54
|
+
if initialize_choice == 'yes':
|
|
55
|
+
initialize_crosswalk_from_mapat()
|
|
56
|
+
_, crosswalk = MediLink_ConfigLoader.load_configuration() # Reload crosswalk
|
|
57
|
+
MediLink_ConfigLoader.log("Crosswalk reloaded successfully.", config, level="INFO")
|
|
58
|
+
else:
|
|
59
|
+
raise KeyError(error_message)
|
|
60
|
+
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
def validate_and_correct_payer_ids(crosswalk, config):
|
|
64
|
+
"""Validates payer IDs via API and handles invalid IDs through user intervention."""
|
|
65
|
+
for payer_id in list(crosswalk['payer_id'].keys()):
|
|
66
|
+
try:
|
|
67
|
+
fetch_payer_name_from_api(payer_id, config, primary_endpoint=None)
|
|
68
|
+
MediLink_ConfigLoader.log("Payer ID {} validated successfully.".format(payer_id), config, level="INFO")
|
|
69
|
+
except Exception as e:
|
|
70
|
+
MediLink_ConfigLoader.log("Payer ID validation failed for {}: {}".format(payer_id, e), config, level="WARNING")
|
|
71
|
+
|
|
72
|
+
while True:
|
|
73
|
+
corrected_payer_id = input("WWARNING: Invalid Payer ID {}. Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: ".format(payer_id))
|
|
74
|
+
|
|
75
|
+
if corrected_payer_id.strip().upper() == 'FORCE':
|
|
76
|
+
MediLink_ConfigLoader.log("User opted to force-continue with unresolved Payer ID {}. Warning: This may indicate an underlying issue.".format(payer_id), config, level="WARNING")
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
if corrected_payer_id:
|
|
80
|
+
try:
|
|
81
|
+
fetch_payer_name_from_api(corrected_payer_id, config, primary_endpoint=None)
|
|
82
|
+
MediLink_ConfigLoader.log("Corrected Payer ID {} validated successfully.".format(corrected_payer_id), config, level="INFO")
|
|
83
|
+
|
|
84
|
+
if update_crosswalk_with_corrected_payer_id(payer_id, corrected_payer_id, config, crosswalk):
|
|
85
|
+
if 'csv_replacements' not in crosswalk:
|
|
86
|
+
crosswalk['csv_replacements'] = {}
|
|
87
|
+
crosswalk['csv_replacements'][payer_id] = corrected_payer_id
|
|
88
|
+
MediLink_ConfigLoader.log("Added replacement filter: {} -> {}".format(payer_id, corrected_payer_id), config, level="INFO")
|
|
89
|
+
else:
|
|
90
|
+
print("Failed to update crosswalk with the corrected Payer ID {}.".format(corrected_payer_id))
|
|
91
|
+
MediLink_ConfigLoader.log("Failed to update crosswalk with the corrected Payer ID {}.".format(corrected_payer_id), config, level="ERROR")
|
|
92
|
+
break
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print("Corrected Payer ID {} validation failed: {}".format(corrected_payer_id, e))
|
|
95
|
+
MediLink_ConfigLoader.log("Corrected Payer ID {} validation failed: {}".format(corrected_payer_id, e), config, level="ERROR")
|
|
96
|
+
else:
|
|
97
|
+
print("Exiting initialization. Please correct the Payer ID and retry.")
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
|
|
100
|
+
def initialize_crosswalk_from_mapat():
|
|
101
|
+
"""
|
|
102
|
+
Input: Historical Carol's CSVs and MAPAT data.
|
|
103
|
+
|
|
104
|
+
Process:
|
|
105
|
+
Extract mappings from Carol's old CSVs to identify Payer IDs and associated Patient IDs.
|
|
106
|
+
Use MAPAT to correlate these Patient IDs with Insurance IDs.
|
|
107
|
+
Compile these mappings into the crosswalk, setting Payer IDs as keys and corresponding Insurance IDs as values.
|
|
108
|
+
|
|
109
|
+
Output: A fully populated crosswalk.json file that serves as a baseline for future updates.
|
|
110
|
+
"""
|
|
111
|
+
config, crosswalk = MediLink_ConfigLoader.load_configuration()
|
|
112
|
+
|
|
113
|
+
# Load historical mappings
|
|
114
|
+
try:
|
|
115
|
+
patient_id_to_insurance_id, payer_id_to_patient_ids = MediBot_Preprocessor_lib.load_data_sources(config, crosswalk)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
print(e)
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
# Map Payer IDs to Insurance IDs
|
|
121
|
+
payer_id_to_details = MediBot_Preprocessor_lib.map_payer_ids_to_insurance_ids(patient_id_to_insurance_id, payer_id_to_patient_ids)
|
|
122
|
+
|
|
123
|
+
# Update the crosswalk for payer IDs only, retaining other mappings
|
|
124
|
+
crosswalk['payer_id'] = payer_id_to_details
|
|
125
|
+
|
|
126
|
+
# Validate payer IDs via API and handle invalid IDs
|
|
127
|
+
validate_and_correct_payer_ids(crosswalk, config)
|
|
128
|
+
|
|
129
|
+
# Save the initial crosswalk
|
|
130
|
+
if save_crosswalk(config['MediLink_Config']['crosswalkPath'], crosswalk):
|
|
131
|
+
message = "Crosswalk initialized with mappings for {} payers.".format(len(crosswalk.get('payer_id', {})))
|
|
132
|
+
print(message)
|
|
133
|
+
MediLink_ConfigLoader.log(message, config, level="INFO")
|
|
134
|
+
else:
|
|
135
|
+
print("Failed to save the crosswalk.")
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
return payer_id_to_details
|
|
138
|
+
|
|
139
|
+
def crosswalk_update(config, crosswalk):
|
|
140
|
+
"""
|
|
141
|
+
Updates the `crosswalk.json` file using mappings from MAINS, Z.dat, and Carol's CSV. This function integrates
|
|
142
|
+
user-defined insurance mappings from Z.dat with existing payer-to-insurance mappings in the crosswalk,
|
|
143
|
+
and validates these mappings using MAINS.
|
|
144
|
+
|
|
145
|
+
Steps:
|
|
146
|
+
1. Load mappings from MAINS for translating insurance names to IDs.
|
|
147
|
+
2. Load mappings from the latest Carol's CSV for new patient entries mapping Patient IDs to Payer IDs.
|
|
148
|
+
3. Parse incremental data from Z.dat which contains recent user interactions mapping Patient IDs to Insurance Names.
|
|
149
|
+
4. Update the crosswalk using the loaded and parsed data, ensuring each Payer ID maps to the correct Insurance IDs.
|
|
150
|
+
5. Persist the updated mappings back to the crosswalk file.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
config (dict): Configuration dictionary containing paths and other settings.
|
|
154
|
+
crosswalk (dict): Existing crosswalk mapping Payer IDs to sets of Insurance IDs.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
bool: True if the crosswalk was successfully updated and saved, False otherwise.
|
|
158
|
+
"""
|
|
159
|
+
# Load insurance mappings from MAINS (Insurance Name to Insurance ID)
|
|
160
|
+
insurance_name_to_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config)
|
|
161
|
+
MediLink_ConfigLoader.log("Loaded insurance data from MAINS...")
|
|
162
|
+
|
|
163
|
+
# Load new Patient ID to Payer ID mappings from Carol's CSV (if necessary)
|
|
164
|
+
# TODO This is a low performance strategy.
|
|
165
|
+
patient_id_to_payer_id = MediBot_Preprocessor_lib.load_historical_payer_to_patient_mappings(config)
|
|
166
|
+
MediLink_ConfigLoader.log("Loaded historical mappings...")
|
|
167
|
+
|
|
168
|
+
# Load incremental mapping data from Z.dat (Patient ID to Insurance Name)
|
|
169
|
+
# TODO This may be a redundant approach?
|
|
170
|
+
patient_id_to_insurance_name = MediBot_Preprocessor_lib.parse_z_dat(config['MediLink_Config']['Z_DAT_PATH'], config['MediLink_Config'])
|
|
171
|
+
MediLink_ConfigLoader.log("Parsed Z data...")
|
|
172
|
+
|
|
173
|
+
# Update the crosswalk with new or revised mappings
|
|
174
|
+
for patient_id, payer_id in patient_id_to_payer_id.items():
|
|
175
|
+
insurance_name = patient_id_to_insurance_name.get(patient_id)
|
|
176
|
+
if insurance_name and insurance_name in insurance_name_to_id:
|
|
177
|
+
insurance_id = insurance_name_to_id[insurance_name]
|
|
178
|
+
|
|
179
|
+
# Ensure payer ID is in the crosswalk and initialize if not
|
|
180
|
+
MediLink_ConfigLoader.log("Initializing payer_id key...")
|
|
181
|
+
if 'payer_id' not in crosswalk:
|
|
182
|
+
crosswalk['payer_id'] = {}
|
|
183
|
+
if payer_id not in crosswalk['payer_id']:
|
|
184
|
+
# TODO The OPTUMEDI default here should be gathered via API and not just a default. There are 2 of these defaults!!
|
|
185
|
+
crosswalk['payer_id'][payer_id] = {'endpoint': 'OPTUMEDI', 'medisoft_id': set(), 'medisoft_medicare_id': set()}
|
|
186
|
+
|
|
187
|
+
# Update the medisoft_id set, temporarily using a set to avoid duplicates
|
|
188
|
+
crosswalk['payer_id'][payer_id]['medisoft_id'].add(insurance_id)
|
|
189
|
+
MediLink_ConfigLoader.log("Added new insurance ID {} to payer ID {}".format(insurance_id, payer_id))
|
|
190
|
+
|
|
191
|
+
# Convert sets to lists just before saving
|
|
192
|
+
for payer_id in crosswalk['payer_id']:
|
|
193
|
+
if isinstance(crosswalk['payer_id'][payer_id]['medisoft_id'], set):
|
|
194
|
+
crosswalk['payer_id'][payer_id]['medisoft_id'] = list(crosswalk['payer_id'][payer_id]['medisoft_id'])
|
|
195
|
+
if isinstance(crosswalk['payer_id'][payer_id]['medisoft_medicare_id'], set):
|
|
196
|
+
crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = list(crosswalk['payer_id'][payer_id]['medisoft_medicare_id'])
|
|
197
|
+
|
|
198
|
+
# Save the updated crosswalk to the specified file
|
|
199
|
+
return save_crosswalk(config['MediLink_Config']['crosswalkPath'], crosswalk)
|
|
200
|
+
|
|
201
|
+
def update_crosswalk_with_corrected_payer_id(old_payer_id, corrected_payer_id, config, crosswalk):
|
|
202
|
+
"""Updates the crosswalk with the corrected payer ID."""
|
|
203
|
+
# Update the payer_id section
|
|
204
|
+
if old_payer_id in crosswalk['payer_id']:
|
|
205
|
+
crosswalk['payer_id'][corrected_payer_id] = crosswalk['payer_id'].pop(old_payer_id)
|
|
206
|
+
MediLink_ConfigLoader.log("Crosswalk updated: replaced Payer ID {} with {}".format(old_payer_id, corrected_payer_id), config, level="INFO")
|
|
207
|
+
else:
|
|
208
|
+
MediLink_ConfigLoader.log("Failed to update crosswalk: could not find old Payer ID {}".format(old_payer_id), config, level="ERROR")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
# Update the csv_replacements section
|
|
212
|
+
if 'csv_replacements' not in crosswalk:
|
|
213
|
+
crosswalk['csv_replacements'] = {}
|
|
214
|
+
crosswalk['csv_replacements'][old_payer_id] = corrected_payer_id
|
|
215
|
+
MediLink_ConfigLoader.log("Crosswalk csv_replacements updated: added {} -> {}".format(old_payer_id, corrected_payer_id), config, level="INFO")
|
|
216
|
+
|
|
217
|
+
# Save the updated crosswalk
|
|
218
|
+
return save_crosswalk(config['MediLink_Config']['crosswalkPath'], crosswalk)
|
|
219
|
+
|
|
220
|
+
def update_crosswalk_with_new_payer_id(insurance_name, payer_id, config):
|
|
221
|
+
"""Updates the crosswalk with a new payer ID."""
|
|
222
|
+
_, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
|
|
223
|
+
medisoft_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config).get(insurance_name)
|
|
224
|
+
|
|
225
|
+
if medisoft_id:
|
|
226
|
+
medisoft_id_str = str(medisoft_id)
|
|
227
|
+
if payer_id not in crosswalk['payer_id']:
|
|
228
|
+
crosswalk['payer_id'][payer_id] = {"medisoft_id": [medisoft_id_str], "medisoft_medicare_id": []}
|
|
229
|
+
else:
|
|
230
|
+
crosswalk['payer_id'][payer_id]['medisoft_id'].append(medisoft_id_str)
|
|
231
|
+
save_crosswalk(config['MediLink_Config']['crosswalkPath'], crosswalk)
|
|
232
|
+
MediLink_ConfigLoader.log("Updated crosswalk with new payer ID {} for insurance name {}".format(payer_id, insurance_name), config, level="INFO")
|
|
233
|
+
else:
|
|
234
|
+
message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}".format(insurance_name)
|
|
235
|
+
print(message)
|
|
236
|
+
MediLink_ConfigLoader.log(message, config, level="ERROR")
|
|
237
|
+
|
|
238
|
+
def save_crosswalk(crosswalk_path, crosswalk):
|
|
239
|
+
"""
|
|
240
|
+
Saves the updated crosswalk to a JSON file.
|
|
241
|
+
Args:
|
|
242
|
+
crosswalk_path (str): Path to the crosswalk.json file.
|
|
243
|
+
crosswalk (dict): The updated crosswalk data.
|
|
244
|
+
Returns:
|
|
245
|
+
bool: True if the file was successfully saved, False otherwise.
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
# Initialize 'payer_id' key if not present
|
|
249
|
+
if 'payer_id' not in crosswalk:
|
|
250
|
+
print("save_crosswalk is initializing 'payer_id' key...")
|
|
251
|
+
crosswalk['payer_id'] = {}
|
|
252
|
+
|
|
253
|
+
# Convert all 'medisoft_id' fields from sets to lists if necessary
|
|
254
|
+
for k, v in crosswalk.get('payer_id', {}).items():
|
|
255
|
+
if isinstance(v.get('medisoft_id'), set):
|
|
256
|
+
v['medisoft_id'] = list(v['medisoft_id'])
|
|
257
|
+
|
|
258
|
+
with open(crosswalk_path, 'w') as file:
|
|
259
|
+
json.dump(crosswalk, file, indent=4) # Save the entire dictionary
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
except KeyError as e:
|
|
263
|
+
# Log the KeyError with specific information about what was missing
|
|
264
|
+
print("Key Error: A required key is missing in the crosswalk data -", e)
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
except TypeError as e:
|
|
268
|
+
# Handle data type errors (e.g., non-serializable types)
|
|
269
|
+
print("Type Error: There was a type issue with the data being saved in the crosswalk -", e)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
except IOError as e:
|
|
273
|
+
# Handle I/O errors related to file operations
|
|
274
|
+
print("I/O Error: An error occurred while writing to the crosswalk file -", e)
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
# A general exception catch to log any other exceptions that may not have been anticipated
|
|
279
|
+
print("Unexpected crosswalk error:", e)
|
|
280
|
+
return False
|