medicafe 0.240419.2__py3-none-any.whl → 0.240613.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 (38) hide show
  1. MediBot/MediBot.bat +174 -38
  2. MediBot/MediBot.py +80 -77
  3. MediBot/MediBot_Charges.py +0 -28
  4. MediBot/MediBot_Crosswalk_Library.py +281 -0
  5. MediBot/MediBot_Post.py +0 -0
  6. MediBot/MediBot_Preprocessor.py +138 -211
  7. MediBot/MediBot_Preprocessor_lib.py +496 -0
  8. MediBot/MediBot_UI.py +80 -35
  9. MediBot/MediBot_dataformat_library.py +79 -35
  10. MediBot/MediBot_docx_decoder.py +295 -0
  11. MediBot/update_medicafe.py +46 -8
  12. MediLink/MediLink.py +207 -108
  13. MediLink/MediLink_837p_encoder.py +299 -214
  14. MediLink/MediLink_837p_encoder_library.py +445 -245
  15. MediLink/MediLink_API_v2.py +174 -0
  16. MediLink/MediLink_APIs.py +139 -0
  17. MediLink/MediLink_ConfigLoader.py +44 -32
  18. MediLink/MediLink_DataMgmt.py +297 -89
  19. MediLink/MediLink_Decoder.py +63 -0
  20. MediLink/MediLink_Down.py +73 -102
  21. MediLink/MediLink_ERA_decoder.py +4 -4
  22. MediLink/MediLink_Gmail.py +479 -4
  23. MediLink/MediLink_Mailer.py +0 -0
  24. MediLink/MediLink_Parser.py +111 -0
  25. MediLink/MediLink_Scan.py +0 -0
  26. MediLink/MediLink_Scheduler.py +2 -131
  27. MediLink/MediLink_StatusCheck.py +0 -4
  28. MediLink/MediLink_UI.py +87 -27
  29. MediLink/MediLink_Up.py +301 -45
  30. MediLink/MediLink_batch.bat +1 -1
  31. MediLink/test.py +74 -0
  32. medicafe-0.240613.0.dist-info/METADATA +55 -0
  33. medicafe-0.240613.0.dist-info/RECORD +43 -0
  34. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/WHEEL +5 -5
  35. medicafe-0.240419.2.dist-info/METADATA +0 -19
  36. medicafe-0.240419.2.dist-info/RECORD +0 -32
  37. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/LICENSE +0 -0
  38. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,174 @@
