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.

MediLink/MediLink_Down.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import os
2
- import logging
3
2
  import argparse
4
3
  import shutil
5
4
  from datetime import datetime
@@ -20,28 +19,6 @@ We need to make another function that figures out claim rejections and tries to
20
19
  7. Configuration Key Accuracy: Audit the script to correct any inaccuracies in configuration key references, ensuring seamless configuration data retrieval.
21
20
  """
22
21
 
23
- # Setup basic logging
24
- def setup_logger(local_storage_path):
25
- # Define a reasonable name for the log file, e.g., "MediLink_Down_Process.log"
26
- log_filename = datetime.now().strftime("MediLink_Down_Process_%m%d%Y.log")
27
- log_filepath = os.path.join(local_storage_path, log_filename)
28
-
29
- for handler in logging.root.handlers[:]:
30
- logging.root.removeHandler(handler)
31
-
32
- # Setup logging to file
33
- logging.basicConfig(level=logging.INFO,
34
- format='%(asctime)s - %(levelname)s - %(message)s',
35
- filename=log_filepath, # Direct logging to a file in local_storage_path
36
- filemode='a') # Append mode
37
-
38
- # If you also want to see the logs in the console, add a StreamHandler
39
- #console_handler = logging.StreamHandler()
40
- #console_handler.setLevel(logging.INFO)
41
- #formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
42
- #console_handler.setFormatter(formatter)
43
- #logging.getLogger('').addHandler(console_handler)
44
-
45
22
  # Because I can't figure out how to get it to work directly in the WinSCP command.
46
23
  # And on the Windows XP machine apparently the default path is C:\\ ...
47
24
  # This needs to get fixed. Ugh.
@@ -62,7 +39,7 @@ def move_downloaded_files(local_storage_path):
62
39
  # source_path = os.path.join('.', file) for the XP machine? -- This whole thing needs repaired.
63
40
  destination_path = os.path.join(local_response_directory, file)
64
41
  shutil.move(source_path, destination_path)
65
- logging.info("Moved '{}' to '{}'".format(file, local_response_directory))
42
+ MediLink_ConfigLoader.log("Moved '{}' to '{}'".format(file, local_response_directory))
66
43
 
67
44
  def find_era_files(era_file_path):
68
45
  """
@@ -92,7 +69,6 @@ def main(desired_endpoint='AVAILITY'):
92
69
  # Setup Logger, Load configuration and output directory
93
70
  config, _ = MediLink_ConfigLoader.load_configuration(args.config_path)
94
71
  local_storage_path = config['MediLink_Config']['local_storage_path']
95
- setup_logger(local_storage_path)
96
72
  output_directory = os.path.join(local_storage_path, "translated_csvs")
97
73
 
98
74
  # Direct ERA file translation if a file path is provided
@@ -100,24 +76,25 @@ def main(desired_endpoint='AVAILITY'):
100
76
  era_files = find_era_files(args.era_file_path)
101
77
  if era_files:
102
78
  era_files_str = ', '.join(era_files)
103
- logging.info("Translating ERA files: {}".format(era_files_str))
79
+ MediLink_ConfigLoader.log("Translating ERA files: {}".format(era_files_str))
104
80
  MediLink_ERA_decoder.translate_era_to_csv(era_files, output_directory)
105
81
  # Instead of returning a single CSV file path, consolidate here
106
82
  consolidate_csv_path = MediLink_ERA_decoder.consolidate_csvs(output_directory)
107
- logging.info("Translation and consolidation completed.")
83
+ MediLink_ConfigLoader.log("Translation and consolidation completed.")
108
84
  return consolidate_csv_path
109
85
  else:
110
- logging.error("No ERA files found matching: {}".format(args.era_file_path))
86
+ MediLink_ConfigLoader.log("No ERA files found matching: {}".format(args.era_file_path))
111
87
  return
112
88
 
113
- # TODO This probably needs to be built into a loop that cycles through all 3 endpoints.
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.
114
91
  # The loop should use the tdqa or whatever the progress bar is called.
115
- print("Please wait...\n")
92
+ # print("Please wait...\n")
116
93
 
117
94
  # Validate endpoint key
118
95
  endpoint_key = args.desired_endpoint
119
96
  if endpoint_key not in config['MediLink_Config']['endpoints']:
120
- logging.error("Endpoint '{}' not found in configuration. Using default 'AVAILITY'.".format(endpoint_key))
97
+ MediLink_ConfigLoader.log("Endpoint '{}' not found in configuration. Using default 'AVAILITY'.".format(endpoint_key))
121
98
  endpoint_key = 'AVAILITY'
122
99
 
123
100
  # Retrieve endpoint configuration and local storage path
@@ -125,17 +102,17 @@ def main(desired_endpoint='AVAILITY'):
125
102
  local_storage_path = config['MediLink_Config']['local_storage_path']
126
103
 
127
104
  # Download ERA files from the configured endpoint
128
- downloaded_files = operate_winscp("download", None, endpoint_config, local_storage_path)
129
-
105
+ downloaded_files = operate_winscp("download", None, endpoint_config, local_storage_path, config)
106
+
130
107
  # Translate downloaded ERA files to CSV format
131
108
  translated_csv_paths = []
132
109
  for file in downloaded_files:
133
- # TODO This needs to add functionality for differentiating between ERA and 277 or
110
+ # TODO (Low Remit) This needs to add functionality for differentiating between ERA, 277, IBT or
134
111
  # whatever else might be included in the download folders.
135
112
  MediLink_ERA_decoder.translate_era_to_csv([file], output_directory)
136
113
  csv_file_path = os.path.join(output_directory, os.path.basename(file) + '.csv')
137
114
  translated_csv_paths.append(csv_file_path)
