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.
- MediBot/MediBot.bat +198 -0
- MediBot/MediBot.py +346 -0
- MediBot/MediBot_Charges.py +28 -0
- MediBot/MediBot_Crosswalk_Library.py +280 -0
- MediBot/MediBot_Preprocessor.py +247 -0
- MediBot/MediBot_Preprocessor_lib.py +357 -0
- MediBot/MediBot_UI.py +240 -0
- MediBot/MediBot_dataformat_library.py +198 -0
- MediBot/MediBot_docx_decoder.py +80 -0
- MediBot/MediPost.py +5 -0
- MediBot/PDF_to_CSV_Cleaner.py +211 -0
- MediBot/__init__.py +0 -0
- MediBot/update_json.py +43 -0
- MediBot/update_medicafe.py +57 -0
- MediLink/MediLink.py +381 -0
- MediLink/MediLink_277_decoder.py +92 -0
- MediLink/MediLink_837p_encoder.py +502 -0
- MediLink/MediLink_837p_encoder_library.py +890 -0
- MediLink/MediLink_API_v2.py +174 -0
- MediLink/MediLink_APIs.py +137 -0
- MediLink/MediLink_ConfigLoader.py +81 -0
- MediLink/MediLink_DataMgmt.py +258 -0
- MediLink/MediLink_Down.py +128 -0
- MediLink/MediLink_ERA_decoder.py +192 -0
- MediLink/MediLink_Gmail.py +100 -0
- MediLink/MediLink_Mailer.py +7 -0
- MediLink/MediLink_Scheduler.py +173 -0
- MediLink/MediLink_StatusCheck.py +4 -0
- MediLink/MediLink_UI.py +118 -0
- MediLink/MediLink_Up.py +383 -0
- MediLink/MediLink_batch.bat +7 -0
- MediLink/Soumit_api.py +22 -0
- MediLink/__init__.py +0 -0
- 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.240415.1.dist-info → medicafe-0.240517.0.dist-info}/WHEEL +1 -1
- medicafe-0.240517.0.dist-info/top_level.txt +2 -0
- medicafe-0.240415.1.dist-info/METADATA +0 -17
- medicafe-0.240415.1.dist-info/RECORD +0 -5
- medicafe-0.240415.1.dist-info/top_level.txt +0 -1
- {medicafe-0.240415.1.dist-info → medicafe-0.240517.0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import argparse
|
|
3
|
+
import shutil
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import glob
|
|
6
|
+
import MediLink_ERA_decoder
|
|
7
|
+
from MediLink_DataMgmt import operate_winscp
|
|
8
|
+
import MediLink_ConfigLoader
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
We need to make another function that figures out claim rejections and tries to solve them.
|
|
12
|
+
|
|
13
|
+
1. Config File Path Adjustment: Ensure the configuration file's path is adaptable for various environments, or clearly document the process for setting this path.
|
|
14
|
+
2. Logging Enhancements: Improve the logging mechanism to offer comprehensive insights through both file and console outputs, aiding in troubleshooting and operational monitoring.
|
|
15
|
+
3. CSV Output Refinement: Update the CSV output structure to include essential ERA data such as Payer Address, ensuring completeness and accuracy of information.
|
|
16
|
+
4. CSV Consolidation Logic: Develop logic for intelligently consolidating CSV outputs from batch-processed ERA files, ensuring coherent and comprehensive data aggregation.
|
|
17
|
+
5. Secure Endpoint Authentication: Establish a secure method for inputting and storing endpoint authentication details, enhancing script security.
|
|
18
|
+
6. Automated Endpoint Processing: Integrate automated looping through configured endpoints for ERA file retrieval, maximizing efficiency and reducing manual oversight.
|
|
19
|
+
7. Configuration Key Accuracy: Audit the script to correct any inaccuracies in configuration key references, ensuring seamless configuration data retrieval.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Because I can't figure out how to get it to work directly in the WinSCP command.
|
|
23
|
+
# And on the Windows XP machine apparently the default path is C:\\ ...
|
|
24
|
+
# This needs to get fixed. Ugh.
|
|
25
|
+
def move_downloaded_files(local_storage_path):
|
|
26
|
+
# Define the target directory for storing downloaded files
|
|
27
|
+
local_response_directory = os.path.join(local_storage_path, "responses")
|
|
28
|
+
|
|
29
|
+
if not os.path.exists(local_response_directory):
|
|
30
|
+
os.makedirs(local_response_directory)
|
|
31
|
+
|
|
32
|
+
# Identify all downloaded .era files in the current directory
|
|
33
|
+
# downloaded_files = [f for f in os.listdir('.') if f.endswith('.era')]
|
|
34
|
+
downloaded_files = [f for f in os.listdir('C:\\Users\\danie\\OneDrive\\Documents') if f.endswith('.era')]
|
|
35
|
+
|
|
36
|
+
# Move each file to the local_response_directory
|
|
37
|
+
for file in downloaded_files:
|
|
38
|
+
source_path = os.path.join('C:\\Users\\danie\\OneDrive\\Documents', file)
|
|
39
|
+
# source_path = os.path.join('.', file) for the XP machine? -- This whole thing needs repaired.
|
|
40
|
+
destination_path = os.path.join(local_response_directory, file)
|
|
41
|
+
shutil.move(source_path, destination_path)
|
|
42
|
+
MediLink_ConfigLoader.log("Moved '{}' to '{}'".format(file, local_response_directory))
|
|
43
|
+
|
|
44
|
+
def find_era_files(era_file_path):
|
|
45
|
+
"""
|
|
46
|
+
Find all files matching the era_file_path pattern.
|
|
47
|
+
This function normalizes the path and supports wildcard patterns.
|
|
48
|
+
"""
|
|
49
|
+
# Normalize the path to handle slashes correctly
|
|
50
|
+
normalized_path = os.path.normpath(era_file_path)
|
|
51
|
+
|
|
52
|
+
# Handling different wildcard scenarios
|
|
53
|
+
if "*" in normalized_path:
|
|
54
|
+
# Use glob to find all files matching the pattern
|
|
55
|
+
matching_files = glob.glob(normalized_path)
|
|
56
|
+
# Normalize paths in the resulting list
|
|
57
|
+
return [os.path.normpath(file) for file in matching_files]
|
|
58
|
+
else:
|
|
59
|
+
# Single file specified, return it in a list if it exists
|
|
60
|
+
return [normalized_path] if os.path.exists(normalized_path) else []
|
|
61
|
+
|
|
62
|
+
def main(desired_endpoint='AVAILITY'):
|
|
63
|
+
parser = argparse.ArgumentParser(description="Process ERA files and convert them to CSV format.")
|
|
64
|
+
parser.add_argument('--config_path', type=str, help='Path to the configuration JSON file', default="json\\config.json") # Default handling of json path
|
|
65
|
+
parser.add_argument('--desired_endpoint', type=str, help='The desired endpoint key from the configuration.', default=desired_endpoint)
|
|
66
|
+
parser.add_argument('--era_file_path', type=str, help='Optional: Specify a path to an ERA file for direct translation.', default=None)
|
|
67
|
+
args = parser.parse_args()
|
|
68
|
+
|
|
69
|
+
# Setup Logger, Load configuration and output directory
|
|
70
|
+
config, _ = MediLink_ConfigLoader.load_configuration(args.config_path)
|
|
71
|
+
local_storage_path = config['MediLink_Config']['local_storage_path']
|
|
72
|
+
output_directory = os.path.join(local_storage_path, "translated_csvs")
|
|
73
|
+
|
|
74
|
+
# Direct ERA file translation if a file path is provided
|
|
75
|
+
if args.era_file_path:
|
|
76
|
+
era_files = find_era_files(args.era_file_path)
|
|
77
|
+
if era_files:
|
|
78
|
+
era_files_str = ', '.join(era_files)
|
|
79
|
+
MediLink_ConfigLoader.log("Translating ERA files: {}".format(era_files_str))
|
|
80
|
+
MediLink_ERA_decoder.translate_era_to_csv(era_files, output_directory)
|
|
81
|
+
# Instead of returning a single CSV file path, consolidate here
|
|
82
|
+
consolidate_csv_path = MediLink_ERA_decoder.consolidate_csvs(output_directory)
|
|
83
|
+
MediLink_ConfigLoader.log("Translation and consolidation completed.")
|
|
84
|
+
return consolidate_csv_path
|
|
85
|
+
else:
|
|
86
|
+
MediLink_ConfigLoader.log("No ERA files found matching: {}".format(args.era_file_path))
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# TODO (Low Remit) This probably needs to be built into a loop that cycles through all 3 endpoints.
|
|
90
|
+
# I think the uploader has something like this implemented already since it sends to all the endpoints.
|
|
91
|
+
# The loop should use the tdqa or whatever the progress bar is called.
|
|
92
|
+
# print("Please wait...\n")
|
|
93
|
+
|
|
94
|
+
# Validate endpoint key
|
|
95
|
+
endpoint_key = args.desired_endpoint
|
|
96
|
+
if endpoint_key not in config['MediLink_Config']['endpoints']:
|
|
97
|
+
MediLink_ConfigLoader.log("Endpoint '{}' not found in configuration. Using default 'AVAILITY'.".format(endpoint_key))
|
|
98
|
+
endpoint_key = 'AVAILITY'
|
|
99
|
+
|
|
100
|
+
# Retrieve endpoint configuration and local storage path
|
|
101
|
+
endpoint_config = config['MediLink_Config']['endpoints'][endpoint_key]
|
|
102
|
+
local_storage_path = config['MediLink_Config']['local_storage_path']
|
|
103
|
+
|
|
104
|
+
# Download ERA files from the configured endpoint
|
|
105
|
+
downloaded_files = operate_winscp("download", None, endpoint_config, local_storage_path, config)
|
|
106
|
+
|
|
107
|
+
# Translate downloaded ERA files to CSV format
|
|
108
|
+
translated_csv_paths = []
|
|
109
|
+
for file in downloaded_files:
|
|
110
|
+
# TODO (Low Remit) This needs to add functionality for differentiating between ERA, 277, IBT or
|
|
111
|
+
# whatever else might be included in the download folders.
|
|
112
|
+
MediLink_ERA_decoder.translate_era_to_csv([file], output_directory)
|
|
113
|
+
csv_file_path = os.path.join(output_directory, os.path.basename(file) + '.csv')
|
|
114
|
+
translated_csv_paths.append(csv_file_path)
|
|
115
|
+
MediLink_ConfigLoader.log("Translated ERA to CSV: {}".format(csv_file_path))
|
|
116
|
+
|
|
117
|
+
# Consolidate new CSVs
|
|
118
|
+
consolidate_csv_path = MediLink_ERA_decoder.consolidate_csvs(output_directory)
|
|
119
|
+
|
|
120
|
+
# Return the list of translated CSV file paths
|
|
121
|
+
return consolidate_csv_path
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
consolidate_csv_path = main()
|
|
125
|
+
if consolidate_csv_path:
|
|
126
|
+
print("CSV File Created: {}".format(consolidate_csv_path))
|
|
127
|
+
else:
|
|
128
|
+
print("No CSV file was created.")
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import csv
|
|
4
|
+
from MediLink_ConfigLoader import load_configuration, log
|
|
5
|
+
from MediLink_DataMgmt import consolidate_csvs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
1. ERA File Processing: Implement robust mechanisms for reading and parsing ERA files, addressing potential file integrity issues and accommodating scenarios with multiple payer addresses within a single ERA.
|
|
10
|
+
2. Wildcard File Processing: Enable effective batch processing of ERA files using wildcard patterns in the `--era_file_path` argument, resulting in a unified CSV output.
|
|
11
|
+
3. Date of Service Parsing: Enhance the parsing logic for 'Date of Service' to accommodate different segment identifiers, improving data extraction reliability.
|
|
12
|
+
4. Payer Address Extraction: Fine-tune the logic for extracting payer and provider addresses from ERA files, ensuring only relevant information is captured.
|
|
13
|
+
5. De-persisting Intermediate Files.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# ERA Parser
|
|
17
|
+
def parse_era_content(content):
|
|
18
|
+
extracted_data = []
|
|
19
|
+
normalized_content = content.replace('~\n', '~')
|
|
20
|
+
lines = normalized_content.split('~')
|
|
21
|
+
|
|
22
|
+
# Reset these values for each new CLP segment
|
|
23
|
+
record = {}
|
|
24
|
+
check_eft, payer_address = None, None
|
|
25
|
+
allowed_amount, write_off, patient_responsibility, adjustment_amount = 0, 0, 0, 0
|
|
26
|
+
is_payer_section = False # Flag to identify payer section for accurate address capture
|
|
27
|
+
|
|
28
|
+
for line in lines:
|
|
29
|
+
segments = line.split('*')
|
|
30
|
+
|
|
31
|
+
if segments[0] == 'TRN' and len(segments) > 2:
|
|
32
|
+
check_eft = segments[2]
|
|
33
|
+
|
|
34
|
+
# Determine the start and end of the payer section to correctly capture the payer's address
|
|
35
|
+
if segments[0] == 'N1':
|
|
36
|
+
if segments[1] == 'PR': # Payer information starts
|
|
37
|
+
is_payer_section = True
|
|
38
|
+
# payer_name = segments[2] # Can capture payer name here if needed
|
|
39
|
+
elif segments[1] == 'PE': # Provider information starts, ending payer section
|
|
40
|
+
is_payer_section = False
|
|
41
|
+
|
|
42
|
+
# Correctly capture payer address only within payer section
|
|
43
|
+
if is_payer_section and segments[0] == 'N3' and len(segments) > 1:
|
|
44
|
+
payer_address = segments[1]
|
|
45
|
+
|
|
46
|
+
if segments[0] == 'CLP' and len(segments) >= 5:
|
|
47
|
+
if record:
|
|
48
|
+
if adjustment_amount == 0 and (write_off > 0 or patient_responsibility > 0):
|
|
49
|
+
adjustment_amount = write_off + patient_responsibility
|
|
50
|
+
|
|
51
|
+
# Finalize and append the current record before starting a new one
|
|
52
|
+
record.update({
|
|
53
|
+
# 'Payer Name': payer_name,
|
|
54
|
+
'Payer Address': payer_address,
|
|
55
|
+
'Allowed Amount': allowed_amount,
|
|
56
|
+
'Write Off': write_off,
|
|
57
|
+
'Patient Responsibility': patient_responsibility,
|
|
58
|
+
'Adjustment Amount': adjustment_amount,
|
|
59
|
+
})
|
|
60
|
+
extracted_data.append(record)
|
|
61
|
+
|
|
62
|
+
# Reset variables for the next record
|
|
63
|
+
allowed_amount, write_off, patient_responsibility, adjustment_amount = 0, 0, 0, 0
|
|
64
|
+
# payer_address = None # Reset address for the next CLP segment if it changes within one ERA file (so no. disable.)
|
|
65
|
+
|
|
66
|
+
# Initialize a new record
|
|
67
|
+
record = {
|
|
68
|
+
'Check EFT': check_eft,
|
|
69
|
+
'Chart Number': segments[1],
|
|
70
|
+
'Payer Address': payer_address,
|
|
71
|
+
'Amount Paid': segments[4],
|
|
72
|
+
'Charge': segments[3], # Total submitted charges for the claim
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
elif segments[0] == 'CAS':
|
|
76
|
+
# Parsing CAS segments for Write Off and Patient Responsibility
|
|
77
|
+
if segments[1] == 'CO': # Write Off
|
|
78
|
+
write_off += float(segments[3])
|
|
79
|
+
elif segments[1] == 'PR': # Patient Responsibility
|
|
80
|
+
patient_responsibility += float(segments[3])
|
|
81
|
+
elif segments[1] == 'OA': # Capture Adjustment Amount from CAS*OA segment
|
|
82
|
+
adjustment_amount += float(segments[3])
|
|
83
|
+
|
|
84
|
+
elif segments[0] == 'AMT' and segments[1] == 'B6':
|
|
85
|
+
# Allowed Amount from AMT segment
|
|
86
|
+
allowed_amount += float(segments[2])
|
|
87
|
+
|
|
88
|
+
elif segments[0] == 'DTM' and (segments[1] == '232' or segments[1] == '472'):
|
|
89
|
+
record['Date of Service'] = segments[2]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if record:
|
|
93
|
+
# Final record handling
|
|
94
|
+
if adjustment_amount == 0 and (write_off > 0 or patient_responsibility > 0):
|
|
95
|
+
adjustment_amount = write_off + patient_responsibility
|
|
96
|
+
# Append the last record
|
|
97
|
+
record.update({
|
|
98
|
+
'Allowed Amount': allowed_amount,
|
|
99
|
+
'Write Off': write_off,
|
|
100
|
+
'Patient Responsibility': patient_responsibility,
|
|
101
|
+
'Adjustment Amount': adjustment_amount,
|
|
102
|
+
})
|
|
103
|
+
extracted_data.append(record)
|
|
104
|
+
|
|
105
|
+
return extracted_data
|
|
106
|
+
|
|
107
|
+
def translate_era_to_csv(files, output_directory):
|
|
108
|
+
if not os.path.exists(output_directory):
|
|
109
|
+
os.makedirs(output_directory)
|
|
110
|
+
|
|
111
|
+
for file_path in files:
|
|
112
|
+
# Ensure the file is read correctly
|
|
113
|
+
with open(file_path, 'r') as era_file:
|
|
114
|
+
era_content = era_file.read()
|
|
115
|
+
|
|
116
|
+
data = parse_era_content(era_content)
|
|
117
|
+
# print("Parsed Data: ", data) # DEBUG
|
|
118
|
+
|
|
119
|
+
csv_file_path = os.path.join(output_directory, os.path.basename(file_path) + '.csv')
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# Open the CSV file with explicit newline handling
|
|
123
|
+
with open(csv_file_path, 'w', newline='') as csv_file:
|
|
124
|
+
fieldnames = ['Date of Service',
|
|
125
|
+
'Check EFT',
|
|
126
|
+
'Chart Number',
|
|
127
|
+
'Payer Address',
|
|
128
|
+
'Amount Paid',
|
|
129
|
+
'Adjustment Amount',
|
|
130
|
+
'Allowed Amount',
|
|
131
|
+
'Write Off',
|
|
132
|
+
'Patient Responsibility',
|
|
133
|
+
'Charge'
|
|
134
|
+
]
|
|
135
|
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
136
|
+
|
|
137
|
+
writer.writeheader()
|
|
138
|
+
for record in data:
|
|
139
|
+
|
|
140
|
+
# print("Writing record: ", record)
|
|
141
|
+
|
|
142
|
+
writer.writerow({
|
|
143
|
+
'Date of Service': record.get('Date of Service', ''),
|
|
144
|
+
'Check EFT': record.get('Check EFT', ''),
|
|
145
|
+
'Chart Number': record.get('Chart Number', ''),
|
|
146
|
+
'Payer Address': record.get('Payer Address', ''),
|
|
147
|
+
'Amount Paid': record.get('Amount Paid', ''),
|
|
148
|
+
'Adjustment Amount': record.get('Adjustment Amount', ''),
|
|
149
|
+
'Allowed Amount': record.get('Allowed Amount', ''),
|
|
150
|
+
'Write Off': record.get('Write Off', ''),
|
|
151
|
+
'Patient Responsibility': record.get('Patient Responsibility', ''),
|
|
152
|
+
'Charge': record.get('Charge', ''),
|
|
153
|
+
})
|
|
154
|
+
# Explicitly flush data to ensure it's written
|
|
155
|
+
csv_file.flush()
|
|
156
|
+
except Exception as e:
|
|
157
|
+
print("Error writing CSV: ", e)
|
|
158
|
+
|
|
159
|
+
# User Interface
|
|
160
|
+
def user_confirm_overwrite(chart_numbers):
|
|
161
|
+
"""Asks the user for confirmation to overwrite an existing file, showing Chart Numbers."""
|
|
162
|
+
print("The following Chart Numbers are in the existing file:")
|
|
163
|
+
for number in chart_numbers:
|
|
164
|
+
print(number)
|
|
165
|
+
return input("The file already exists. Do you want to overwrite it? (y/n): ").strip().lower() == 'y'
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
# Load configuration
|
|
169
|
+
|
|
170
|
+
config, _ = load_configuration()
|
|
171
|
+
|
|
172
|
+
# Setup logger
|
|
173
|
+
local_storage_path = config['MediLink_Config']['local_storage_path']
|
|
174
|
+
|
|
175
|
+
# Define output directory
|
|
176
|
+
output_directory = os.path.join(local_storage_path, "translated_csvs")
|
|
177
|
+
|
|
178
|
+
# Retrieve ERA files from command line arguments
|
|
179
|
+
files = sys.argv[1:] # Exclude the script name
|
|
180
|
+
if not files:
|
|
181
|
+
log("No ERA files provided as arguments.")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
# Translate ERA files to CSV format
|
|
185
|
+
translate_era_to_csv(files, output_directory)
|
|
186
|
+
|
|
187
|
+
# Consolidate CSVs
|
|
188
|
+
consolidate_csv_path = consolidate_csvs(output_directory)
|
|
189
|
+
if consolidate_csv_path:
|
|
190
|
+
print("Consolidated CSV File Created: {}".format(consolidate_csv_path))
|
|
191
|
+
else:
|
|
192
|
+
print("No CSV file was created.")
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This documentation outlines the current functionality and development insights of a Google Apps Script and associated
|
|
3
|
+
client-side Python script designed to facilitate the automated retrieval of secured content from emails. The system
|
|
4
|
+
enables users to extract links and one-time passwords (OTPs) from emails, initiating and managing secure email interactions
|
|
5
|
+
without manual intervention.
|
|
6
|
+
|
|
7
|
+
**Current Functionality:**
|
|
8
|
+
1. **Google Apps Script (Server-side)**:
|
|
9
|
+
- Handles HTTP GET requests, routing them based on the 'action' parameter to perform tasks like retrieving stored
|
|
10
|
+
OTPs, extracting links from emails, and retrieving email subjects for user selection.
|
|
11
|
+
- Utilizes Gmail search queries to find relevant emails based on sender and date criteria, then extracts data like
|
|
12
|
+
subjects or specific links contained within these emails.
|
|
13
|
+
- Supports interactive selection of emails via a web app, allowing users to choose an email from a list and extract
|
|
14
|
+
a specific link.
|
|
15
|
+
- Employs properties service to store and retrieve data like links and OTPs securely.
|
|
16
|
+
|
|
17
|
+
2. **HTML Client (Server-side)**:
|
|
18
|
+
- Provides a user interface for selecting emails from a list, triggered by server-side scripts.
|
|
19
|
+
- Includes JavaScript to handle client-server communication and user interactions, such as selecting an email and
|
|
20
|
+
extracting a link.
|
|
21
|
+
|
|
22
|
+
3. **Python Script (Client-side)**:
|
|
23
|
+
- Interfaces with the server-side web app to initiate processes like link retrieval.
|
|
24
|
+
- Manages opening URLs in the user's browser, allowing for interaction with the web app directly from the client-side
|
|
25
|
+
environment.
|
|
26
|
+
|
|
27
|
+
**Future Work:**
|
|
28
|
+
- [ ] Consider disabling OTP triggers for now since we don't really use it for the present implementation. The phone solution works.
|
|
29
|
+
- [ ] Implement and troubleshoot the OTP extraction and validation system to fully automate the secured content retrieval process.
|
|
30
|
+
- [X] Gmail Server-side Authentication flow & Webapp build
|
|
31
|
+
- [X] Upgrade to handle multiple possible emails selection
|
|
32
|
+
- [ ] Augment to detect Surgery Schedule emails with doc attachments that don't require OTP.
|
|
33
|
+
- [X] Upgrade Gmail query to only get emails with the protected links.
|
|
34
|
+
- [ ] Something that goes here that I forgot.
|
|
35
|
+
|
|
36
|
+
**Technical Challenges and Solutions:**
|
|
37
|
+
1. **Authentication Limitations on XP Systems:**
|
|
38
|
+
- **Challenge:** The XP operating system could not perform authentication natively outside a browser, and available
|
|
39
|
+
libraries were not capable of handling dynamic authentication.
|
|
40
|
+
- **Solution:** We centralized all user interactions within an HTML page served by Google Apps Script, thereby
|
|
41
|
+
eliminating the need for complex client-side operations. The client-side script was simplified to merely opening URLs, reducing the complexity and potential for errors.
|
|
42
|
+
|
|
43
|
+
2. **Secure Data Handling:**
|
|
44
|
+
- **Challenge:** Initially, handling sensitive data such as OTPs and integrating with O365 protected emails was complex
|
|
45
|
+
due to security requirements and the transient nature of such data. The solution had to accommodate the specific
|
|
46
|
+
security protocols of Microsoft's environment without direct interaction.
|
|
47
|
+
- **Solution:** Utilizing Google Apps Script’s property service to store OTPs temporarily and securely. Additionally,
|
|
48
|
+
we addressed O365 integration by ensuring the browser handled email links directly, respecting Microsoft's security
|
|
49
|
+
constraints like x-frame options, thus maintaining functionality without compromising security.
|
|
50
|
+
|
|
51
|
+
3. **User Interaction and Workflow Streamlining:**
|
|
52
|
+
- **Challenge:** Managing the workflow from email selection to secure content access required multiple steps that could
|
|
53
|
+
potentially confuse the user. The application also had to be efficient under low-bandwidth constraints, and initially,
|
|
54
|
+
XP could not download attachments directly, requiring manual user intervention.
|
|
55
|
+
- **Solution:** The introduction of an interactive web app interface enabled users to select emails directly through a
|
|
56
|
+
user-friendly list, significantly simplifying the workflow and minimizing user errors. This method also streamlined
|
|
57
|
+
the process, making it suitable for low-bandwidth environments and circumventing the need for full email client loads.
|
|
58
|
+
"""
|
|
59
|
+
import sys
|
|
60
|
+
import subprocess
|
|
61
|
+
import webbrowser
|
|
62
|
+
from MediLink_ConfigLoader import log
|
|
63
|
+
|
|
64
|
+
def open_browser_with_executable(url, browser_path=None):
|
|
65
|
+
"""
|
|
66
|
+
Opens a browser with the specified URL using a provided browser executable path or the default browser.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
if browser_path:
|
|
70
|
+
log("Attempting to open URL with provided executable: {} {}".format(browser_path, url))
|
|
71
|
+
# Try to open the browser using subprocess.Popen
|
|
72
|
+
process = subprocess.Popen([browser_path, url], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
73
|
+
stdout, stderr = process.communicate()
|
|
74
|
+
if process.returncode == 0:
|
|
75
|
+
log("Browser opened with provided executable path using subprocess.Popen.")
|
|
76
|
+
else:
|
|
77
|
+
log("Browser failed to open using subprocess.Popen. Return code: {}. Stderr: {}".format(process.returncode, stderr))
|
|
78
|
+
else:
|
|
79
|
+
# Fall back to the default browser if no specific path is provided
|
|
80
|
+
log("No browser path provided. Attempting to open URL with default browser: {}".format(url))
|
|
81
|
+
webbrowser.open(url)
|
|
82
|
+
log("Default browser opened.")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
log("Failed to open browser: {}".format(e))
|
|
85
|
+
|
|
86
|
+
def initiate_link_retrieval():
|
|
87
|
+
"""
|
|
88
|
+
Opens the web application through a direct URL that includes the action parameter.
|
|
89
|
+
"""
|
|
90
|
+
log("Initiating link retrieval process.")
|
|
91
|
+
# Direct URL that includes the action parameter to load the HTML content directly
|
|
92
|
+
url = "https://script.google.com/macros/s/AKfycbzlq8d32mDlLdtFxgL_zvLJernlGPB64ftyxyH8F1nNlr3P-VBH6Yd0NGa1pbBc5AozvQ/exec?action=get_link"
|
|
93
|
+
try:
|
|
94
|
+
browser_path = sys.argv[1] if len(sys.argv) > 1 else None
|
|
95
|
+
open_browser_with_executable(url, browser_path)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
log("Error during link retrieval initiation: {}".format(e))
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
initiate_link_retrieval()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the tool that is going to pull from Medisoft the "REFUND TO PATIENT" amounts and addresses and whatever
|
|
3
|
+
and talk to either Bill.com or a BOA BillPay for Business service or CheckIssuing or something to send automated
|
|
4
|
+
refund checks to patients.
|
|
5
|
+
|
|
6
|
+
Also, this tool should be able to do mailed invoicing for "BILL TO PATIENT" to bill a patient as well.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MediLink Scheduler: A medical claim scheduling and management system that integrates functionalities from MediScheduler and MediLink to optimize medical claim submissions based on patient deductible status, insurance provider deadlines, and a 90-day submission window. It leverages real-time data from insurance APIs for dynamic scheduling to maximize insurance payments by aligning claim submission with deductible fulfillment.
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- Deductible Optimization: Determines optimal submission times based on deductible fulfillment to maximize reimbursements.
|
|
6
|
+
- Dynamic Scheduling: Updates claim submission timelines in response to changes in deductible status and insurance coverage data.
|
|
7
|
+
- Secure Data Handling: Ensures the security of sensitive patient data through encryption and HIPAA-compliant practices.
|
|
8
|
+
- Integration with MediLink Systems: Seamlessly formats, submits, and tracks claims across MediLink and associated systems.
|
|
9
|
+
- Robust Error Handling and Notifications: Implements mechanisms for error detection and user alerts regarding scheduling and submission errors.
|
|
10
|
+
|
|
11
|
+
Main Functionalities:
|
|
12
|
+
1. Configuration and System Initialization:
|
|
13
|
+
- Configures and establishes secure connections to insurance APIs.
|
|
14
|
+
- Loads encrypted system settings and initializes subsystems for logging, data encryption, and notifications.
|
|
15
|
+
|
|
16
|
+
2. Scheduler Integration:
|
|
17
|
+
- Enhances MediLink's main menu to include an option for "Optimize and Schedule Submissions".
|
|
18
|
+
- This option is visible only when there are new or imminent claims, activating the scheduling function for these claims.
|
|
19
|
+
|
|
20
|
+
3. Scheduling Process:
|
|
21
|
+
- Identifies new or urgent claims.
|
|
22
|
+
- Fetches real-time deductible information from insurance APIs.
|
|
23
|
+
- Computes remaining deductible and determines suitable submission dates.
|
|
24
|
+
- Schedules claims with consideration for deductible status and the 90-day deadline, updating a scheduling log with details.
|
|
25
|
+
|
|
26
|
+
4. User Dashboard Update:
|
|
27
|
+
- Displays scheduled submissions, including deductible statuses and scheduled dates.
|
|
28
|
+
- Allows users to manually adjust or override automated scheduling decisions.
|
|
29
|
+
|
|
30
|
+
5. Pre-Submission Checks:
|
|
31
|
+
- Daily verification of claims ready for submission.
|
|
32
|
+
- Prepares 837P claim files and finalizes submission details with user confirmation.
|
|
33
|
+
- Securely transmits data to insurance providers and logs all submission activities.
|
|
34
|
+
|
|
35
|
+
6. Direct Submission Handling:
|
|
36
|
+
- Uses MediLink_Up.submit_claims to manage final claim submissions.
|
|
37
|
+
- Ensures that all scheduled claims are submitted with requisite details.
|
|
38
|
+
|
|
39
|
+
7. Data Security:
|
|
40
|
+
- Applies encryption to any new data storage or sensitive data involved in the scheduling process.
|
|
41
|
+
- Maintains compliance with HIPAA and relevant security standards.
|
|
42
|
+
|
|
43
|
+
8. Error Management and Alerts:
|
|
44
|
+
- Extends error handling capabilities to include specific scheduling and submission issues.
|
|
45
|
+
- Enhances notifications for reminders and updates on deductible status.
|
|
46
|
+
|
|
47
|
+
9. Integration with MediLink Logging and Error Handling:
|
|
48
|
+
- Integrates scheduler operations into MediLink’s existing logging and error management frameworks.
|
|
49
|
+
|
|
50
|
+
10. Maintenance and Data Cleanup:
|
|
51
|
+
- Regularly refreshes insurance data mappings and updates deductible information.
|
|
52
|
+
- Manages cleanup of processed claims to maintain database integrity and performance.
|
|
53
|
+
|
|
54
|
+
11. User Interface Improvements:
|
|
55
|
+
- Updates UI to ensure user-friendliness and provides clear operational feedback.
|
|
56
|
+
- Revises help documentation to reflect new functionalities and guides for scheduling tasks.
|
|
57
|
+
|
|
58
|
+
Database Management:
|
|
59
|
+
- Maintains a JSON-based database for patient billing data, labeled as patient_billing_db, storing information from various sources (Z.dat file, APIs, SFTP).
|
|
60
|
+
- Database content includes claim statuses, deductible details, error logs, and identifiers for billing readiness.
|
|
61
|
+
- Facilitates efficient tracking and processing of claims, with considerations for integrating individual patient data in batched submissions.
|
|
62
|
+
- Stays on the local machine in a defined secure location per config, ensuring HIPAA compliance without the need for data encryption at rest.
|
|
63
|
+
|
|
64
|
+
Note: Potential for data corruption or synchronization issues due to system limitations; backup and manual verification measures are advised.
|
|
65
|
+
|
|
66
|
+
Crosswalk Example:
|
|
67
|
+
"payer_id": {
|
|
68
|
+
"87726": {
|
|
69
|
+
"medisoft_medicare_id": [],
|
|
70
|
+
"medisoft_id": [
|
|
71
|
+
"320"
|
|
72
|
+
],
|
|
73
|
+
"endpoint": "OPTUMEDI"
|
|
74
|
+
},
|
|
75
|
+
"60054": {
|
|
76
|
+
"medisoft_medicare_id": [],
|
|
77
|
+
"medisoft_id": [
|
|
78
|
+
"369"
|
|
79
|
+
],
|
|
80
|
+
"endpoint": "OPTUMEDI"
|
|
81
|
+
},
|
|
82
|
+
"00590": {
|
|
83
|
+
"medisoft_medicare_id": [],
|
|
84
|
+
"medisoft_id": [
|
|
85
|
+
"317"
|
|
86
|
+
],
|
|
87
|
+
"endpoint": "OPTUMEDI"
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
Crosswalk add:
|
|
92
|
+
- submit_buffer_maria per payer_id
|
|
93
|
+
- submit_limit_days per payer_id
|
|
94
|
+
- deductible_max = $2500.
|
|
95
|
+
- submit limit buffer = 15 days before final.
|
|
96
|
+
|
|
97
|
+
MediLink_Scheduler logic to produce dates of submission:
|
|
98
|
+
deductible slows it until a max limit ($2500), then send anyway.
|
|
99
|
+
after maria buffer, before submit (-15?) limit day.
|
|
100
|
+
Maria collects pre-payment, this is a factor, needs to be somehow accounted for in the process where a quantity gets pre-assigned to a patient so we know when to submit the claim.
|
|
101
|
+
|
|
102
|
+
Date last reviewed, Insurance Type, In network, Annual Deductable Remaining, Recieved pre-payment amount, Check# or 'Cash'.
|
|
103
|
+
|
|
104
|
+
In the future those pre-payments should be either eCheck, Zelle or by CC + 5% surcharge.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# JSON DB structure draft.
|
|
110
|
+
{
|
|
111
|
+
"patients": {
|
|
112
|
+
"patient_id": {
|
|
113
|
+
"first_name": "John",
|
|
114
|
+
"last_name": "Doe",
|
|
115
|
+
"date_of_birth": "1985-07-12",
|
|
116
|
+
"insurance_details": {
|
|
117
|
+
"provider_id": "XYZ123",
|
|
118
|
+
"policy_number": "P123456789",
|
|
119
|
+
"coverage_start_date": "2020-01-01"
|
|
120
|
+
},
|
|
121
|
+
"contact_info": {
|
|
122
|
+
"email": "john.doe@example.com",
|
|
123
|
+
"phone": "555-1234"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"claims": {
|
|
128
|
+
"claim_id": {
|
|
129
|
+
"patient_id": "patient_id",
|
|
130
|
+
"date_of_service": "2023-04-01",
|
|
131
|
+
"status": "pending",
|
|
132
|
+
"scheduled_submission_date": "2023-04-15",
|
|
133
|
+
"actual_submission_date": null,
|
|
134
|
+
"billing_amount": 500.00,
|
|
135
|
+
"deductible_applied": 100.00,
|
|
136
|
+
"covered_amount": 400.00
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"insurance_providers": {
|
|
140
|
+
"provider_id": {
|
|
141
|
+
"name": "InsuranceCorp",
|
|
142
|
+
"contact_details": {
|
|
143
|
+
"phone": "555-6789",
|
|
144
|
+
"email": "support@insurancecorp.com"
|
|
145
|
+
},
|
|
146
|
+
"endpoint": "https://api.insurancecorp.com"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"system_configuration": {
|
|
150
|
+
"last_update": "2023-04-18",
|
|
151
|
+
"backup_frequency": "daily",
|
|
152
|
+
"data_encryption_key": "encrypted_key_value"
|
|
153
|
+
},
|
|
154
|
+
"logs": {
|
|
155
|
+
"log_id": {
|
|
156
|
+
"timestamp": "2023-04-18T12:34:56",
|
|
157
|
+
"event_type": "error",
|
|
158
|
+
"description": "Failed to connect to insurance API",
|
|
159
|
+
"related_claim_id": "claim_id"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"billing_queue": {
|
|
163
|
+
"patient_id": "claim_id"
|
|
164
|
+
},
|
|
165
|
+
"submitted_837p_batches": {
|
|
166
|
+
"batch_id": {
|
|
167
|
+
"timestamp": "2023-04-20T09:00:00",
|
|
168
|
+
"patients": ["patient_id1", "patient_id2", ...],
|
|
169
|
+
"endpoint": "clearinghouse_endpoint",
|
|
170
|
+
"status": "submitted"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|