medicafe 0.240809.0__py3-none-any.whl → 0.240925.9__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.

@@ -1,8 +1,5 @@
1
- import time
2
- import requests
3
- import yaml
4
- import json
5
- import os
1
+ # MediLink_API_v3.py
2
+ import time, requests, yaml, json, os, traceback
6
3
 
7
4
  try:
8
5
  from MediLink import MediLink_ConfigLoader
@@ -64,18 +61,37 @@ class TokenCache:
64
61
 
65
62
  def get(self, endpoint_name, current_time):
66
63
  token_info = self.tokens.get(endpoint_name, {})
67
- if token_info and token_info['expires_at'] > current_time:
68
- return token_info['access_token']
64
+ if token_info:
65
+ expires_at = token_info['expires_at']
66
+ # Log cache hit and expiration time
67
+ log_message = "Token for {} expires at {}. Current time: {}".format(endpoint_name, expires_at, current_time)
68
+ MediLink_ConfigLoader.log(log_message, level="DEBUG")
69
+
70
+ if expires_at > current_time:
71
+ return token_info['access_token']
72
+
73
+ # Log cache miss
74
+ log_message = "No valid token found for {}".format(endpoint_name)
75
+ MediLink_ConfigLoader.log(log_message, level="INFO")
76
+
69
77
  return None
70
78
 
71
79
  def set(self, endpoint_name, access_token, expires_in, current_time):
72
- # Ensure types are correct
73
80
  current_time = ensure_numeric(current_time)
74
81
  expires_in = ensure_numeric(expires_in)
75
-
82
+
83
+ # Log the expires_in value to debug
84
+ log_message = "Token expires in: {} seconds for {}".format(expires_in, endpoint_name)
85
+ MediLink_ConfigLoader.log(log_message, level="INFO")
86
+
87
+ # Adjust expiration time by subtracting a buffer of 120 seconds
88
+ expires_at = current_time + expires_in - 120
89
+ log_message = "Setting token for {}. Expires at: {}".format(endpoint_name, expires_at)
90
+ MediLink_ConfigLoader.log(log_message, level="INFO")
91
+
76
92
  self.tokens[endpoint_name] = {
77
93
  'access_token': access_token,
78
- 'expires_at': current_time + expires_in - 120
94
+ 'expires_at': expires_at
79
95
  }
80
96
 
81
97
  class BaseAPIClient:
@@ -95,12 +111,16 @@ class APIClient(BaseAPIClient):
95
111
  super().__init__(config)
96
112
 
97
113
  def get_access_token(self, endpoint_name):
114
+ MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
98
115
  current_time = time.time()
99
116
  cached_token = self.token_cache.get(endpoint_name, current_time)
117
+
100
118
  if cached_token:
101
- MediLink_ConfigLoader.log("Using cached token for endpoint: {}".format(endpoint_name), level="INFO")
119
+ expires_at = self.token_cache.tokens[endpoint_name]['expires_at']
120
+ MediLink_ConfigLoader.log("Cached token expires at {}".format(expires_at), level="DEBUG")
102
121
  return cached_token
103
-
122
+
123
+ # If no valid token, fetch a new one
104
124
  endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
105
125
  token_url = endpoint_config['token_url']