138
- logging.info("Translated ERA to CSV: {}".format(csv_file_path))
115
+ MediLink_ConfigLoader.log("Translated ERA to CSV: {}".format(csv_file_path))
139
116
 
140
117
  # Consolidate new CSVs
141
118
  consolidate_csv_path = MediLink_ERA_decoder.consolidate_csvs(output_directory)
@@ -1,9 +1,10 @@
1
1
  import os
2
- import logging
3
2
  import sys
4
- from MediLink_ConfigLoader import setup_logger, load_configuration
3
+ import csv
4
+ from MediLink_ConfigLoader import load_configuration, log
5
5
  from MediLink_DataMgmt import consolidate_csvs
6
6
 
7
+
7
8
  """
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.
9
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.
@@ -170,7 +171,6 @@ if __name__ == "__main__":
170
171
 
171
172
  # Setup logger
172
173
  local_storage_path = config['MediLink_Config']['local_storage_path']
173
- setup_logger(local_storage_path)
174
174
 
175
175
  # Define output directory
176
176
  output_directory = os.path.join(local_storage_path, "translated_csvs")
@@ -178,7 +178,7 @@ if __name__ == "__main__":
178
178
  # Retrieve ERA files from command line arguments
179
179
  files = sys.argv[1:] # Exclude the script name
180
180
  if not files:
181
- logging.error("No ERA files provided as arguments.")
181
+ log("No ERA files provided as arguments.")
182
182
  sys.exit(1)
183
183
 
184
184
  # Translate ERA files to CSV format
@@ -1,4 +1,100 @@
1
1
  """
2
- Get new filtered GMail emails with Carol's CSV, make OTP request, login, download CSV.
3
- Also download the surgery schedule.
4
- """
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
+ """
@@ -62,6 +62,47 @@ Database Management:
62
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
63
 
64
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
+
65
106
  """
66
107
 
67
108
 
MediLink/MediLink_UI.py CHANGED
@@ -1,20 +1,21 @@
1
- import datetime
2
- import sys
1
+ from datetime import datetime
3
2
 
4
3
  """
5
4
  Development Tasks for User Interface (UI) Enhancement in MediSoft Claims Submittal (MediLink) Script:
6
5
 
7
- Streamline user interaction for endpoint selection and patient adjustments with automated endpoint validation.
8
- Strengthen error handling in file conversion, transmission, and user inputs with actionable user feedback.
9
- Expand logging levels and develop a user notification system for process milestones and errors.
10
- Focus workflow on patient details for endpoint adjustments, facilitating patient-centric file submissions.
11
- Implement internet connectivity checks with retry options for submissions and offer pause/resume capabilities.
12
- Include final review and confirmation steps before submission, allowing for endpoint adjustments per patient.
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.
13
14
  """
14
15
 
15
16
  def display_welcome():
16
17
  print("\n" + "-" * 60)
17
- print("\n *~^~*: Welcome to MediLink! :*~^~*\n")
18
+ print(" *~^~*: Welcome to MediLink! :*~^~*")
18
19
  print("-" * 60 + "\n")
19
20
 
20
21
  def display_menu(options):
@@ -35,7 +36,7 @@ def display_patient_options(detailed_patient_data):
35
36
  """
36
37
  Displays a list of patients with their current suggested endpoints, prompting for selections to adjust.
37
38
  """
38
- print("\nPlease select the patients you want to adjust by entering their numbers separated by commas (e.g., 1,3,5):")
39
+ print("\nPlease select the patients to adjust by entering their numbers separated by commas\n(e.g., 1,3,5):")
39
40
  # Can disable this extra print for now because the px list would already be on-screen.
40
41
  #for i, data in enumerate(detailed_patient_data, start=1):
41
42
  # patient_info = "{0} ({1}) - {2}".format(data['patient_name'], data['patient_id'], data['surgery_date'])
@@ -62,26 +63,27 @@ def get_endpoint_decision():
62
63
  """
63
64
  return input("Change endpoint? (Y/N): ").strip().lower()
64
65
 
65
- def display_endpoint_options(endpoint_mapping):
66
+ def display_endpoint_options(endpoints_config):
66
67
  """
67
68
  Displays the endpoint options to the user based on the provided mapping.
68
69
 
69
70
  Args:
70
- endpoint_mapping (dict): A dictionary mapping option numbers to endpoint names.
71
- Example: {'1': 'Availity', '2': 'OptumEDI', ...}
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'}, ...}
72
74
 
73
75
  Returns:
74
76
  None
75
77
  """
76
78
  print("Select the new endpoint for the patient:")
77
- for option, endpoint_name in endpoint_mapping.items():
78
- print("{0}. {1}".format(option, endpoint_name))
79
+ for index, (key, details) in enumerate(endpoints_config.items(), 1):
80
+ print("{0}. {1}".format(index, details['name']))
79
81
 
80
82
  def get_new_endpoint_choice():
81
83
  """
82
84
  Gets the user's choice for a new endpoint.
83
85
  """
84
- return input("Select new endpoint (1, 2, or 3): ").strip()
86
+ return input("Select desired endpoint (e.g. 1, 2): ").strip()
85
87
 
86
88
  def display_patient_summaries(detailed_patient_data):
87
89
  """
@@ -103,7 +105,7 @@ def ask_for_proceeding_with_endpoints():
103
105
 
104
106
  def display_file_summary(index, summary):
105
107
  # Ensure surgery_date is converted to a datetime object
106
- surgery_date = datetime.datetime.strptime(summary['surgery_date'], "%m-%d-%y")
108
+ surgery_date = datetime.strptime(summary['surgery_date'], "%m-%d-%y")
107
109
 
108
110
  # Displays the summary of a file.
109
111
  print("{:02d}. {:5} (ID: {:<8}) {:20} {:15} Suggested Endpoint: {}".format(