1
+ import time
2
+ import requests
3
+
4
+ # Importing configuration loader
5
+ try:
6
+ from MediLink import MediLink_ConfigLoader
7
+ except ImportError:
8
+ import MediLink_ConfigLoader
9
+
10
+ # Class for handling API calls
11
+ class APIClient:
12
+ def __init__(self):
13
+ # Load configuration
14
+ self.config, _ = MediLink_ConfigLoader.load_configuration()
15
+ # Initialize token cache
16
+ self.token_cache = {}
17
+
18
+ # Method to get access token
19
+ def get_access_token(self, endpoint_name):
20
+ # Retrieve endpoint configuration
21
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
22
+ current_time = time.time()
23
+
24
+ # Check if token is cached and still valid
25
+ if endpoint_name in self.token_cache:
26
+ cached_token = self.token_cache[endpoint_name]
27
+ if cached_token['expires_at'] > current_time:
28
+ return cached_token['access_token']
29
+
30
+ # Prepare data for token request
31
+ data = {
32
+ 'grant_type': 'client_credentials',
33
+ 'client_id': endpoint_config['client_id'],
34
+ 'client_secret': endpoint_config['client_secret'],
35
+ 'scope': 'hipaa'
36
+ }
37
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
38
+
39
+ # Request token
40
+ response = requests.post(endpoint_config['token_url'], headers=headers, data=data)
41
+ response.raise_for_status()
42
+ token_data = response.json()
43
+ access_token = token_data['access_token']
44
+ expires_in = token_data.get('expires_in', 3600)
45
+
46
+ # Cache token with expiration time adjusted
47
+ self.token_cache[endpoint_name] = {
48
+ 'access_token': access_token,
49
+ 'expires_at': current_time + expires_in - 120
50
+ }
51
+ return access_token
52
+
53
+ # Method for making API calls
54
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None):
55
+ # Retrieve endpoint configuration
56
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
57
+ # Get access token
58
+ token = self.get_access_token(endpoint_name)
59
+ headers = {'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'}
60
+
61
+ # Construct full URL
62
+ url = endpoint_config['api_url'] + url_extension
63
+
64
+ # Make appropriate type of call
65
+ if call_type == 'GET':
66
+ response = requests.get(url, headers=headers, params=params)
67
+ elif call_type == 'POST':
68
+ headers['Content-Type'] = 'application/json'
69
+ response = requests.post(url, headers=headers, json=data)
70
+ elif call_type == 'DELETE':
71
+ response = requests.delete(url, headers=headers)
72
+ else:
73
+ raise ValueError("Unsupported call type")
74
+
75
+ if response.status_code >= 400:
76
+ print("Error {}: {}".format(response.status_code, response.text))
77
+ response.raise_for_status()
78
+
79
+ return response.json()
80
+
81
+ # Method for creating coverage
82
+ def create_coverage(self, endpoint_name, patient_info):
83
+ return self.make_api_call(endpoint_name, 'POST', url_extension="/coverages", data=patient_info)
84
+
85
+ # Method for getting all coverages
86
+ def get_coverages(self, endpoint_name, params=None):
87
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/coverages", params=params)
88
+
89
+ # Method for getting coverage by ID
90
+ def get_coverage_by_id(self, endpoint_name, coverage_id):
91
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/coverages/{}".format(coverage_id))
92
+
93
+ # Method for deleting coverage by ID
94
+ def delete_coverage_by_id(self, endpoint_name, coverage_id):
95
+ return self.make_api_call(endpoint_name, 'DELETE', url_extension="/coverages/{}".format(coverage_id))
96
+
97
+ # Method for getting payer list
98
+ def get_payer_list(self, endpoint_name, params=None):
99
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/availity-payer-list", params=params)
100
+
101
+ # Function to fetch payer name from API
102
+ def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
103
+ client = APIClient()
104
+
105
+ # Step 1: Reload configuration for safety (This should be able to be replaced by APIClient())
106
+ config, _ = MediLink_ConfigLoader.load_configuration()
107
+
108
+ # Step 2: Check if the primary endpoint is specified and is valid
109
+ # (these validity checks will need to be incorporated into the main functionality)
110
+ endpoints = config['MediLink_Config']['endpoints']
111
+ if primary_endpoint and primary_endpoint in endpoints:
112
+ endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
113
+ else:
114
+ endpoint_order = list(endpoints.keys())
115
+
116
+ # Step 3: Iterate through available endpoints in specified order
117
+ for endpoint_name in endpoint_order:
118
+ endpoint_config = endpoints[endpoint_name]
119
+ if not all(key in endpoint_config for key in ['token_url', 'client_id', 'client_secret']):
120
+ MediLink_ConfigLoader.log("Skipping {} due to missing API keys.".format(endpoint_name), config, level="WARNING")
121
+ continue
122
+
123
+ try:
124
+ response = client.get_payer_list(endpoint_name, params={'payerId': payer_id})
125
+ if 'payers' in response and response['payers']:
126
+ payer = response['payers'][0]
127
+ payer_name = payer.get('displayName') or payer.get('name')
128
+
129
+ MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), config, level="INFO")
130
+ return payer_name
131
+ else:
132
+ MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), config, level="INFO")
133
+ except requests.RequestException as e:
134
+ MediLink_ConfigLoader.log("API call failed at {} for Payer ID '{}': {}".format(endpoint_name, payer_id, str(e)), config, level="ERROR")
135
+
136
+ error_message = "All endpoints exhausted for Payer ID {}.".format(payer_id)
137
+ MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
138
+ raise ValueError(error_message)
139
+
140
+ # Example usage
141
+ if __name__ == "__main__":
142
+ client = APIClient()
143
+ try:
144
+ # Fetch and print payer name
145
+ payer_name = fetch_payer_name_from_api("11347", 'config.yaml')
146
+ print("Payer Name: {}".format(payer_name))
147
+
148
+ # Example patient info
149
+ patient_info = {
150
+ "policyNumber": "12345",
151
+ "name": "John Doe",
152
+ "dob": "1980-01-01"
153
+ }
154
+ # Create coverage and print response
155
+ response = client.create_coverage('AVAILITY', patient_info)
156
+ print("Create Coverage Response: {}".format(response))
157
+
158
+ # Get all coverages and print response
159
+ response = client.get_coverages('AVAILITY')
160
+ print("All Coverages: {}".format(response))
161
+
162
+ # Example coverage ID
163
+ coverage_id = "some-coverage-id"
164
+ # Get coverage by ID and print response
165
+ response = client.get_coverage_by_id('AVAILITY', coverage_id)
166
+ print("Coverage by ID: {}".format(response))
167
+
168
+ # Delete coverage by ID and print response
169
+ response = client.delete_coverage_by_id('AVAILITY', coverage_id)
170
+ print("Delete Coverage Response: {}".format(response))
171
+
172
+ except Exception as e:
173
+ # Print error if any
174
+ print("Error: {}".format(e))
@@ -0,0 +1,139 @@
1
+ # Unused archive backup. This has been superceded by API_v2
2
+
3
+ import time
4
+ import requests
5
+ try:
6
+ from MediLink import MediLink_ConfigLoader
7
+ except ImportError:
8
+ import MediLink_ConfigLoader
9
+
10
+ # Fetches the payer name from API based on the payer ID.
11
+ def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
12
+ """
13
+ Fetches the payer name from the API using the provided payer ID.
14
+
15
+ Args:
16
+ payer_id (str): The ID of the payer.
17
+ config (dict): Configuration settings.
18
+ primary_endpoint (str): The primary endpoint for resolving payer information.
19
+
20
+ Raises:
21
+ ValueError: If all endpoints are exhausted without finding the payer.
22
+
23
+ Returns:
24
+ str: The fetched payer name.
25
+ """
26
+ # Reload for safety
27
+ config, _ = MediLink_ConfigLoader.load_configuration()
28
+
29
+ # Step 1: Retrieve endpoint configurations
30
+ endpoints = config['MediLink_Config']['endpoints']
31
+ tried_endpoints = []
32
+
33
+ # Step 2: Check if the primary endpoint is specified and is valid
34
+ if primary_endpoint and primary_endpoint in endpoints:
35
+ endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
36
+ else:
37
+ endpoint_order = list(endpoints.keys())
38
+
39
+ # Step 3: Iterate through available endpoints in specified order
40
+ for endpoint_name in endpoint_order:
41
+ endpoint_config = endpoints[endpoint_name]
42
+ if not all(key in endpoint_config for key in ['token_url', 'client_id', 'client_secret']):
43
+ MediLink_ConfigLoader.log("Skipping {} due to missing API keys.".format(endpoint_name), config, level="WARNING")
44
+ continue
45
+
46
+ # Step 4: Get access token for the endpoint
47
+ token = get_access_token(endpoint_config)
48
+ api_url = endpoint_config.get("api_url", "")
49
+ headers = {'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'}
50
+ params = {'payerId': payer_id}
51
+
52
+ try:
53
+ # Step 5: Make API call to fetch payer name
54
+ response = requests.get(api_url, headers=headers, params=params)
55
+ response.raise_for_status()
56
+ data = response.json()
57
+ if 'payers' in data and data['payers']:
58
+ payer = data['payers'][0]
59
+ payer_name = payer.get('displayName') or payer.get('name')
60
+
61
+ # Log successful match
62
+ MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), config, level="INFO")
63
+
64
+ return payer_name
65
+ else:
66
+ MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), config, level="INFO")
67
+ except requests.RequestException as e:
68
+ # Step 6: Log API call failure
69
+ MediLink_ConfigLoader.log("API call failed at {} for Payer ID '{}': {}".format(endpoint_name, payer_id, str(e)), config, level="ERROR")
70
+ tried_endpoints.append(endpoint_name)
71
+
72
+ # Step 7: Log all endpoints exhaustion and raise error
73
+ error_message = "All endpoints exhausted for Payer ID {}. Endpoints tried: {}".format(payer_id, ', '.join(tried_endpoints))
74
+ MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
75
+ raise ValueError(error_message)
76
+
77
+ # Test Case for API fetch
78
+ #payer_id = "11347"
79
+ #config = load_configuration()
80
+ #payer_name = fetch_payer_name_from_api(payer_id, config, endpoint='AVAILITY')
81
+ #print(payer_id, payer_name)
82
+
83
+ # Initialize a global dictionary to store the access token and its expiry time
84
+ # TODO (Low API) This will need to get setup for each endpoint separately.
85
+ token_cache = {
86
+ 'access_token': None,
87
+ 'expires_at': 0 # Timestamp of when the token expires
88
+ }
89
+
90
+ def get_access_token(endpoint_config):
91
+ current_time = time.time()
92
+
93
+ # Check if the cached token is still valid
94
+ if token_cache['access_token'] and token_cache['expires_at'] > current_time:
95
+ return token_cache['access_token']
96
+
97
+ # Validate endpoint configuration
98
+ if not endpoint_config or not all(k in endpoint_config for k in ['client_id', 'client_secret', 'token_url']):
99
+ raise ValueError("Endpoint configuration is incomplete or missing necessary fields.")
100
+
101
+ # Extract credentials and URL from the config
102
+ CLIENT_ID = endpoint_config.get("client_id")
103
+ CLIENT_SECRET = endpoint_config.get("client_secret")
104
+ url = endpoint_config.get("token_url")
105
+
106
+ # Setup the data payload and headers for the HTTP request
107
+ data = {
108
+ 'grant_type': 'client_credentials',
109
+ 'client_id': CLIENT_ID,
110
+ 'client_secret': CLIENT_SECRET,
111
+ 'scope': 'hipaa'
112
+ }
113
+ headers = {
114
+ 'Content-Type': 'application/x-www-form-urlencoded'
115
+ }
116
+
117
+ try:
118
+ # Perform the HTTP request to get the access token
119
+ response = requests.post(url, headers=headers, data=data)
120
+ response.raise_for_status() # This will raise an exception for HTTP error statuses
121
+ json_response = response.json()
122
+ access_token = json_response.get('access_token')
123
+ expires_in = json_response.get('expires_in', 3600) # Default to 3600 seconds if not provided
124
+
125
+ if not access_token:
126
+ raise ValueError("No access token returned by the server.")
127
+
128
+ # Store the access token and calculate the expiry time
129
+ token_cache['access_token'] = access_token
130
+ token_cache['expires_at'] = current_time + expires_in - 120 # Subtracting 120 seconds to provide buffer
131
+
132
+ return access_token
133
+ except requests.RequestException as e:
134
+ # Handle HTTP errors (e.g., network problems, invalid response)
135
+ error_msg = "Failed to retrieve access token: {0}. Response status: {1}".format(str(e), response.status_code if response else 'No response')
136
+ raise Exception(error_msg)
137
+ except ValueError as e:
138
+ # Handle specific errors like missing access token
139
+ raise Exception("Configuration or server response error: {0}".format(str(e)))
@@ -4,45 +4,28 @@ import logging
4
4
  from datetime import datetime