106
126
  data = {
@@ -122,65 +142,225 @@ class APIClient(BaseAPIClient):
122
142
  expires_in = token_data.get('expires_in', 3600)
123
143
 
124
144
  self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
125
- MediLink_ConfigLoader.log("Obtained new token for endpoint: {}".format(endpoint_name), level="INFO")
145
+ MediLink_ConfigLoader.log("Obtained NEW token for endpoint: {}".format(endpoint_name), level="INFO")
126
146
  return access_token
127
147
 
128
148
  def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
129
149
  token = self.get_access_token(endpoint_name)
150
+ if token:
151
+ MediLink_ConfigLoader.log("[Make API Call] Token found for {}".format(endpoint_name), level="DEBUG")
152
+ else:
153
+ MediLink_ConfigLoader.log("[Make API Call] No token obtained for {}".format(endpoint_name), level="ERROR")
154
+ raise ValueError("No access token available for endpoint: {}".format(endpoint_name))
155
+
130
156
  if headers is None:
131
157
  headers = {}
132
158
  headers.update({'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'})
133
- url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url'] + url_extension
134
-
135
- # Debug: Print request details
136
- # print("Request URL: {}".format(url))
137
- # print("Request Headers: {}".format(headers))
138
- # print("Request Params: {}".format(params))
139
- # print("Request Data: {}".format(data))
140
-
141
- if call_type == 'GET':
142
- response = requests.get(url, headers=headers, params=params)
143
- elif call_type == 'POST':
144
- headers['Content-Type'] = 'application/json'
145
- response = requests.post(url, headers=headers, json=data)
146
- elif call_type == 'DELETE':
147
- response = requests.delete(url, headers=headers)
148
- else:
149
- raise ValueError("Unsupported call type")
159
+ base_url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url']
160
+ url = base_url + url_extension
161
+
162
+ try:
163
+ # Make the API call based on call_type
164
+ if call_type == 'GET':
165
+ response = requests.get(url, headers=headers, params=params)
166
+ elif call_type == 'POST':
167
+ headers['Content-Type'] = 'application/json'
168
+ response = requests.post(url, headers=headers, json=data)
169
+ elif call_type == 'DELETE':
170
+ response = requests.delete(url, headers=headers)
171
+ else:
172
+ raise ValueError("Unsupported call type: {}".format(call_type))
173
+
174
+ masked_headers = headers.copy()
175
+ if 'Authorization' in masked_headers:
176
+ masked_headers['Authorization'] = 'Bearer ***'
150
177
 
151
- if response.status_code >= 400:
152
- error_message = "Error {}: {}".format(response.status_code, response.text)
153
- MediLink_ConfigLoader.log(error_message, level="ERROR")
178
+ # Raise an HTTPError if the response was unsuccessful
154
179
  response.raise_for_status()
155
180
 
156
- return response.json()
181
+ return response.json()
182
+
183
+ except requests.exceptions.HTTPError as http_err:
184
+ # If http_err.response is None, handle it separately
185
+ if http_err.response is None:
186
+ log_message = (
187
+ "HTTPError with no response. "
188
+ "URL: {url}, "
189
+ "Method: {method}, "
190
+ "Params: {params}, "
191
+ "Data: {data}, "
192
+ "Headers: {masked_headers}, "
193
+ "Error: {error}"
194
+ ).format(
195
+ url=url,
196
+ method=call_type,
197
+ params=params,
198
+ data=data,
199
+ masked_headers=masked_headers,
200
+ error=str(http_err)
201
+ )
202
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
203
+ else:
204
+ # Extract status code and full response content
205
+ status_code = http_err.response.status_code
206
+ try:
207
+ # Try to log the JSON content if available
208
+ response_content = http_err.response.json()
209
+ except ValueError:
210
+ # Fallback to raw text if JSON decoding fails
211
+ response_content = http_err.response.text
212
+
213
+ log_message = (
214
+ "HTTPError: Status Code: {status}, "
215
+ "URL: {url}, "
216
+ "Method: {method}, "
217
+ "Params: {params}, "
218
+ "Data: {data}, "
219
+ "Headers: {masked_headers}, "
220
+ "Response Content: {content}"
221
+ ).format(
222
+ status=status_code,
223
+ url=url,
224
+ method=call_type,
225
+ params=params,
226
+ data=data,
227
+ masked_headers=masked_headers,
228
+ content=response_content
229
+ )
230
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
231
+ raise
232
+
233
+ except requests.exceptions.RequestException as req_err:
234
+ # Log connection-related issues or other request exceptions
235
+ log_message = (
236
+ "RequestException: No response received. "
237
+ "URL: {url}, "
238
+ "Method: {method}, "
239
+ "Params: {params}, "
240
+ "Data: {data}, "
241
+ "Headers: {masked_headers}, "
242
+ "Error: {error}"
243
+ ).format(
244
+ url=url,
245
+ method=call_type,
246
+ params=params,
247
+ data=data,
248
+ masked_headers=masked_headers,
249
+ error=str(req_err)
250
+ )
251
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
252
+ raise
157
253
 
158
- def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
159
- client = APIClient()
160
- config, _ = MediLink_ConfigLoader.load_configuration()
161
- endpoints = config['MediLink_Config']['endpoints']
254
+ except Exception as e:
255
+ # Capture traceback for unexpected exceptions
256
+ tb = traceback.format_exc()
257
+ log_message = (
258
+ "Unexpected error: {error}. "
259
+ "URL: {url}, "
260
+ "Method: {method}, "
261
+ "Params: {params}, "
262
+ "Data: {data}, "
263
+ "Headers: {masked_headers}. "
264
+ "Traceback: {traceback}"
265
+ ).format(
266
+ error=str(e),
267
+ url=url,
268
+ method=call_type,
269
+ params=params,
270
+ data=data,
271
+ masked_headers=masked_headers,
272
+ traceback=tb
273
+ )
274
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
275
+ raise
276
+
277
+ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
278
+ """
279
+ Fetches the payer name using the provided APIClient instance.
162
280
 
163
- if primary_endpoint and primary_endpoint in endpoints:
164
- endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
165
- else:
166
- endpoint_order = list(endpoints.keys())
281
+ :param client: An instance of APIClient
282
+ :param payer_id: The payer ID to fetch
283
+ :param primary_endpoint: The primary endpoint to use
284
+ :return: The payer name if found
285
+ """
286
+ # Ensure client is an instance of APIClient
287
+ if not isinstance(client, APIClient):
288
+ error_message = "Invalid client provided. Expected an instance of APIClient."
289
+ print(error_message)
290
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
291
+ exit(1) # Exit the script
292
+
293
+ # BUG Force AVAILITY until I can get a payer-list api from any of the other endpoints.
294
+ MediLink_ConfigLoader.log("[Fetch payer name from API] Overriding {} with AVAILITY.".format(primary_endpoint), level="DEBUG")
295
+ primary_endpoint = 'AVAILITY'
296
+
297
+ try:
298
+ endpoints = config['MediLink_Config']['endpoints']
299
+ except KeyError as e:
300
+ error_message = "Configuration loading error: Missing key {0}".format(e)
301
+ print(error_message)
302
+ MediLink_ConfigLoader.log(error_message, level="CRITICAL")
303
+ # Attempt to reload configuration if key is missing
304
+ config, _ = MediLink_ConfigLoader.load_configuration()
305
+ endpoints = config['MediLink_Config']['endpoints'] # Re-attempt to access endpoints
306
+
307
+ # Define endpoint rotation logic
308
+ endpoint_order = ([primary_endpoint] +
309
+ [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
310
+ if primary_endpoint in endpoints else list(endpoints.keys()))
167
311
 
168
312
  for endpoint_name in endpoint_order:
169
313
  try:
170
- response = client.make_api_call(endpoint_name, 'GET', config['MediLink_Config']['endpoints'][endpoint_name].get('payer_list_endpoint', '/availity-payer-list'), {'payerId': payer_id})
314
+ endpoint_url = endpoints[endpoint_name].get('payer_list_endpoint', '/availity-payer-list')
315
+ response = client.make_api_call(endpoint_name, 'GET', endpoint_url, {'payerId': payer_id})
316
+
317
+ # Check if response exists
318
+ if not response:
319
+ log_message = "No response from {0} for Payer ID {1}".format(endpoint_name, payer_id)
320
+ print(log_message)
321
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
322
+ continue
323
+
324
+ # Check if the status code is not 200
325
+ status_code = response.get('statuscode', 200)
326
+ if status_code != 200:
327
+ log_message = "Invalid response status code {0} from {1} for Payer ID {2}. Message: {3}".format(
328
+ status_code, endpoint_name, payer_id, response.get('message', 'No message'))
329
+ print(log_message)
330
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
331
+ continue
332
+
333
+ # Extract payers and validate the response structure
171
334
  payers = response.get('payers', [])
172
- if payers:
173
- payer_name = payers[0].get('displayName', payers[0].get('name'))
174
- MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), level="INFO")
175
- return payer_name
176
- else:
177
- MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), level="INFO")
335
+ if not payers:
336
+ log_message = "No payer found at {0} for ID {1}. Response: {2}".format(endpoint_name, payer_id, response)
337
+ print(log_message)
338
+ MediLink_ConfigLoader.log(log_message, level="INFO")
339
+ continue
340
+
341
+ # Extract the payer name from the first payer in the list
342
+ payer_name = payers[0].get('displayName') or payers[0].get('name')
343
+ if not payer_name:
344
+ log_message = "Payer name not found in the response from {0} for ID {1}. Response: {2}".format(
345
+ endpoint_name, payer_id, response)
346
+ print(log_message)
347
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
348
+ continue
349
+
350
+ # Log successful payer retrieval
351
+ log_message = "Found payer at {0} for ID {1}: {2}".format(endpoint_name, payer_id, payer_name)
352
+ MediLink_ConfigLoader.log(log_message, level="INFO")
353
+ return payer_name
354
+
178
355
  except Exception as e:
