medicafe 0.240415.1__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.

Files changed (42) hide show
  1. MediBot/MediBot.bat +198 -0
  2. MediBot/MediBot.py +346 -0
  3. MediBot/MediBot_Charges.py +28 -0
  4. MediBot/MediBot_Crosswalk_Library.py +280 -0
  5. MediBot/MediBot_Preprocessor.py +247 -0
  6. MediBot/MediBot_Preprocessor_lib.py +357 -0
  7. MediBot/MediBot_UI.py +240 -0
  8. MediBot/MediBot_dataformat_library.py +198 -0
  9. MediBot/MediBot_docx_decoder.py +80 -0
  10. MediBot/MediPost.py +5 -0
  11. MediBot/PDF_to_CSV_Cleaner.py +211 -0
  12. MediBot/__init__.py +0 -0
  13. MediBot/update_json.py +43 -0
  14. MediBot/update_medicafe.py +57 -0
  15. MediLink/MediLink.py +381 -0
  16. MediLink/MediLink_277_decoder.py +92 -0
  17. MediLink/MediLink_837p_encoder.py +502 -0
  18. MediLink/MediLink_837p_encoder_library.py +890 -0
  19. MediLink/MediLink_API_v2.py +174 -0
  20. MediLink/MediLink_APIs.py +137 -0
  21. MediLink/MediLink_ConfigLoader.py +81 -0
  22. MediLink/MediLink_DataMgmt.py +258 -0
  23. MediLink/MediLink_Down.py +128 -0
  24. MediLink/MediLink_ERA_decoder.py +192 -0
  25. MediLink/MediLink_Gmail.py +100 -0
  26. MediLink/MediLink_Mailer.py +7 -0
  27. MediLink/MediLink_Scheduler.py +173 -0
  28. MediLink/MediLink_StatusCheck.py +4 -0
  29. MediLink/MediLink_UI.py +118 -0
  30. MediLink/MediLink_Up.py +383 -0
  31. MediLink/MediLink_batch.bat +7 -0
  32. MediLink/Soumit_api.py +22 -0
  33. MediLink/__init__.py +0 -0
  34. MediLink/test.py +74 -0
  35. medicafe-0.240517.0.dist-info/METADATA +53 -0
  36. medicafe-0.240517.0.dist-info/RECORD +39 -0
  37. {medicafe-0.240415.1.dist-info → medicafe-0.240517.0.dist-info}/WHEEL +1 -1
  38. medicafe-0.240517.0.dist-info/top_level.txt +2 -0
  39. medicafe-0.240415.1.dist-info/METADATA +0 -17
  40. medicafe-0.240415.1.dist-info/RECORD +0 -5
  41. medicafe-0.240415.1.dist-info/top_level.txt +0 -1
  42. {medicafe-0.240415.1.dist-info → medicafe-0.240517.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,118 @@
1
+ from datetime import datetime
2
+
3
+ """
4
+ Development Tasks for User Interface (UI) Enhancement in MediSoft Claims Submittal (MediLink) Script:
5
+
6
+ - [ ] Streamline user interaction for endpoint selection and patient adjustments with automated endpoint validation.
7
+ - [ ] Strengthen error handling in file conversion, transmission, and user inputs with actionable user feedback.
8
+ - [ ] Expand logging levels and develop a user notification system for process milestones and errors.
9
+ - [ ] Focus workflow on patient details for endpoint adjustments, facilitating patient-centric file submissions.
10
+ - [ ] Implement internet connectivity checks with retry options for submissions and offer pause/resume capabilities.
11
+ - [ ] Include final review and confirmation steps before submission, allowing for endpoint adjustments per patient.
12
+ - [ ] TODO Need to resolve suggested endpoint issues/refresh/quasi-persist (this is partially implemented, careful)
13
+ - [ ] TODO Augment the menu for displaying the insurance type information before submittal.
14
+ """
15
+
16
+ def display_welcome():
17
+ print("\n" + "-" * 60)
18
+ print(" *~^~*: Welcome to MediLink! :*~^~*")
19
+ print("-" * 60 + "\n")
20
+
21
+ def display_menu(options):
22
+ print("Menu Options:")
23
+ for i, option in enumerate(options):
24
+ print("{0}. {1}".format(i+1, option))
25
+
26
+ def get_user_choice():
27
+ return input("Enter your choice: ").strip()
28
+
29
+ def display_exit_message():
30
+ print("\nExiting MediLink.")
31
+
32
+ def display_invalid_choice():
33
+ print("Invalid choice. Please select a valid option.")
34
+
35
+ def display_patient_options(detailed_patient_data):
36
+ """
37
+ Displays a list of patients with their current suggested endpoints, prompting for selections to adjust.
38
+ """
39
+ print("\nPlease select the patients to adjust by entering their numbers separated by commas\n(e.g., 1,3,5):")
40
+ # Can disable this extra print for now because the px list would already be on-screen.
41
+ #for i, data in enumerate(detailed_patient_data, start=1):
42
+ # patient_info = "{0} ({1}) - {2}".format(data['patient_name'], data['patient_id'], data['surgery_date'])
43
+ # endpoint = data.get('suggested_endpoint', 'N/A')
44
+ # print("{:<3}. {:<30} Current Endpoint: {}".format(i, patient_info, endpoint))
45
+
46
+ def get_selected_indices(patient_count):
47
+ """
48
+ Collects user input for selected indices to adjust endpoints.
49
+ """
50
+ selected_indices_input = input("> ")
51
+ selected_indices = [int(index.strip()) - 1 for index in selected_indices_input.split(',') if index.strip().isdigit() and 0 <= int(index.strip()) - 1 < patient_count]
52
+ return selected_indices
53
+
54
+ def display_patient_for_adjustment(patient_name, suggested_endpoint):
55
+ """
56
+ Displays the current endpoint for a selected patient and prompts for a change.
57
+ """
58
+ print("\n- {0} | Current Endpoint: {1}".format(patient_name, suggested_endpoint))
59
+
60
+ def get_endpoint_decision():
61
+ """
62
+ Asks the user if they want to change the endpoint.
63
+ """
64
+ return input("Change endpoint? (Y/N): ").strip().lower()
65
+
66
+ def display_endpoint_options(endpoints_config):
67
+ """
68
+ Displays the endpoint options to the user based on the provided mapping.
69
+
70
+ Args:
71
+ endpoints_config (dict): A dictionary mapping endpoint keys to their properties,
72
+ where each property includes a 'name' key for the user-friendly name.
73
+ Example: {'Availity': {'name': 'Availity'}, 'OptumEDI': {'name': 'OptumEDI'}, ...}
74
+
75
+ Returns:
76
+ None
77
+ """
78
+ print("Select the new endpoint for the patient:")
79
+ for index, (key, details) in enumerate(endpoints_config.items(), 1):
80
+ print("{0}. {1}".format(index, details['name']))
81
+
82
+ def get_new_endpoint_choice():
83
+ """
84
+ Gets the user's choice for a new endpoint.
85
+ """
86
+ return input("Select desired endpoint (e.g. 1, 2): ").strip()
87
+
88
+ def display_patient_summaries(detailed_patient_data):
89
+ """
90
+ Displays summaries of all patients and their suggested endpoints.
91
+ """
92
+ print("\nSummary of patient details and suggested endpoint:")
93
+ for index, summary in enumerate(detailed_patient_data, start=1):
94
+ try:
95
+ display_file_summary(index, summary)
96
+ except KeyError as e:
97
+ print("Summary at index {} is missing key: {}".format(index, e))
98
+
99
+ def ask_for_proceeding_with_endpoints():
100
+ """
101
+ Asks the user if they want to proceed with all suggested endpoints.
102
+ """
103
+ proceed = input("\nDo you want to proceed with all suggested endpoints? (Y/N): ").strip().lower()
104
+ return proceed == 'y'
105
+
106
+ def display_file_summary(index, summary):
107
+ # Ensure surgery_date is converted to a datetime object
108
+ surgery_date = datetime.strptime(summary['surgery_date'], "%m-%d-%y")
109
+
110
+ # Displays the summary of a file.
111
+ print("{:02d}. {:5} (ID: {:<8}) {:20} {:15} Suggested Endpoint: {}".format(
112
+ index,
113
+ surgery_date.strftime("%m-%d"),
114
+ summary['patient_id'],
115
+ summary['patient_name'][:20],
116
+ summary['primary_insurance'][:15],
117
+ summary['suggested_endpoint'])
118
+ )
@@ -0,0 +1,383 @@
1
+ from datetime import datetime
2
+ import os
3
+ import re
4
+ import subprocess
5
+ from tqdm import tqdm
6
+ import MediLink_837p_encoder
7
+ from MediLink_ConfigLoader import log
8
+ from MediLink_DataMgmt import operate_winscp
9
+
10
+ """
11
+ Handles the transmission of files to endpoints and related tasks for the MediLink script.
12
+
13
+ Functions:
14
+ - check_internet_connection(): Checks for an active internet connection.
15
+ - UPDATE THIS: transmit_files_to_endpoint(converted_files, endpoint_config, local_storage_path): Transmits files to a specified endpoint using WinSCP.
16
+ - handle_transmission_result(transmission_result): Handles the result of the file transmission process.
17
+ - submit_claims(detailed_patient_data_grouped_by_endpoint, config): Submits claims to respective endpoints based on patient data and configuration.
18
+ - confirm_transmission(detailed_patient_data_grouped_by_endpoint): Displays patient data ready for transmission and asks for confirmation.
19
+
20
+ This module provides functionality for transmitting files, confirming transmissions, and ensuring connectivity for the MediLink script.
21
+ """
22
+
23
+ # Internet Connectivity Check
24
+ def check_internet_connection():
25
+ """
26
+ Checks if there is an active internet connection.
27
+ Returns: Boolean indicating internet connectivity status.
28
+ """
29
+ try:
30
+ # Run a ping command to a reliable external server (e.g., Google's DNS server)
31
+ ping_process = subprocess.Popen(["ping", "-n", "1", "8.8.8.8"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
32
+ ping_output, ping_error = ping_process.communicate()
33
+
34
+ # Check if the ping was successful
35
+ if "Reply from" in ping_output.decode("utf-8"):
36
+ return True
37
+ else:
38
+ return False
39
+ except Exception as e:
40
+ print("An error occurred checking for internet connectivity:", e)
41
+ return False
42
+
43
+ def submit_claims(detailed_patient_data_grouped_by_endpoint, config):
44
+ # Accumulate submission results
45
+ submission_results = {}
46
+
47
+ if not detailed_patient_data_grouped_by_endpoint:
48
+ print("No new files detected for submission.")
49
+ return
50
+
51
+ # Wrap the iteration with tqdm for a progress bar
52
+ for endpoint, patients_data in tqdm(detailed_patient_data_grouped_by_endpoint.items(), desc="Progress", unit="endpoint"):
53
+ if not patients_data: # Skip if no patient data for the endpoint
54
+ continue
55
+
56
+ # Attempt submission to each endpoint
57
+ if True: #confirm_transmission({endpoint: patients_data}): # Confirm transmission to each endpoint with detailed overview
58
+ if check_internet_connection():
59
+ # Process files per endpoint
60
+ converted_files = MediLink_837p_encoder.convert_files_for_submission(patients_data, config)
61
+ if converted_files: # Check if files were successfully converted
62
+ # Transmit files per endpoint
63
+ try:
64
+ operation_type = "upload"
65
+ transmission_result = operate_winscp(operation_type, converted_files, config['MediLink_Config']['endpoints'][endpoint], config['MediLink_Config']['local_claims_path'], config)
66
+ success_dict = handle_transmission_result(transmission_result, config, operation_type)
67
+ submission_results[endpoint] = success_dict
68
+ # TODO Future work: Availity SentFiles: Retrieve and interpret the response file from Availity SentFiles to acknowledge successful transfers.
69
+ except FileNotFoundError as e:
70
+ # Log that the log file is not found
71
+ print("Failed to transmit files to {0}. Error: Log file not found - {1}".format(endpoint, str(e)))
72
+ submission_results[endpoint] = {"status": False, "error": "Log file not found - " + str(e)}
73
+
74
+ except IOError as e:
75
+ # Log IO errors
76
+ print("Failed to transmit files to {0}. Error: Input/output error - {1}".format(endpoint, str(e)))
77
+ submission_results[endpoint] = {"status": False, "error": "Input/output error - " + str(e)}
78
+
79
+ except Exception as e:
80
+ # Log other exceptions
81
+ print("Failed to transmit files to {0}. Error: {1}".format(endpoint, str(e)))
82
+ submission_results[endpoint] = {"status": False, "error": str(e)}
83
+ else:
84
+
85
+ print("No files were converted for transmission to {0}.".format(endpoint))
86
+ else:
87
+ print("No internet connection detected.")
88
+ try_again = input("Do you want to try again? (Y/N): ").strip().lower()
89
+ if try_again != 'y':
90
+ print("Exiting transmission process. Please try again later.")
91
+ return # Exiting the function if the user decides not to retry
92
+ else:
93
+ print("Transmission canceled for endpoint {0}.".format(endpoint))
94
+
95
+ # Continue to next endpoint regardless of the previous outcomes
96
+
97
+ # Build and display receipt
98
+ build_and_display_receipt(submission_results, config)
99
+
100
+ print("Claim submission process completed.\n")
101
+
102
+ def handle_transmission_result(transmission_result, config, operation_type):
103
+ """
104
+ Analyze the outcomes of file transmissions based on WinSCP log entries.
105
+
106
+ :param transmission_result: List of paths for files that were attempted to be transmitted.
107
+ :param config: Configuration dictionary containing paths and settings.
108
+ :return: Dictionary mapping each file path to a boolean indicating successful transmission.
109
+ """
110
+ log_filename = "winscp_{}.log".format(operation_type)
111
+
112
+ # BUG local_claims_path is where the uploads are only. this needs to have a switch. Check where WinSCP actually logs though.
113
+ log_path = os.path.join(config['MediLink_Config']['local_claims_path'], log_filename)
114
+ success_dict = {}
115
+
116
+ try:
117
+ with open(log_path, 'r') as log_file:
118
+ log_contents = log_file.readlines()
119
+
120
+ log("WinSCP Log file opened and contents read successfully.")
121
+
122
+ if log_contents is None:
123
+ log("Unexpected NoneType for log_contents")
124
+ elif not log_contents:
125
+ log("Log file '{}' is empty.".format(log_path))
126
+ success_dict = {file_path: False for file_path in transmission_result}
127
+ else:
128
+ last_lines = log_contents[-15:]
129
+ log("Processing the last {} lines of the log file.".format(len(last_lines)))
130
+ for file_path in transmission_result:
131
+ if file_path is None:
132
+ log("Error: NoneType file path found in transmission results.")
133
+ continue
134
+ try:
135
+ success_message = "Transfer done: '{}'".format(file_path)
136
+ log("Looking for: {} in WinSCP log.".format(success_message))
137
+ success = any(success_message in line for line in last_lines)
138
+ log("Validation: {0} - Transfer done for file: {1}".format(success, file_path))
139
+ success_dict[file_path] = success
140
+ except TypeError as e:
141
+ log("TypeError during processing file path '{}': {}".format(file_path, e))
142
+ log("Completed WinSCP log verification check.")
143
+
144
+ except FileNotFoundError:
145
+ log("Log file '{}' not found.".format(log_path))
146
+ success_dict = {file_path: False for file_path in transmission_result}
147
+
148
+ except IOError as e:
149
+ log("IO error when handling the log file '{}': {}".format(log_path, e))
150
+ success_dict = {file_path: False for file_path in transmission_result}
151
+
152
+ except Exception as e:
153
+ log("Error processing the transmission log: {}".format(e))
154
+ success_dict = {file_path: False for file_path in transmission_result}
155
+
156
+ return success_dict
157
+
158
+ def build_and_display_receipt(submission_results, config):
159
+ """
160
+ Define Submission Receipt:
161
+
162
+ A receipt of submitted claims is typically attached to each printed facesheet for recordkeeping confirming submission.
163
+
164
+ input: accumulated success_dict
165
+ (historical record won't work because the winscp logs will be incomplete, use clearinghouse to check historical records.)
166
+
167
+ 2.) Parse each of the 837p files from filepaths in success_dict:
168
+
169
+ Note: each file will only have one receiver name and one date & time of submission. The dictionary should be organized such that the receiver name (for that file) is the key for a list of patients with patient data.
170
+
171
+ output: print to screen a pretty ASCII human-readable organized 'receipt' by receiver name using the dictionary data, then dump to a notepad called Receipt_[date of submission].txt at config[MediLink_Config][local_storage_path] and subprocess open the file to the user to see.
172
+ """
173
+ # Prepare data for receipt
174
+ # Organize submission results into a format suitable for the receipt
175
+ log("Preparing receipt data...")
176
+ receipt_data = prepare_receipt_data(submission_results)
177
+
178
+ # Build the receipt
179
+ receipt_content = build_receipt_content(receipt_data)
180
+
181
+ # Print the receipt to the screen
182
+ log("Printing receipt...")
183
+ print(receipt_content)
184
+
185
+ # Save the receipt to a text file
186
+ save_receipt_to_file(receipt_content, config)
187
+
188
+ # Probably return receipt_data since its the easier to use dictionary
189
+ return receipt_data
190
+
191
+ def prepare_receipt_data(submission_results):
192
+ """
193
+ Prepare submission results for a receipt, including data from both successful and failed submissions.
194
+
195
+ This function extracts patient names, dates of service, amounts billed, and insurance names from an 837p file.
196
+ It also includes the date and time of batch claim submission, and the receiver name from the 1000B segment.
197
+ Data is organized by receiver name and includes both successful and failed submissions.
198
+
199
+ Parameters:
200
+ - submission_results (dict): Contains submission results grouped by endpoint, with success status.
201
+
202
+ Returns:
203
+ - dict: Organized data for receipt preparation, including both successful and failed submission details.
204
+ """
205
+ receipt_data = {}
206
+ for endpoint, files in submission_results.items():
207
+ for file_path, success in files.items():
208
+ # Parse the 837p file for patient data, regardless of success.
209
+ patient_data, date_of_submission = parse_837p_file(file_path)
210
+
211
+ if endpoint not in receipt_data:
212
+ receipt_data[endpoint] = {
213
+ "date_of_submission": date_of_submission,
214
+ "patients": []
215
+ }
216
+
217
+ # Enhance patient data with success status
218
+ for patient in patient_data:
219
+ patient['status'] = success
220
+
221
+ receipt_data[endpoint]["patients"].extend(patient_data)
222
+
223
+ validate_data(receipt_data)
224
+ return receipt_data
225
+
226
+ def validate_data(receipt_data):
227
+ # Simple validation to check if data fields are correctly populated
228
+ for endpoint, data in receipt_data.items():
229
+ patients = data.get("patients", [])
230
+ for index, patient in enumerate(patients, start=1):
231
+ missing_fields = [field for field in ('name', 'service_date', 'amount_billed', 'insurance_name', 'status') if not patient.get(field)]
232
+
233
+ if missing_fields:
234
+ # Log the missing fields without revealing PHI
235
+ log("Receipt Data validation error for endpoint '{}', patient {}: Missing information in fields: {}".format(endpoint, index, ", ".join(missing_fields)))
236
+ return True
237
+
238
+ def parse_837p_file(file_path):
239
+ """
240
+ Parse an 837p file to extract patient details and date of submission.
241
+
242
+ This function reads the specified 837p file, extracts patient details such as name, service date, and amount billed,
243
+ and retrieves the date of submission from the GS segment. It then organizes this information into a list of dictionaries
244
+ containing patient data. If the GS segment is not found, it falls back to using the current date and time.
245
+
246
+ Parameters:
247
+ - file_path (str): The path to the 837p file to parse.
248
+
249
+ Returns:
250
+ - tuple: A tuple containing two elements:
251
+ - A list of dictionaries, where each dictionary represents patient details including name, service date, and amount billed.
252
+ - A string representing the date and time of submission in the format 'YYYY-MM-DD HH:MM:SS'.
253
+ """
254
+ patient_details = []
255
+ date_of_submission = None
256
+ try:
257
+ with open(file_path, 'r') as file:
258
+ content = file.read()
259
+ log("Parsing submitted 837p...")
260
+
261
+ # Extract the submission date from the GS segment
262
+ gs_match = re.search(r'GS\*HC\*[^*]*\*[^*]*\*([0-9]{8})\*([0-9]{4})', content)
263
+ if gs_match:
264
+ date = gs_match.group(1)
265
+ time = gs_match.group(2)
266
+ date_of_submission = datetime.strptime("{}{}".format(date, time), "%Y%m%d%H%M").strftime("%Y-%m-%d %H:%M:%S")
267
+ else:
268
+ # Fallback to the current date and time if GS segment is not found
269
+ date_of_submission = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
270
+
271
+ # Split content using 'SE*{count}*{control_number}~' as delimiter
272
+ patient_records = re.split(r'SE\*\d+\*\d{4}~', content)
273
+
274
+ # Remove any empty strings from list that may have been added from split
275
+ patient_records = [record for record in patient_records if record.strip()]
276
+
277
+ for record in patient_records:
278
+ # Extract patient name
279
+ name_match = re.search(r'NM1\*IL\*1\*([^*]+)\*([^*]+)\*([^*]*)', record)
280
+ # Extract service date
281
+ service_date_match = re.search(r'DTP\*472\D*8\*([0-9]{8})', record)
282
+ # Extract claim amount
283
+ amount_match = re.search(r'CLM\*[^\*]*\*([0-9]+\.?[0-9]*)', record)
284
+ # Extract insurance name (payer_name)
285
+ insurance_name_match = re.search(r'NM1\*PR\*2\*([^*]+)\*', record)
286
+
287
+ if name_match and service_date_match and amount_match:
288
+ # Handle optional middle name
289
+ middle_name = name_match.group(3).strip() if name_match.group(3) else ""
290
+ patient_name = "{} {} {}".format(name_match.group(2), middle_name, name_match.group(1)).strip()
291
+ service_date = "{}-{}-{}".format(service_date_match.group(1)[:4], service_date_match.group(1)[4:6], service_date_match.group(1)[6:])
292
+ amount_billed = float(amount_match.group(1))
293
+ insurance_name = insurance_name_match.group(1)
294
+
295
+ patient_details.append({
296
+ "name": patient_name,
297
+ "service_date": service_date,
298
+ "amount_billed": amount_billed,
299
+ "insurance_name": insurance_name
300
+ })
301
+ except Exception as e:
302
+ print("Error reading or parsing the 837p file: {0}".format(str(e)))
303
+
304
+ return patient_details, date_of_submission
305
+
306
+ def build_receipt_content(receipt_data):
307
+ """
308
+ Build the receipt content in a human-readable ASCII format with a tabular data presentation for patient information.
309
+
310
+ Args:
311
+ receipt_data (dict): Dictionary containing receipt data with patient details.
312
+
313
+ Returns:
314
+ str: Formatted receipt content as a string.
315
+ """
316
+ # Build the receipt content in a human-readable ASCII format
317
+ receipt_lines = ["Submission Receipt", "=" * 60, ""] # Header
318
+
319
+ for endpoint, data in receipt_data.items():
320
+ header = "Endpoint: {0} (Submitted: {1})".format(endpoint, data['date_of_submission'])
321
+ receipt_lines.extend([header, "-" * len(header)])
322
+
323
+ # Table headers
324
+ table_header = "{:<20} | {:<15} | {:<15} | {:<20} | {:<10}".format("Patient", "Service Date", "Amount Billed", "Insurance", "Status")
325
+ receipt_lines.append(table_header)
326
+ receipt_lines.append("-" * len(table_header))
327
+
328
+ # Adding patient information in a tabular format
329
+ for patient in data["patients"]:
330
+ success_status = "SUCCESS" if patient['status'] else "FAILED"
331
+ patient_info = "{:<20} | {:<15} | ${:<14} | {:<20} | {:<10}".format(
332
+ patient['name'],
333
+ patient['service_date'],
334
+ patient['amount_billed'],
335
+ patient['insurance_name'],
336
+ success_status
337
+ )
338
+ receipt_lines.append(patient_info)
339
+
340
+ receipt_lines.append("") # Blank line for separation
341
+
342
+ receipt_content = "\n".join(receipt_lines)
343
+ return receipt_content
344
+
345
+ def save_receipt_to_file(receipt_content, config):
346
+ try:
347
+ # Save the receipt content to a text file named Receipt_[date_of_submission].txt
348
+ # Use the configured local storage path to determine the file location
349
+ file_name = "Receipt_{0}.txt".format(datetime.now().strftime('%Y%m%d_%H%M%S'))
350
+ file_path = os.path.join(config['MediLink_Config']['local_claims_path'], file_name)
351
+
352
+ with open(file_path, 'w') as file:
353
+ file.write(receipt_content)
354
+
355
+ # Open the file automatically for the user
356
+ os.startfile(file_path)
357
+ except Exception as e:
358
+ print("Failed to save or open receipt file:", e)
359
+
360
+ # Secure File Transmission
361
+ def confirm_transmission(detailed_patient_data_grouped_by_endpoint):
362
+ """
363
+ Displays detailed patient data ready for transmission and their endpoints,
364
+ asking for user confirmation before proceeding.
365
+
366
+ :param detailed_patient_data_grouped_by_endpoint: Dictionary with endpoints as keys and
367
+ lists of detailed patient data as values.
368
+ :param config: Configuration settings loaded from a JSON file.
369
+ """
370
+ print("\nReview of patient data ready for transmission:")
371
+ for endpoint, patient_data_list in detailed_patient_data_grouped_by_endpoint.items():
372
+ print("\nEndpoint: {0}".format(endpoint))
373
+ for patient_data in patient_data_list:
374
+ patient_info = "({1}) {0}".format(patient_data['patient_name'], patient_data['patient_id'])
375
+ print("- {:<35} | {:<5}, ${:<6}, Primary: {}".format(
376
+ patient_info, patient_data['surgery_date'][:5], patient_data['amount'], patient_data['primary_insurance']))
377
+
378
+ confirmation = input("\nProceed with transmission to all endpoints? (Y/N): ").strip().lower()
379
+ return confirmation == 'y'
380
+
381
+ # Entry point if this script is run directly. Probably needs to be handled better.
382
+ if __name__ == "__main__":
383
+ print("Please run MediLink directly.")
@@ -0,0 +1,7 @@
1
+ @echo off
2
+
3
+ :: Execute the Python script
4
+ py C:\Python34\Lib\site-packages\MediLink\MediLink.py
5
+
6
+ :end_script
7
+ pause
MediLink/Soumit_api.py ADDED
@@ -0,0 +1,22 @@
1
+ import os
2
+ import requests
3
+ from icecream import ic
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ url = "https://api.availity.com/availity/development-partner/v1/token"
9
+
10
+ data = {
11
+ 'grant_type': 'client_credentials',
12
+ 'client_id': os.getenv("CLIENT_ID"),
13
+ 'client_secret': os.getenv("CLIENT_SECRET"),
14
+ 'scope': 'hipaa'
15
+ }
16
+
17
+ headers = {
18
+ 'Content-Type': 'application/x-www-form-urlencoded'
19
+ }
20
+
21
+ resp = requests.post(url, headers = headers, data = data)
22
+ ic(resp.json())
MediLink/__init__.py ADDED
File without changes
MediLink/test.py ADDED
@@ -0,0 +1,74 @@
1
+ import re
2
+ from datetime import datetime
3
+
4
+ def log(message):
5
+ print(f"LOG: {message}")
6
+
7
+ def parse_837p_file(content):
8
+ patient_details = []
9
+ date_of_submission = None
10
+ try:
11
+
12
+ log("Parsing submitted 837p...")
13
+
14
+ # Extract the submission date from the GS segment
15
+ gs_match = re.search(r'GS\*HC\*[^*]*\*[^*]*\*([0-9]{8})\*([0-9]{4})', content)
16
+ if gs_match:
17
+ date = gs_match.group(1)
18
+ time = gs_match.group(2)
19
+ date_of_submission = datetime.strptime("{}{}".format(date, time), "%Y%m%d%H%M").strftime("%Y-%m-%d %H:%M:%S")
20
+ else:
21
+ # Fallback to the current date and time if GS segment is not found
22
+ date_of_submission = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
23
+
24
+ # Split content using 'SE*{count}*{control_number}~' as delimiter
25
+ patient_records = re.split(r'SE\*\d+\*\d{4}~', content)
26
+
27
+ # Remove any empty strings from list that may have been added from split
28
+ patient_records = [record for record in patient_records if record.strip()]
29
+
30
+ for record in patient_records:
31
+ # Extract patient name
32
+ name_match = re.search(r'NM1\*IL\*1\*([^*]+)\*([^*]+)\*([^*]*)', record)
33
+ # Extract service date
34
+ service_date_match = re.search(r'DTP\*472\D*8\*([0-9]{8})', record)
35
+ # Extract claim amount
36
+ amount_match = re.search(r'CLM\*[^\*]*\*([0-9]+\.?[0-9]*)', record)
37
+
38
+ if name_match and service_date_match and amount_match:
39
+ # Handle optional middle name
40
+ middle_name = name_match.group(3).strip() if name_match.group(3) else ""
41
+ patient_name = "{} {} {}".format(name_match.group(2), middle_name, name_match.group(1)).strip()
42
+ service_date = "{}-{}-{}".format(service_date_match.group(1)[:4], service_date_match.group(1)[4:6], service_date_match.group(1)[6:])
43
+ amount_billed = float(amount_match.group(1))
44
+
45
+ patient_details.append({
46
+ "name": patient_name,
47
+ "service_date": service_date,
48
+ "amount_billed": amount_billed
49
+ })
50
+ except Exception as e:
51
+ print("Error reading or parsing the 837p file: {0}".format(str(e)))
52
+
53
+ # Optionally, return also date_of_submission as a separate variable
54
+ return patient_details, date_of_submission
55
+
56
+ # Mocked file content based on your provided sample data
57
+ file_content = """
58
+ GS*HC*ZCHC0113*OPTUM*20240414*1130*1*X*005010X222A1~
59
+ NM1*IL*1*BURLAP*CAROLINE*A***MI*995796519~
60
+ CLM*BURLO000032624*540.00***24:B:1*Y*A*Y*Y~
61
+ DTP*472*D8*20240328~
62
+ SE*26*0001~
63
+ NM1*IL*1*GAMORA*WHOIS*R***MI*987654307~
64
+ CLM*GAMOA000032824*450.00***24:B:1*Y*A*Y*Y~
65
+ DTP*472*D8*20240318~
66
+ SE*26*0002~
67
+ ST*837*0002*005010X222A1~
68
+ BHT*0019*00*GORJA000*20240414*1130*CH~
69
+ """
70
+
71
+ # Run the function with the mocked content
72
+ parsed_details = parse_837p_file(file_content)
73
+ for detail in parsed_details:
74
+ print(detail)
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.1
2
+ Name: medicafe
3
+ Version: 0.240517.0
4
+ Summary: MediCafe
5
+ Home-page: https://github.com/katanada2
6
+ Author: Daniel Vidaud
7
+ Author-email: daniel@personalizedtransformation.com
8
+ License: MIT
9
+ Keywords: medicafe python34 medibot medilink
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: requests
13
+ Requires-Dist: argparse
14
+ Requires-Dist: numpy ==1.11.3
15
+ Requires-Dist: pandas ==0.20.0
16
+ Requires-Dist: tqdm ==4.14.0
17
+
18
+
19
+ # Project Overview: MediCafe
20
+
21
+ ## Project Description
22
+ MediCafe is a comprehensive suite designed to automate and streamline several aspects of medical administrative tasks within Medisoft, a popular medical practice management software. The system consists of two main components: MediBot and MediLink, each serving distinct functions but integrated to enhance the workflow of medical practices.
23
+
24
+ ## MediBot Module
25
+ MediBot is primarily focused on automating data entry processes in Medisoft. It utilizes AutoHotkey scripting to control and automate the GUI interactions required for inputting patient data into Medisoft. Key features and functionalities include:
26
+
27
+ - **Error Handling and Logging:** MediBot aims to implement a robust error handling mechanism that can log issues and provide feedback for troubleshooting.
28
+ - **Insurance Mode Adjustments:** The system can adjust data inputs based on specific requirements from various insurance providers, including Medicare.
29
+ - **Diagnosis Entry Automation:** MediBot automates the extraction and entry of diagnosis codes from surgical schedules into Medisoft.
30
+ - **Script Efficiency:** The module enhances the efficiency of scripts handling Medisoft’s quirks, such as fields that are skipped or require special navigation.
31
+ - **User Interface (UI) Enhancements:** Plans to develop a graphical user interface to help non-technical users manage and execute scripts more easily.
32
+ - **Documentation and Support:** Comprehensive documentation and support channels are being established to assist users in setup, configuration, and troubleshooting.
33
+
34
+ ## MediLink Module
35
+ MediLink focuses on the backend processes related to medical claims submission, particularly handling communications with multiple endpoints like Availity, Optum, and PNT Data. Its main features include:
36
+
37
+ - **Dynamic Configurations:** Supports multiple endpoints with environmental settings to ensure flexibility in claims submission.
38
+ - **File Detection and Integrity Checks:** Enhances the detection of new claim files with detailed logging and integrity checks for preprocessing validation.
39
+ - **Automated Response Handling:** Automates the process of receiving and integrating response files from endpoints into Medisoft, alerting users to exceptions.
40
+ - **Endpoint Management:** Allows dynamic updating of endpoints based on insurance provider changes, ensuring accurate and efficient claims processing.
41
+ - **User Interface (UI) Interactions:** Provides a user interface for managing claims submission, including confirming or adjusting suggested endpoints.
42
+
43
+ ## Integration and Workflow
44
+ The two modules work in tandem to provide a seamless experience. MediBot handles the initial data entry into Medisoft, preparing the system with up-to-date patient and treatment information. This data is then utilized by MediLink for preparing and submitting medical claims to various insurance providers. Errors and feedback from MediLink can prompt adjustments in MediBot's data entry processes, creating a feedback loop that enhances accuracy and efficiency.
45
+
46
+ The integration aims to reduce the administrative burden on medical practices, decrease the incidence of data entry errors, and ensure timely submission of medical claims, thereby improving the revenue cycle management of healthcare providers.
47
+
48
+ ## Target Users
49
+ The system is intended for use by administrative staff in medical practices who are responsible for patient data management and claims processing. By automating these tasks, the system not only saves time but also reduces the potential for human error, leading to more accurate billing and improved operational efficiency.
50
+
51
+ ## Future Directions
52
+ Future enhancements may include the development of additional modules for other aspects of medical practice management, further integrations with healthcare systems, and continuous improvements in user interface design to accommodate an even broader range of users.
53
+