5
5
  from collections import OrderedDict
6
6
  import sys
7
+ import platform
7
8
 
8
9
  """
9
10
  This function should be generalizable to have a initialization script over all the Medi* functions
10
11
  """
11
-
12
- # Setup basic logging.
13
- # BUG Consolidate this with MediLink_837p_encoder_library.log
14
- def setup_logger(local_storage_path):
15
- # Define a reasonable name for the log file, e.g., "MediLink_Down_Process.log"
16
- log_filename = datetime.now().strftime("MediLink_Down_Process_%m%d%Y.log")
17
- log_filepath = os.path.join(local_storage_path, log_filename)
18
-
19
- for handler in logging.root.handlers[:]:
20
- logging.root.removeHandler(handler)
21
-
22
- # Setup logging to file
23
- logging.basicConfig(level=logging.INFO,
24
- format='%(asctime)s - %(levelname)s - %(message)s',
25
- filename=log_filepath, # Direct logging to a file in local_storage_path
26
- filemode='a') # Append mode
27
-
28
- # If you also want to see the logs in the console, add a StreamHandler
29
- #console_handler = logging.StreamHandler()
30
- #console_handler.setLevel(logging.INFO)
31
- #formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
32
- #console_handler.setFormatter(formatter)
33
- #logging.getLogger('').addHandler(console_handler)
34
-
35
12
  def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'), crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
