medicafe 0.250822.2__py3-none-any.whl → 0.250909.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.
@@ -1,428 +0,0 @@
1
- # MediCafe/api_core.py
2
- """
3
- Core API functionality for MediCafe.
4
- Moved from MediLink to centralize shared API operations.
5
-
6
- COMPATIBILITY: Python 3.4.4 and Windows XP compatible
7
- """
8
-
9
- import time
10
- import json
11
- import os
12
- import traceback
13
-
14
- try:
15
- import yaml
16
- except ImportError:
17
- yaml = None
18
-
19
- try:
20
- import requests
21
- except ImportError:
22
- requests = None
23
-
24
- # Use core utilities for standardized imports
25
- from MediCafe.core_utils import get_shared_config_loader
26
- MediLink_ConfigLoader = get_shared_config_loader()
27
-
28
- """
29
- Core API client classes and utilities for MediCafe.
30
- This module provides the foundation for all API operations across the project.
31
- """
32
-
33
- class ConfigLoader:
34
- @staticmethod
35
- def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
36
- crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
37
- return MediLink_ConfigLoader.load_configuration(config_path, crosswalk_path)
38
-
39
- @staticmethod
40
- def load_swagger_file(swagger_path):
41
- try:
42
- print("Attempting to load Swagger file: {}".format(swagger_path))
43
- with open(swagger_path, 'r') as swagger_file:
44
- if swagger_path.endswith('.yaml') or swagger_path.endswith('.yml'):
45
- print("Parsing YAML file: {}".format(swagger_path))
46
- swagger_data = yaml.safe_load(swagger_file)
47
- elif swagger_path.endswith('.json'):
48
- print("Parsing JSON file: {}".format(swagger_path))
49
- swagger_data = json.load(swagger_file)
50
- else:
51
- raise ValueError("Unsupported Swagger file format.")
52
- print("Successfully loaded Swagger file: {}".format(swagger_path))
53
- return swagger_data
54
- except ValueError as e:
55
- print("Error parsing Swagger file {}: {}".format(swagger_path, e))
56
- MediLink_ConfigLoader.log("Error parsing Swagger file {}: {}".format(swagger_path, e), level="ERROR")
57
- except FileNotFoundError:
58
- print("Swagger file not found: {}".format(swagger_path))
59
- MediLink_ConfigLoader.log("Swagger file not found: {}".format(swagger_path), level="ERROR")
60
- except Exception as e:
61
- print("Unexpected error loading Swagger file {}: {}".format(swagger_path, e))
62
- MediLink_ConfigLoader.log("Unexpected error loading Swagger file {}: {}".format(swagger_path, e), level="ERROR")
63
- return None
64
-
65
- # Function to ensure numeric type
66
- def ensure_numeric(value):
67
- if isinstance(value, str):
68
- try:
69
- value = float(value)
70
- except ValueError:
71
- raise ValueError("Cannot convert {} to a numeric type".format(value))
72
- return value
73
-
74
- class TokenCache:
75
- def __init__(self):
76
- self.tokens = {}
77
-
78
- def get(self, endpoint_name, current_time):
79
- token_info = self.tokens.get(endpoint_name, {})
80
- if token_info:
81
- expires_at = token_info['expires_at']
82
- # Log cache hit and expiration time
83
- log_message = "Token for {} expires at {}. Current time: {}".format(endpoint_name, expires_at, current_time)
84
- MediLink_ConfigLoader.log(log_message, level="DEBUG")
85
-
86
- if expires_at > current_time:
87
- return token_info['access_token']
88
-
89
- # Log cache miss
90
- # Token refresh flow validation has been implemented in get_access_token() to prevent unnecessary token pickup
91
- log_message = "No valid token found for {}".format(endpoint_name)
92
- MediLink_ConfigLoader.log(log_message, level="INFO")
93
-
94
- return None
95
-
96
- def set(self, endpoint_name, access_token, expires_in, current_time):
97
- current_time = ensure_numeric(current_time)
98
- expires_in = ensure_numeric(expires_in)
99
-
100
- # Log the expires_in value to debug
101
- log_message = "Token expires in: {} seconds for {}".format(expires_in, endpoint_name)
102
- MediLink_ConfigLoader.log(log_message, level="INFO")
103
-
104
- # Adjust expiration time by subtracting a buffer of 120 seconds
105
- buffer_seconds = 120
106
- adjusted_expires_in = expires_in - buffer_seconds
107
-
108
- if adjusted_expires_in <= 0:
109
- MediLink_ConfigLoader.log("Warning: Token expiration time too short after buffer adjustment", level="WARNING")
110
- adjusted_expires_in = 60 # Minimum 60 seconds
111
-
112
- expires_at = current_time + adjusted_expires_in
113
-
114
- self.tokens[endpoint_name] = {
115
- 'access_token': access_token,
116
- 'expires_at': expires_at,
117
- 'expires_in': expires_in
118
- }
119
-
120
- log_message = "Token cached for {} with expiration at {}".format(endpoint_name, expires_at)
121
- MediLink_ConfigLoader.log(log_message, level="INFO")
122
-
123
- class BaseAPIClient:
124
- def __init__(self, config):
125
- self.config = config
126
- self.token_cache = TokenCache()
127
-
128
- def get_access_token(self, endpoint_name):
129
- raise NotImplementedError("Subclasses should implement this!")
130
-
131
- def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
132
- raise NotImplementedError("Subclasses should implement this!")
133
-
134
- class APIClient(BaseAPIClient):
135
- def __init__(self):
136
- config, _ = MediLink_ConfigLoader.load_configuration()
137
- super().__init__(config)
138
-
139
- # Add enhanced features if available
140
- try:
141
- from MediCafe.api_utils import APICircuitBreaker, APICache, APIRateLimiter
142
- from MediLink import MediLink_insurance_utils
143
- get_feature_flag = MediLink_insurance_utils.get_feature_flag
144
-
145
- # Initialize enhancements if enabled
146
- enable_circuit_breaker = get_feature_flag('api_circuit_breaker', default=False)
147
- enable_caching = get_feature_flag('api_caching', default=False)
148
- enable_rate_limiting = get_feature_flag('api_rate_limiting', default=False)
149
-
150
- self.circuit_breaker = APICircuitBreaker() if enable_circuit_breaker else None
151
- self.api_cache = APICache() if enable_caching else None
152
- self.rate_limiter = APIRateLimiter() if enable_rate_limiting else None
153
-
154
- if any([enable_circuit_breaker, enable_caching, enable_rate_limiting]):
155
- MediLink_ConfigLoader.log("Enhanced API client initialized with circuit_breaker={}, caching={}, rate_limiting={}".format(
156
- enable_circuit_breaker, enable_caching, enable_rate_limiting), level="INFO")
157
- except ImportError:
158
- MediLink_ConfigLoader.log("API enhancements not available, using standard client", level="DEBUG")
159
- self.circuit_breaker = None
160
- self.api_cache = None
161
- self.rate_limiter = None
162
-
163
- def get_access_token(self, endpoint_name):
164
- MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
165
- current_time = time.time()
166
- cached_token = self.token_cache.get(endpoint_name, current_time)
167
-
168
- if cached_token:
169
- expires_at = self.token_cache.tokens[endpoint_name]['expires_at']
170
- MediLink_ConfigLoader.log("Cached token expires at {}".format(expires_at), level="DEBUG")
171
- return cached_token
172
-
173
- # Validate that we actually need a token before fetching
174
- # Check if the endpoint configuration exists and is valid
175
- try:
176
- endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
177
- if not endpoint_config:
178
- MediLink_ConfigLoader.log("No configuration found for endpoint: {}".format(endpoint_name), level="ERROR")
179
- return None
180
-
181
- # Validate required configuration fields
182
- required_fields = ['token_url', 'client_id', 'client_secret']
183
- missing_fields = [field for field in required_fields if field not in endpoint_config]
184
- if missing_fields:
185
- MediLink_ConfigLoader.log("Missing required configuration fields for {}: {}".format(endpoint_name, missing_fields), level="ERROR")
186
- return None
187
-
188
- except KeyError:
189
- MediLink_ConfigLoader.log("Endpoint {} not found in configuration".format(endpoint_name), level="ERROR")
190
- return None
191
- except Exception as e:
192
- MediLink_ConfigLoader.log("Error validating endpoint configuration for {}: {}".format(endpoint_name, str(e)), level="ERROR")
193
- return None
194
-
195
- # If no valid token, fetch a new one
196
- token_url = endpoint_config['token_url']
197
- data = {
198
- 'grant_type': 'client_credentials',
199
- 'client_id': endpoint_config['client_id'],
200
- 'client_secret': endpoint_config['client_secret']
201
- }
202
-
203
- # Add scope if specified in the configuration
204
- if 'scope' in endpoint_config:
205
- data['scope'] = endpoint_config['scope']
206
-
207
- headers = {'Content-Type': 'application/x-www-form-urlencoded'}
208
-
209
- try:
210
- response = requests.post(token_url, headers=headers, data=data)
211
- response.raise_for_status()
212
- token_data = response.json()
213
- access_token = token_data['access_token']
214
- expires_in = token_data.get('expires_in', 3600)
215
-
216
- self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
217
- MediLink_ConfigLoader.log("Obtained NEW token for endpoint: {}".format(endpoint_name), level="INFO")
218
- return access_token
219
- except requests.exceptions.RequestException as e:
220
- MediLink_ConfigLoader.log("Failed to obtain token for {}: {}".format(endpoint_name, str(e)), level="ERROR")
221
- return None
222
- except (KeyError, ValueError) as e:
223
- MediLink_ConfigLoader.log("Invalid token response for {}: {}".format(endpoint_name, str(e)), level="ERROR")
224
- return None
225
-
226
- def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
227
- # Try enhanced API call if available
228
- if hasattr(self, '_make_enhanced_api_call'):
229
- try:
230
- return self._make_enhanced_api_call(endpoint_name, call_type, url_extension, params, data, headers)
231
- except Exception as e:
232
- MediLink_ConfigLoader.log("Enhanced API call failed, falling back to standard: {}".format(str(e)), level="WARNING")
233
-
234
- # Fall back to standard API call
235
- return self._make_standard_api_call(endpoint_name, call_type, url_extension, params, data, headers)
236
-
237
- def _make_enhanced_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
238
- """Enhanced API call with circuit breaker, caching, and rate limiting."""
239
- if self.circuit_breaker:
240
- return self.circuit_breaker.call(self._make_standard_api_call, endpoint_name, call_type, url_extension, params, data, headers)
241
- else:
242
- return self._make_standard_api_call(endpoint_name, call_type, url_extension, params, data, headers)
243
-
244
- def _make_standard_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
245
- """Standard API call implementation."""
246
- try:
247
- # Get access token
248
- access_token = self.get_access_token(endpoint_name)
249
- if not access_token:
250
- raise Exception("Failed to obtain access token for endpoint: {}".format(endpoint_name))
251
-
252
- # Get endpoint configuration
253
- endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
254
- base_url = endpoint_config['base_url']
255
- api_url = base_url + url_extension
256
-
257
- # Prepare headers
258
- if headers is None:
259
- headers = {}
260
-
261
- headers.update({
262
- 'Authorization': 'Bearer {}'.format(access_token),
263
- 'Content-Type': 'application/json'
264
- })
265
-
266
- # Make the request
267
- def make_request():
268
- if call_type.upper() == 'GET':
269
- response = requests.get(api_url, headers=headers, params=params)
270
- elif call_type.upper() == 'POST':
271
- response = requests.post(api_url, headers=headers, params=params, json=data)
272
- elif call_type.upper() == 'PUT':
273
- response = requests.put(api_url, headers=headers, params=params, json=data)
274
- elif call_type.upper() == 'DELETE':
275
- response = requests.delete(api_url, headers=headers, params=params)
276
- else:
277
- raise ValueError("Unsupported HTTP method: {}".format(call_type))
278
-
279
- response.raise_for_status()
280
- return response.json()
281
-
282
- # Apply rate limiting if available
283
- if self.rate_limiter:
284
- return self.rate_limiter.call(make_request)
285
- else:
286
- return make_request()
287
-
288
- except Exception as e:
289
- MediLink_ConfigLoader.log("API call failed for {}: {}".format(endpoint_name, str(e)), level="ERROR")
290
- raise
291
-
292
- # Core API utility functions
293
- def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
294
- """
295
- Fetch payer name from API using the provided client.
296
-
297
- Args:
298
- client: API client instance
299
- payer_id: Payer ID to look up
300
- config: Configuration dictionary
301
- primary_endpoint: Primary endpoint to use for lookup
302
-
303
- Returns:
304
- str: Payer name if found, None otherwise
305
- """
306
- try:
307
- # Try primary endpoint first
308
- response = client.make_api_call(
309
- primary_endpoint,
310
- 'GET',
311
- '/payers/{}'.format(payer_id)
312
- )
313
-
314
- if response and 'name' in response:
315
- return response['name']
316
-
317
- # Try alternative endpoints if primary fails
318
- alternative_endpoints = ['ELIGIBILITY', 'CLAIMS']
319
- for endpoint in alternative_endpoints:
320
- try:
321
- response = client.make_api_call(
322
- endpoint,
323
- 'GET',
324
- '/payers/{}'.format(payer_id)
325
- )
326
-
327
- if response and 'name' in response:
328
- return response['name']
329
- except Exception as e:
330
- MediLink_ConfigLoader.log("Failed to fetch payer name from {}: {}".format(endpoint, str(e)), level="DEBUG")
331
- continue
332
-
333
- return None
334
-
335
- except Exception as e:
336
- MediLink_ConfigLoader.log("Failed to fetch payer name for {}: {}".format(payer_id, str(e)), level="ERROR")
337
- return None
338
-
339
- def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
340
- first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
341
- middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
342
- service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
343
- corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
344
- organization_id=None, identify_service_level_deductible=True):
345
- """
346
- Get eligibility information using v3 API.
347
-
348
- Args:
349
- client: API client instance
350
- payer_id: Payer ID
351
- provider_last_name: Provider's last name
352
- search_option: Search option
353
- date_of_birth: Date of birth
354
- member_id: Member ID
355
- npi: National Provider Identifier
356
- **kwargs: Additional optional parameters
357
-
358
- Returns:
359
- dict: Eligibility response data
360
- """
361
- # Ensure all required parameters have values
362
- required_params = {
363
- 'payer_id': payer_id,
364
- 'provider_last_name': provider_last_name,
365
- 'search_option': search_option,
366
- 'date_of_birth': date_of_birth,
367
- 'member_id': member_id,
368
- 'npi': npi
369
- }
370
-
371
- # Validate required parameters
372
- for param_name, param_value in required_params.items():
373
- if not param_value:
374
- raise ValueError("Required parameter '{}' is missing or empty".format(param_name))
375
-
376
- # Build request data
377
- request_data = {
378
- 'payer_id': payer_id,
379
- 'provider_last_name': provider_last_name,
380
- 'search_option': search_option,
381
- 'date_of_birth': date_of_birth,
382
- 'member_id': member_id,
383
- 'npi': npi,
384
- 'identify_service_level_deductible': identify_service_level_deductible
385
- }
386
-
387
- # Add optional parameters if provided
388
- optional_params = {
389
- 'first_name': first_name,
390
- 'last_name': last_name,
391
- 'payer_label': payer_label,
392
- 'payer_name': payer_name,
393
- 'service_start': service_start,
394
- 'service_end': service_end,
395
- 'middle_name': middle_name,
396
- 'gender': gender,
397
- 'ssn': ssn,
398
- 'city': city,
399
- 'state': state,
400
- 'zip': zip,
401
- 'group_number': group_number,
402
- 'service_type_code': service_type_code,
403
- 'provider_first_name': provider_first_name,
404
- 'tax_id_number': tax_id_number,
405
- 'provider_name_id': provider_name_id,
406
- 'corporate_tax_owner_id': corporate_tax_owner_id,
407
- 'corporate_tax_owner_name': corporate_tax_owner_name,
408
- 'organization_name': organization_name,
409
- 'organization_id': organization_id
410
- }
411
-
412
- for param_name, param_value in optional_params.items():
413
- if param_value is not None:
414
- request_data[param_name] = param_value
415
-
416
- try:
417
- response = client.make_api_call(
418
- 'ELIGIBILITY',
419
- 'POST',
420
- '/eligibility/v3',
421
- data=request_data
422
- )
423
-
424
- return response
425
-
426
- except Exception as e:
427
- MediLink_ConfigLoader.log("Eligibility v3 request failed: {}".format(str(e)), level="ERROR")
428
- raise