179
- MediLink_ConfigLoader.log("API call to {} failed: {}".format(endpoint_name, e), level="ERROR")
356
+ error_message = "Error calling {0} for Payer ID {1}. Exception: {2}".format(endpoint_name, payer_id, e)
357
+ MediLink_ConfigLoader.log(error_message, level="INFO")
180
358
 
181
- error_message = "All endpoints exhausted for Payer ID {}.".format(payer_id)
182
- MediLink_ConfigLoader.log(error_message, level="CRITICAL")
183
- raise ValueError(error_message)
359
+ # If all endpoints fail
360
+ final_error_message = "All endpoints exhausted for Payer ID {0}.".format(payer_id)
361
+ print(final_error_message)
362
+ MediLink_ConfigLoader.log(final_error_message, level="CRITICAL")
363
+ raise ValueError(final_error_message)
184
364
 
185
365
  def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false'):
186
366
  endpoint_name = 'UHCAPI'
@@ -273,7 +453,8 @@ def is_test_mode(client, body, endpoint_type):
273
453
  if client.config.get("MediLink_Config", {}).get("TestMode", True):
274
454
  print("Test Mode is enabled! API Call not executed.")
275
455
  print("\nIntended request body:", body)
276
- MediLink_ConfigLoader.log("Test Mode is enabled! Simulating API response for {}.".format(endpoint_type), level="INFO")
456
+ MediLink_ConfigLoader.log("Test Mode is enabled! Simulating 1 second delay for API response for {}.".format(endpoint_type), level="INFO")
457
+ time.sleep(1)
277
458
  MediLink_ConfigLoader.log("Intended request body: {}".format(body), level="INFO")
