medicafe 0.250728.8__py3-none-any.whl → 0.250805.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 (58) hide show
  1. MediBot/MediBot.bat +233 -19
  2. MediBot/MediBot.py +138 -46
  3. MediBot/MediBot_Crosswalk_Library.py +127 -623
  4. MediBot/MediBot_Crosswalk_Utils.py +618 -0
  5. MediBot/MediBot_Preprocessor.py +72 -17
  6. MediBot/MediBot_Preprocessor_lib.py +470 -76
  7. MediBot/MediBot_UI.py +32 -17
  8. MediBot/MediBot_dataformat_library.py +68 -20
  9. MediBot/MediBot_docx_decoder.py +120 -19
  10. MediBot/MediBot_smart_import.py +180 -0
  11. MediBot/__init__.py +89 -0
  12. MediBot/get_medicafe_version.py +25 -0
  13. MediBot/update_json.py +35 -6
  14. MediBot/update_medicafe.py +19 -1
  15. MediCafe/MediLink_ConfigLoader.py +160 -0
  16. MediCafe/__init__.py +171 -0
  17. MediCafe/__main__.py +222 -0
  18. MediCafe/api_core.py +1098 -0
  19. MediCafe/api_core_backup.py +427 -0
  20. MediCafe/api_factory.py +306 -0
  21. MediCafe/api_utils.py +356 -0
  22. MediCafe/core_utils.py +450 -0
  23. MediCafe/graphql_utils.py +445 -0
  24. MediCafe/logging_config.py +123 -0
  25. MediCafe/logging_demo.py +61 -0
  26. MediCafe/migration_helpers.py +463 -0
  27. MediCafe/smart_import.py +436 -0
  28. MediLink/MediLink.py +66 -26
  29. MediLink/MediLink_837p_cob_library.py +28 -28
  30. MediLink/MediLink_837p_encoder.py +33 -34
  31. MediLink/MediLink_837p_encoder_library.py +243 -151
  32. MediLink/MediLink_837p_utilities.py +129 -5
  33. MediLink/MediLink_API_Generator.py +83 -60
  34. MediLink/MediLink_API_v3.py +1 -1
  35. MediLink/MediLink_ClaimStatus.py +177 -31
  36. MediLink/MediLink_DataMgmt.py +405 -72
  37. MediLink/MediLink_Decoder.py +20 -1
  38. MediLink/MediLink_Deductible.py +155 -28
  39. MediLink/MediLink_Display_Utils.py +72 -0
  40. MediLink/MediLink_Down.py +127 -5
  41. MediLink/MediLink_Gmail.py +712 -653
  42. MediLink/MediLink_PatientProcessor.py +257 -0
  43. MediLink/MediLink_UI.py +85 -61
  44. MediLink/MediLink_Up.py +28 -4
  45. MediLink/MediLink_insurance_utils.py +227 -264
  46. MediLink/MediLink_main.py +248 -0
  47. MediLink/MediLink_smart_import.py +264 -0
  48. MediLink/__init__.py +93 -0
  49. MediLink/insurance_type_integration_test.py +66 -76
  50. MediLink/test.py +1 -1
  51. MediLink/test_timing.py +59 -0
  52. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/METADATA +1 -1
  53. medicafe-0.250805.0.dist-info/RECORD +81 -0
  54. medicafe-0.250805.0.dist-info/entry_points.txt +2 -0
  55. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/top_level.txt +1 -0
  56. medicafe-0.250728.8.dist-info/RECORD +0 -59
  57. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/LICENSE +0 -0
  58. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Production API Client Factory - Python 3.4.4 Compatible