36
13
  """
37
14
  Loads endpoint configuration, credentials, and other settings from JSON files.
38
15
 
39
16
  Returns: A tuple containing dictionaries with configuration settings for the main config and crosswalk.
40
17
  """
41
- # BUG HARDCODE FOR NOW
42
- config_path="G:\\My Drive\\Codes\\MediCafe\\json\\config.json"
43
- # "F:\\Medibot\\json\\config.json"
44
- crosswalk_path="G:\\My Drive\\Codes\\MediCafe\\json\\crosswalk.json"
45
- # "F:\\Medibot\\json\\crosswalk.json"
18
+ # TODO (Low Config Upgrade) The Medicare / Private differentiator flag probably needs to be pulled or passed to this.
19
+ # BUG (HARDCODE) FOR NOW:
20
+ # Detect the operating system
21
+ if platform.system() == 'Windows' and platform.release() == 'XP':
22
+ # Use F: paths for Windows XP
23
+ config_path = "F:\\Medibot\\json\\config.json"
24
+ crosswalk_path = "F:\\Medibot\\json\\crosswalk.json"
25
+ else:
26
+ # Use G: paths for other versions of Windows
27
+ config_path = "G:\\My Drive\\Codes\\MediCafe\\json\\config.json"
28
+ crosswalk_path = "G:\\My Drive\\Codes\\MediCafe\\json\\crosswalk.json"
46
29
 