278
459
 
279
460
  if endpoint_type == 'claim_submission':
@@ -350,11 +531,11 @@ if __name__ == "__main__":
350
531
 
351
532
  # Define a configuration to enable or disable tests
352
533
  test_config = {
353
- 'test_fetch_payer_name': False,
534
+ 'test_fetch_payer_name': True,
354
535
  'test_claim_summary': False,
355
536
  'test_eligibility': False,
356
537
  'test_eligibility_v3': False,
357
- 'test_claim_submission': True,
538
+ 'test_claim_submission': False,
358
539
  }
359
540
 
360
541
  try:
@@ -364,10 +545,10 @@ if __name__ == "__main__":
364
545
  if test_config.get('test_fetch_payer_name', False):
365
546
  try:
366
547
  for case in api_test_cases:
367
- payer_name = fetch_payer_name_from_api(case['payer_id'], client.config)
368
- print("TEST API: Payer Name: {}".format(payer_name))
548
+ payer_name = fetch_payer_name_from_api(client, case['payer_id'], client.config)
549
+ print("*** TEST API: Payer Name: {}".format(payer_name))
369
550
  except Exception as e:
370
- print("TEST API: Error in Fetch Payer Name Test: {}".format(e))
551
+ print("*** TEST API: Error in Fetch Payer Name Test: {}".format(e))
371
552
 