4
+ Unified factory for all API operations using MediCafe api_core as the foundation.
5
+
6
+ COMPATIBILITY: Python 3.4.4 and Windows XP compatible
7
+ STRATEGY: v3-only implementation with v2 deprecation handling
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import time
13
+
14
+ # Direct imports from MediCafe package (simplified since we're in the same package)
15
+ try:
16
+ from .api_core import APIClient as ProductionAPIClient
17
+ PRODUCTION_AVAILABLE = True
18
+ except ImportError:
19
+ try:
20
+ # Fallback for when running as standalone module
21
+ from .api_core import APIClient as ProductionAPIClient
22
+ PRODUCTION_AVAILABLE = True
23
+ except ImportError:
24
+ PRODUCTION_AVAILABLE = False
25
+ class ProductionAPIClient:
26
+ def __init__(self):
27
+ pass
28
+
29
+ # Use core utilities for standardized imports
30
+ try:
31
+ from .core_utils import get_shared_config_loader
32
+ MediLink_ConfigLoader = get_shared_config_loader()
33
+ if MediLink_ConfigLoader is None:
34
+ raise ImportError("MediLink_ConfigLoader not available")
35
+ except ImportError:
36
+ try:
37
+ # Fallback for when running as standalone module
38
+ from MediCafe.core_utils import get_shared_config_loader
39
+ MediLink_ConfigLoader = get_shared_config_loader()
40
+ if MediLink_ConfigLoader is None:
41
+ raise ImportError("MediLink_ConfigLoader not available")
42
+ except ImportError:
43
+ # Fallback for when core_utils is not available
44
+ class MediLink_ConfigLoader:
45
+ @staticmethod
46
+ def log(message, level="INFO"):
47
+ print("[{}] {}".format(level, message))
48
+
49
+ # Hardcoded configuration with reasonable defaults
50
+ DEFAULT_FACTORY_CONFIG = {
51
+ 'default_version': 'v3',
52
+ 'fallback_enabled': False, # v2 deprecated
53
+ 'circuit_breaker': {
54
+ 'failure_threshold': 5,
55
+ 'recovery_timeout': 30
56
+ },
57
+ 'deprecation_warnings': {
58
+ 'v2_warning_shown': False
59
+ }
60
+ }
61
+
62
+ class FactoryCircuitBreaker:
63
+ """
64
+ Circuit breaker for factory-level reliability.
65
+ Python 3.4.4 compatible implementation.
66
+ """
67
+
68
+ def __init__(self, failure_threshold=5, recovery_timeout=30):
69
+ self.failure_threshold = failure_threshold
70
+ self.recovery_timeout = recovery_timeout
71
+ self.failure_count = 0
72
+ self.last_failure_time = None
73
+ self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
74
+
75
+ def call(self, func, *args, **kwargs):
76
+ """Execute function with circuit breaker protection."""
77
+ if self.state == 'OPEN':
78
+ if self._should_attempt_reset():
79
+ self.state = 'HALF_OPEN'
80
+ else:
81
+ raise Exception("API Factory Circuit Breaker is OPEN")
82
+
83
+ try:
84
+ result = func(*args, **kwargs)
85
+ self._on_success()
86
+ return result
87
+ except Exception as e:
88
+ self._on_failure()
89
+ raise
90
+
91
+ def _should_attempt_reset(self):
92
+ """Check if enough time has passed to attempt reset."""
93
+ if self.last_failure_time is None:
94
+ return True
95
+ return (time.time() - self.last_failure_time) >= self.recovery_timeout
96
+
97
+ def _on_success(self):
98
+ """Reset circuit breaker on successful call."""
99
+ self.failure_count = 0
100
+ self.state = 'CLOSED'
101
+
102
+ def _on_failure(self):
103
+ """Handle failure and potentially open circuit breaker."""
104
+ self.failure_count += 1
105
+ self.last_failure_time = time.time()
106
+ if self.failure_count >= self.failure_threshold:
107
+ self.state = 'OPEN'
108
+ MediLink_ConfigLoader.log("Circuit breaker OPENED after {} failures".format(self.failure_count), level="WARNING")
109
+
110
+ class APIClientFactory:
111
+ """
112
+ Production-ready API client factory focused on api_core.
113
+
114
+ Features:
115
+ - Unified access point for all API operations
116
+ - Configuration-driven behavior (with hardcoded defaults)
117
+ - Circuit breaker reliability
118
+ - Payer-specific optimizations
119
+ - Deprecation management for v2
120
+
121
+ Python 3.4.4 and Windows XP compatible.
122
+ """
123
+
124
+ _shared_client = None
125
+ _factory_config = None
126
+
127
+ @classmethod
128
+ def _load_factory_config(cls):
129
+ """Load factory configuration with hardcoded defaults."""
130
+ if cls._factory_config is not None:
131
+ return cls._factory_config
132
+
133
+ # Use hardcoded defaults only
134
+ cls._factory_config = DEFAULT_FACTORY_CONFIG.copy()
135
+ MediLink_ConfigLoader.log("Factory using hardcoded configuration", level="DEBUG")
136
+
137
+ return cls._factory_config
138
+
139
+ def __init__(self, config=None):
140
+ self.config = config or self._load_factory_config()
141
+ self.circuit_breaker = FactoryCircuitBreaker(
142
+ failure_threshold=self.config['circuit_breaker']['failure_threshold'],
143
+ recovery_timeout=self.config['circuit_breaker']['recovery_timeout']
144
+ )
145
+ self._warn_about_deprecations()
146
+
147
+ def _warn_about_deprecations(self):
148
+ """Show deprecation warnings for legacy API versions."""
149
+ # v2 has been archived - no longer needed
150
+ pass
151
+
152
+ def get_client(self, version='v3', **kwargs):
153
+ """
154
+ Get API client with reliable creation and error handling.
155
+
156
+ Args:
157
+ version (str): API version ('v3' only, 'v2' is archived and unavailable)
158
+ **kwargs: Additional parameters
159
+
160
+ Returns:
161
+ APIClient: Configured v3 API client instance
162
+ """
163
+ # Handle archived version requests
164
+ if version == 'v2':
165
+ raise ValueError("API v2 has been archived and is no longer available. Use v3 instead.")
166
+ elif version not in ['v3', 'auto']:
167
+ MediLink_ConfigLoader.log(
168
+ "WARNING: Unknown API version '{}' requested. Using v3.".format(version),
169
+ level="WARNING"
170
+ )
171
+ version = 'v3'
172
+
173
+ # Use circuit breaker for client creation
174
+ def create_client():
175
+ if not PRODUCTION_AVAILABLE:
176
+ MediLink_ConfigLoader.log("Production APIClient not available, using fallback", level="WARNING")
177
+ return ProductionAPIClient()
178
+
179
+ client = ProductionAPIClient()
180
+ MediLink_ConfigLoader.log("Created v3 API client via factory", level="DEBUG")
181
+
182
+ return client
183
+
184
+ return self.circuit_breaker.call(create_client)
185
+
186
+ @classmethod
187
+ def get_shared_client(cls, **kwargs):
188
+ """
189
+ Get shared API client for operations that benefit from token caching.
190
+ """
191
+ if cls._shared_client is None:
192
+ factory = cls()
193
+ cls._shared_client = factory.get_client(**kwargs)
194
+ MediLink_ConfigLoader.log("Initialized shared v3 API client via factory", level="INFO")
195
+
196
+ return cls._shared_client
197
+
198
+ @classmethod
199
+ def reset_shared_client(cls):
200
+ """Reset shared client for testing and configuration changes."""
201
+ cls._shared_client = None
202
+ cls._factory_config = None
203
+ MediLink_ConfigLoader.log("Factory shared client reset", level="DEBUG")
204
+
205
+ def make_request(self, endpoint, data=None, method='POST', **kwargs):
206
+ """
207
+ Make API request through factory.
208
+
209
+ Args:
210
+ endpoint (str): API endpoint
211
+ data (dict): Request data
212
+ method (str): HTTP method
213
+ **kwargs: Additional request parameters
214
+
215
+ Returns:
216
+ dict: API response data
217
+ """
218
+ client = self.get_client()
219
+
220
+ # Delegate to client's make_api_call method
221
+ if hasattr(client, 'make_api_call'):
222
+ return client.make_api_call(endpoint, method, data=data, **kwargs)
223
+ else:
224
+ raise NotImplementedError("Client does not support make_api_call method")
225
+
226
+ def make_batch_request(self, requests_data, **kwargs):
227
+ """
228
+ Process multiple API requests efficiently.
229
+
230
+ Args:
231
+ requests_data (list): List of request dictionaries
232
+ **kwargs: Additional parameters
233
+
234
+ Returns:
235
+ list: List of response data
236
+ """
237
+ client = self.get_client()
238
+
239
+ # Process requests individually
240
+ results = []
241
+ for request in requests_data:
242
+ try:
243
+ result = self.make_request(
244
+ request.get('endpoint'),
245
+ request.get('data'),
246
+ request.get('method', 'POST'),
247
+ **kwargs
248
+ )
249
+ results.append(result)
250
+ except Exception as e:
251
+ MediLink_ConfigLoader.log("Batch request failed: {}".format(e), level="ERROR")
252
+ results.append({'error': str(e)})
253
+
254
+ return results
255
+
256
+ # Convenience functions for backward compatibility
257
+ def APIClient(**kwargs):
258
+ """
259
+ Drop-in replacement for existing APIClient() calls.
260
+
261
+ Returns shared v3 client via factory for token caching benefits.
262
+ """
263
+ return APIClientFactory.get_shared_client(**kwargs)
264
+
265
+ def create_fresh_api_client(**kwargs):
266
+ """Create independent API client when shared state is not desired."""
267
+ factory = APIClientFactory()
268
+ return factory.get_client(**kwargs)
269
+
270
+ def reset_api_client():
271
+ """Reset shared client (primarily for testing)."""
272
+ APIClientFactory.reset_shared_client()
273
+
274
+ # Factory instance getter for core_utils integration
275
+ def get_factory_instance():
276
+ """Get factory instance for core utilities integration."""
277
+ return APIClientFactory()
278
+
279
+ if __name__ == "__main__":
280
+ print("API Client Factory - Production Implementation (Python 3.4.4 Compatible)")
281
+ print("=" * 70)
282
+
283
+ try:
284
+ # Test factory creation
285
+ factory = APIClientFactory()
286
+ print("Factory created successfully")
287
+
288
+ # Test client creation
289
+ client = factory.get_client()
290
+ print("v3 client created: {}".format(type(client)))
291
+
292
+ # Test shared client
293
+ shared_client = APIClientFactory.get_shared_client()
294
+ print("Shared client available: {}".format(type(shared_client)))
295
+
296
+ # Test deprecation handling
297
+ deprecated_client = factory.get_client(version='v2')
298
+ print("Deprecation handling working")
299
+
300
+ # Test circuit breaker
301
+ print("Circuit breaker threshold: {}".format(factory.circuit_breaker.failure_threshold))
302
+
303
+ print("\nAll factory functionality validated")
304
+
305
+ except Exception as e:
306
+ print("Factory validation failed: {}".format(e))
MediCafe/api_utils.py ADDED
@@ -0,0 +1,356 @@
1
+ # MediLink_api_utils.py
2
+ # Enhanced API utilities for circuit breaker, caching, and rate limiting
3
+ # Extracted from enhanced implementations for safe production integration
4
+ # Python 3.4.4 compatible implementation
5
+
6
+ import time
7
+
8
+ # Use core utilities for standardized imports
9
+ from MediCafe.core_utils import get_shared_config_loader
10
+ MediLink_ConfigLoader = get_shared_config_loader()
11
+
12
+ # Token cache for API authentication
13
+ class TokenCache:
14
+ """Token cache for API authentication"""
15
+ def __init__(self):
16
+ self.tokens = {}
17
+
18
+ def get(self, endpoint_name, current_time):
19
+ """Get cached token if valid"""
20
+ token_info = self.tokens.get(endpoint_name, {})
21
+ if token_info:
22
+ expires_at = token_info['expires_at']
23
+ # Log cache hit and expiration time
24
+ log_message = "Token for {} expires at {}. Current time: {}".format(endpoint_name, expires_at, current_time)
25
+ MediLink_ConfigLoader.log(log_message, level="DEBUG")
26
+
27
+ if expires_at > current_time:
28
+ return token_info['access_token']
29
+
30
+ # Log cache miss
31
+ log_message = "No valid token found for {}".format(endpoint_name)
32
+ MediLink_ConfigLoader.log(log_message, level="INFO")
33
+ return None
34
+
35
+ def set(self, endpoint_name, access_token, expires_in, current_time):
36
+ """Set cached token with expiration"""
37
+ # Ensure numeric types
38
+ if isinstance(current_time, str):
39
+ try:
40
+ current_time = float(current_time)
41
+ except ValueError:
42
+ raise ValueError("Cannot convert current_time to numeric type")
43
+
44
+ if isinstance(expires_in, str):
45
+ try:
46
+ expires_in = float(expires_in)
47
+ except ValueError:
48
+ raise ValueError("Cannot convert expires_in to numeric type")
49
+
50
+ # Log the expires_in value to debug
51
+ log_message = "Token expires in: {} seconds for {}".format(expires_in, endpoint_name)
52
+ MediLink_ConfigLoader.log(log_message, level="INFO")
53
+
54
+ # Adjust expiration time by subtracting a buffer of 120 seconds
55
+ expires_at = current_time + expires_in - 120
56
+ log_message = "Setting token for {}. Expires at: {}".format(endpoint_name, expires_at)
57
+ MediLink_ConfigLoader.log(log_message, level="INFO")
58
+
59
+ self.tokens[endpoint_name] = {
60
+ 'access_token': access_token,
61
+ 'expires_at': expires_at
62
+ }
63
+
64
+ # Circuit breaker pattern for API resilience
65
+ class APICircuitBreaker:
66
+ """Circuit breaker pattern for API resilience"""
67
+ def __init__(self, failure_threshold=5, timeout=300):
68
+ self.failure_count = 0
69
+ self.failure_threshold = failure_threshold
70
+ self.timeout = timeout
71
+ self.last_failure_time = None
72
+ self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
73
+
74
+ def can_execute(self):
75
+ """Check if circuit breaker allows execution"""
76
+ if self.state == 'OPEN':
77
+ if time.time() - self.last_failure_time > self.timeout:
78
+ self.state = 'HALF_OPEN'
79
+ MediLink_ConfigLoader.log("Circuit breaker moving to HALF_OPEN state", level="INFO")
80
+ return True
81
+ else:
82
+ return False
83
+ return True
84
+
85
+ def record_success(self):
86
+ """Record successful API call"""
87
+ if self.state == 'HALF_OPEN':
88
+ self.state = 'CLOSED'
89
+ self.failure_count = 0
90
+ MediLink_ConfigLoader.log("Circuit breaker reset to CLOSED state", level="INFO")
91
+
92
+ def record_failure(self):
93
+ """Record failed API call"""
94
+ self.failure_count += 1
95
+ self.last_failure_time = time.time()
96
+ if self.failure_count >= self.failure_threshold:
97
+ self.state = 'OPEN'
98
+ MediLink_ConfigLoader.log("Circuit breaker OPENED due to {} failures".format(self.failure_count), level="ERROR")
99
+
100
+ def call_with_breaker(self, api_function, *args, **kwargs):
101
+ """Call API function with circuit breaker protection"""
102
+ if not self.can_execute():
103
+ raise Exception("API circuit breaker is OPEN - too many failures")
104
+
105
+ try:
106
+ result = api_function(*args, **kwargs)
107
+ self.record_success()
108
+ return result
109
+ except Exception as e:
110
+ self.record_failure()
111
+ raise e
112
+
113
+ # Cache for API responses to reduce redundant calls
114
+ class APICache:
115
+ """Simple time-based cache for API responses"""
116
+ def __init__(self, cache_duration=3600): # 1 hour cache
117
+ self.cache = {}
118
+ self.cache_duration = cache_duration
119
+
120
+ def _generate_cache_key(self, *args, **kwargs):
121
+ """Generate cache key from function arguments"""
122
+ key_parts = []
123
+ for arg in args:
124
+ key_parts.append(str(arg))
125
+ for k, v in sorted(kwargs.items()):
126
+ key_parts.append("{}:{}".format(k, v))
127
+ return "|".join(key_parts)
128
+
129
+ def get(self, *args, **kwargs):
130
+ """Get cached result if available and not expired"""
131
+ cache_key = self._generate_cache_key(*args, **kwargs)
132
+ if cache_key in self.cache:
133
+ cached_data = self.cache[cache_key]
134
+ if time.time() - cached_data['timestamp'] < self.cache_duration:
135
+ MediLink_ConfigLoader.log("Cache hit for key: {}".format(cache_key[:50]), level="DEBUG")
136
+ return cached_data['result']
137
+ return None
138
+
139
+ def set(self, result, *args, **kwargs):
140
+ """Cache result"""
141
+ cache_key = self._generate_cache_key(*args, **kwargs)
142
+ self.cache[cache_key] = {
143
+ 'result': result,
144
+ 'timestamp': time.time()
145
+ }
146
+ MediLink_ConfigLoader.log("Cached result for key: {}".format(cache_key[:50]), level="DEBUG")
147
+
148
+ def clear_expired(self):
149
+ """Remove expired cache entries"""
150
+ now = time.time()
151
+ expired_keys = []
152
+ for key, data in self.cache.items():
153
+ if now - data['timestamp'] >= self.cache_duration:
154
+ expired_keys.append(key)
155
+
156
+ for key in expired_keys:
157
+ del self.cache[key]
158
+
159
+ if expired_keys:
160
+ MediLink_ConfigLoader.log("Cleared {} expired cache entries".format(len(expired_keys)), level="DEBUG")
161
+
162
+ # Rate limiter to prevent API overload
163
+ class APIRateLimiter:
164
+ """Rate limiter to prevent API overload"""
165
+ def __init__(self, max_calls_per_minute=60):
166
+ self.max_calls = max_calls_per_minute
167
+ self.calls = []
168
+
169
+ def can_make_call(self):
170
+ """Check if we can make another API call within rate limits"""
171
+ now = time.time()
172
+ # Remove calls older than 1 minute
173
+ self.calls = [call_time for call_time in self.calls if now - call_time < 60]
174
+ can_call = len(self.calls) < self.max_calls
175
+ if not can_call:
176
+ MediLink_ConfigLoader.log("Rate limit reached: {} calls in last minute".format(len(self.calls)), level="WARNING")
177
+ return can_call
178
+
179
+ def record_call(self):
180
+ """Record that an API call was made"""
181
+ self.calls.append(time.time())
182
+
183
+ def wait_if_needed(self):
184
+ """Wait if rate limit would be exceeded"""
185
+ if not self.can_make_call():
186
+ # Wait until oldest call expires
187
+ if self.calls:
188
+ wait_time = 60 - (time.time() - self.calls[0])
189
+ if wait_time > 0:
190
+ MediLink_ConfigLoader.log("Rate limit reached, waiting {:.1f} seconds".format(wait_time), level="INFO")
191
+ time.sleep(wait_time)
192
+
193
+ # Enhanced API client wrapper
194
+ class EnhancedAPIWrapper:
195
+ """Wrapper that adds circuit breaker, caching, and rate limiting to any API client"""
196
+
197
+ def __init__(self, base_client, enable_circuit_breaker=True, enable_caching=True, enable_rate_limiting=True):
198
+ self.base_client = base_client
199
+
200
+ # Initialize enhancement components
201
+ self.circuit_breaker = APICircuitBreaker() if enable_circuit_breaker else None
202
+ self.cache = APICache() if enable_caching else None
203
+ self.rate_limiter = APIRateLimiter() if enable_rate_limiting else None
204
+
205
+ MediLink_ConfigLoader.log("Enhanced API wrapper initialized with circuit_breaker={}, caching={}, rate_limiting={}".format(
206
+ enable_circuit_breaker, enable_caching, enable_rate_limiting), level="INFO")
207
+
208
+ def make_enhanced_call(self, method_name, *args, **kwargs):
209
+ """Make API call with all enhancements applied"""
210
+ # Check cache first
211
+ if self.cache:
212
+ cached_result = self.cache.get(method_name, *args, **kwargs)
213
+ if cached_result is not None:
214
+ return cached_result
215
+
216
+ # Check rate limits
217
+ if self.rate_limiter:
218
+ self.rate_limiter.wait_if_needed()
219
+
220
+ # Get the method from base client
221
+ if not hasattr(self.base_client, method_name):
222
+ raise AttributeError("Base client does not have method: {}".format(method_name))
223
+
224
+ method = getattr(self.base_client, method_name)
225
+
226
+ # Execute with circuit breaker protection
227
+ if self.circuit_breaker:
228
+ result = self.circuit_breaker.call_with_breaker(method, *args, **kwargs)
229
+ else:
230
+ result = method(*args, **kwargs)
231
+
232
+ # Record rate limit call
233
+ if self.rate_limiter:
234
+ self.rate_limiter.record_call()
235
+
236
+ # Cache result
237
+ if self.cache:
238
+ self.cache.set(result, method_name, *args, **kwargs)
239
+
240
+ return result
241
+
242
+ # Utility functions for API enhancement integration
243
+ def create_enhanced_api_client(base_client_class, *args, **kwargs):
244
+ """
245
+ Factory function to create enhanced API client from base client class.
246
+ Returns base client wrapped with enhancements.
247
+ """
248
+ try:
249
+ # Create base client
250
+ base_client = base_client_class(*args, **kwargs)
251
+
252
+ # Wrap with enhancements
253
+ enhanced_client = EnhancedAPIWrapper(base_client)
254
+
255
+ MediLink_ConfigLoader.log("Created enhanced API client from {}".format(base_client_class.__name__), level="INFO")
256
+ return enhanced_client
257
+
258
+ except Exception as e:
259
+ MediLink_ConfigLoader.log("Failed to create enhanced API client: {}".format(str(e)), level="ERROR")
260
+ # Return base client as fallback
261
+ return base_client_class(*args, **kwargs)
262
+
263
+ def safe_api_call(api_function, *args, **kwargs):
264
+ """
265
+ Safe wrapper for any API call with error handling and fallback.
266
+ Returns (result, success_flag, error_message)
267
+ """
268
+ try:
269
+ result = api_function(*args, **kwargs)
270
+ return result, True, None
271
+ except Exception as e:
272
+ error_msg = str(e)
273
+ MediLink_ConfigLoader.log("API call failed: {}".format(error_msg), level="ERROR")
274
+ return None, False, error_msg
275
+
276
+ def validate_api_response(response, required_fields=None):
277
+ """
278
+ Validate API response has required structure and fields.
279
+ Returns True if valid, False otherwise.
280
+ """
281
+ if not response:
282
+ return False
283
+
284
+ if required_fields:
285
+ for field in required_fields:
286
+ if field not in response:
287
+ MediLink_ConfigLoader.log("Missing required field in API response: {}".format(field), level="WARNING")
288
+ return False
289
+
290
+ return True
291
+
292
+ # API health monitoring
293
+ class APIHealthMonitor:
294
+ """Monitor API health and performance metrics"""
295
+
296
+ def __init__(self):
297
+ self.metrics = {
298
+ 'total_calls': 0,
299
+ 'successful_calls': 0,
300
+ 'failed_calls': 0,
301
+ 'cache_hits': 0,
302
+ 'circuit_breaker_trips': 0,
303
+ 'rate_limit_hits': 0,
304
+ 'start_time': time.time()
305
+ }
306
+
307
+ def record_call(self, success=True):
308
+ """Record API call outcome"""
309
+ self.metrics['total_calls'] += 1
310
+ if success:
311
+ self.metrics['successful_calls'] += 1
312
+ else:
313
+ self.metrics['failed_calls'] += 1
314
+
315
+ def record_cache_hit(self):
316
+ """Record cache hit"""
317
+ self.metrics['cache_hits'] += 1
318
+
319
+ def record_circuit_breaker_trip(self):
320
+ """Record circuit breaker trip"""
321
+ self.metrics['circuit_breaker_trips'] += 1
322
+
323
+ def record_rate_limit_hit(self):
324
+ """Record rate limit hit"""
325
+ self.metrics['rate_limit_hits'] += 1
326
+
327
+ def get_health_summary(self):
328
+ """Get health summary statistics"""
329
+ total_calls = self.metrics['total_calls']
330
+ if total_calls == 0:
331
+ return {'status': 'NO_CALLS', 'metrics': self.metrics}
332
+
333
+ success_rate = self.metrics['successful_calls'] / float(total_calls)
334
+ cache_hit_rate = self.metrics['cache_hits'] / float(total_calls)
335
+
336
+ runtime = time.time() - self.metrics['start_time']
337
+ calls_per_minute = (total_calls / runtime) * 60 if runtime > 0 else 0
338
+
339
+ summary = {
340
+ 'status': 'HEALTHY' if success_rate > 0.95 else 'DEGRADED' if success_rate > 0.80 else 'UNHEALTHY',
341
+ 'success_rate': success_rate,
342
+ 'cache_hit_rate': cache_hit_rate,
343
+ 'calls_per_minute': calls_per_minute,
344
+ 'runtime_seconds': runtime,
345
+ 'metrics': self.metrics
346
+ }
347
+
348
+ MediLink_ConfigLoader.log("API Health Summary: {}".format(str(summary)), level="INFO")
349
+ return summary
350
+
351
+ # Global API health monitor instance
352
+ _global_health_monitor = APIHealthMonitor()
353
+
354
+ def get_api_health_monitor():
355
+ """Get global API health monitor instance"""
356
+ return _global_health_monitor