47
30
  try:
48
31
  with open(config_path, 'r') as config_file:
@@ -55,8 +38,11 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
55
38
  crosswalk = json.load(crosswalk_file)
56
39
 
57
40
  return config, crosswalk
58
- except json.JSONDecodeError as e:
59
- print("Error parsing JSON file: {}".format(e))
41
+ except ValueError as e:
42
+ if isinstance(e, UnicodeDecodeError):
43
+ print("Error decoding JSON file: {}".format(e))
44
+ else:
45
+ print("Error parsing JSON file: {}".format(e))
60
46
  sys.exit(1) # Exit the script due to a critical error in configuration loading
61
47
  except FileNotFoundError:
62
48
  print("One or both JSON files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
@@ -66,4 +52,30 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
66
52
  sys.exit(1) # Exit the script due to a critical error in configuration loading
67
53
  except Exception as e:
68
54
  print("An unexpected error occurred while loading the configuration: {}".format(e))
69
- sys.exit(1) # Exit the script due to a critical error in configuration loading
55
+ sys.exit(1) # Exit the script due to a critical error in configuration loading
56
+
57
+ # Logs messages with optional error type and claim data.
58
+ def log(message, config=None, level="INFO", error_type=None, claim=None):
59
+
60
+ # If config is not provided, load it
61
+ if config is None:
62
+ config, _ = load_configuration()
63
+
64
+ # Setup logger if not already configured
65
+ if not logging.root.handlers:
66
+ local_storage_path = config['MediLink_Config'].get('local_storage_path', '.') if isinstance(config, dict) else '.'
67
+ log_filename = datetime.now().strftime("Log_%m%d%Y.log")
68
+ log_filepath = os.path.join(local_storage_path, log_filename)
69
+ logging.basicConfig(level=logging.INFO,
70
+ format='%(asctime)s - %(levelname)s - %(message)s',
71
+ filename=log_filepath,
72
+ filemode='a')
73
+
74
+ # Prepare log message
75
+ claim_data = " - Claim Data: {}".format(claim) if claim else ""
76
+ error_info = " - Error Type: {}".format(error_type) if error_type else ""
77
+ full_message = "{} {}{}".format(message, claim_data, error_info)
78
+
79
+ # Log the message
80
+ logger = logging.getLogger()
81
+ getattr(logger, level.lower())(full_message)