372
553
  # Test 2: Get Claim Summary
373
554
  if test_config.get('test_claim_summary', False):
MediLink/MediLink_APIs.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Unused archive backup. This has been superceded by API_v2
2
2
 
3
- import time
4
- import requests
3
+ import time, requests
5
4
  try:
6
5
  from MediLink import MediLink_ConfigLoader
7
6
  except ImportError:
@@ -1,4 +1,6 @@
1
+ # MediLink_ClaimStatus.py
1
2
  from datetime import datetime, timedelta
3
+ import os
2
4
  import MediLink_API_v3
3
5
 
4
6
  try:
@@ -19,7 +21,7 @@ start_date_str = start_date.strftime('%m/%d/%Y')
19
21
  billing_provider_tin = config['MediLink_Config'].get('billing_provider_tin')
20
22
 
21
23
  # Define the list of payer_id's to iterate over
22
- payer_ids = ['87726'] # Default value
24
+ payer_ids = ['87726'] # Default value, getting a bad request error when trying to get all payer_ids.
23
25
  # Allowed payer id's for UHC
24
26
  # payer_ids = ['87726', '03432', '96385', '95467', '86050', '86047', '95378', '06111', '37602']
25
27
 
@@ -27,19 +29,23 @@ payer_ids = ['87726'] # Default value
27
29
  client = MediLink_API_v3.APIClient()
28
30
 
29
31
  # Function to process and display the data in a compact, tabular format
30
- def display_claim_summary(claim_summary, payer_id):
32
+ def display_claim_summary(claim_summary, payer_id, output_file):
31
33
  claims = claim_summary.get('claims', [])
32
34
 
33
35
  # Display header
34
36
  header = "Payer ID: {} | Start Date: {} | End Date: {}".format(payer_id, start_date_str, end_date_str)
35
37
  print(header)
38
+ output_file.write(header + "\n") # Write header to the output file
36
39
  print("=" * len(header))
40
+ output_file.write("=" * len(header) + "\n") # Write separator to the output file
37
41
 
38
42
  # Table header
39
43
  table_header = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
40
44
  "Claim #", "Status", "Patient", "Proc.", "Serv.", "Allowed", "Paid", "Pt Resp", "Charged")
41
45
  print(table_header)
46
+ output_file.write(table_header + "\n") # Write table header to the output file
42
47
  print("-" * len(table_header))
48
+ output_file.write("-" * len(table_header) + "\n") # Write separator to the output file
43
49
 
44
50
  # Process each claim and display it in a compact format
45
51
  claims_dict = {}
@@ -90,6 +96,7 @@ def display_claim_summary(claim_summary, payer_id):
90
96
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
91
97
  )
92
98
  print(table_row)
99
+ output_file.write(table_row + "\n") # Write each row to the output file
93
100
 
94
101
  if claim_data['total_paid_amount'] == '0.00':
95
102
  for xwalk in claim_data['claim_xwalk_data']:
@@ -109,6 +116,7 @@ def display_claim_summary(claim_summary, payer_id):
109
116
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
110
117
  )
111
118
  print(table_row + " (Duplicate with different data)")
119
+ output_file.write(table_row + " (Duplicate with different data)\n") # Write each row to the output file
112
120
 
113
121
  if claim_data['total_paid_amount'] == '0.00':
114
122
  for xwalk in claim_data['claim_xwalk_data']:
@@ -128,6 +136,7 @@ def display_claim_summary(claim_summary, payer_id):
128
136
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
129
137
  )
130
138
  print(table_row)
139
+ output_file.write(table_row + "\n") # Write each row to the output file
131
140
 
132
141
  if claim_data['total_paid_amount'] == '0.00':
133
142
  for xwalk in claim_data['claim_xwalk_data']:
@@ -138,7 +147,13 @@ def display_claim_summary(claim_summary, payer_id):
138
147
  clmIcnSufxCd = xwalk['clmIcnSufxCd'] # String: e.g., "01"
139
148
  print(" 507: {} ({}) | 508: {} ({}) | ICN Suffix: {}".format(clm507Cd, clm507CdDesc, clm508Cd, clm508CdDesc, clmIcnSufxCd))
140
149
 
141
- # Loop through each payer_id and call the API, then display the claim summary
142
- for payer_id in payer_ids:
143
- claim_summary = MediLink_API_v3.get_claim_summary_by_provider(client, billing_provider_tin, start_date_str, end_date_str, payer_id=payer_id)
144
- display_claim_summary(claim_summary, payer_id)
150
+ # Create a temporary file to store the claim summary
151
+ output_file_path = os.path.join(os.getenv('TEMP'), 'claim_summary_report.txt')
152
+ with open(output_file_path, 'w') as output_file:
153
+ # Loop through each payer_id and call the API, then display the claim summary
154
+ for payer_id in payer_ids:
155
+ claim_summary = MediLink_API_v3.get_claim_summary_by_provider(client, billing_provider_tin, start_date_str, end_date_str, payer_id=payer_id)
156
+ display_claim_summary(claim_summary, payer_id, output_file) # Pass output_file to the display function
157
+
158
+ # Open the generated file in Notepad
159
+ os.startfile(output_file_path) # Use os.startfile for better handling
@@ -1,11 +1,7 @@
1
- import os
2
- import json
3
- import logging
1
+ # MediLink_ConfigLoader.py
2
+ import os, json, logging, sys, platform, yaml
4
3
  from datetime import datetime
5
4
  from collections import OrderedDict
6
- import sys
7
- import platform
8
- import yaml
9
5
 
10
6
  """
11
7
  This function should be generalizable to have a initialization script over all the Medi* functions
@@ -61,7 +57,7 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
61
57
  sys.exit(1) # Exit the script due to a critical error in configuration loading
62
58
 
63
59
  # Logs messages with optional error type and claim data.
64
- def log(message, config=None, level="INFO", error_type=None, claim=None):
60
+ def log(message, config=None, level="INFO", error_type=None, claim=None, verbose=False):
65
61
 
66
62
  # If config is not provided, load it
67
63
  if config is None:
@@ -72,7 +68,11 @@ def log(message, config=None, level="INFO", error_type=None, claim=None):
72
68
  local_storage_path = config['MediLink_Config'].get('local_storage_path', '.') if isinstance(config, dict) else '.'
73
69
  log_filename = datetime.now().strftime("Log_%m%d%Y.log")
74
70
  log_filepath = os.path.join(local_storage_path, log_filename)
75
- logging.basicConfig(level=logging.INFO,
71
+
72
+ # Set logging level based on verbosity
73
+ logging_level = logging.DEBUG if verbose else logging.INFO
74
+
75
+ logging.basicConfig(level=logging_level,
76
76
  format='%(asctime)s - %(levelname)s - %(message)s',
77
77
  filename=log_filepath,
78
78
  filemode='a')