medicafe 0.250728.9__py3-none-any.whl → 0.250805.2__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 (57) 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 +314 -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_837p_cob_library.py +28 -28
  29. MediLink/MediLink_837p_encoder.py +33 -34
  30. MediLink/MediLink_837p_encoder_library.py +226 -150
  31. MediLink/MediLink_837p_utilities.py +129 -5
  32. MediLink/MediLink_API_Generator.py +83 -60
  33. MediLink/MediLink_API_v3.py +1 -1
  34. MediLink/MediLink_ClaimStatus.py +177 -31
  35. MediLink/MediLink_DataMgmt.py +378 -63
  36. MediLink/MediLink_Decoder.py +20 -1
  37. MediLink/MediLink_Deductible.py +155 -28
  38. MediLink/MediLink_Display_Utils.py +72 -0
  39. MediLink/MediLink_Down.py +127 -5
  40. MediLink/MediLink_Gmail.py +720 -653
  41. MediLink/MediLink_PatientProcessor.py +257 -0
  42. MediLink/MediLink_UI.py +85 -71
  43. MediLink/MediLink_Up.py +28 -4
  44. MediLink/MediLink_insurance_utils.py +227 -230
  45. MediLink/MediLink_main.py +248 -0
  46. MediLink/MediLink_smart_import.py +264 -0
  47. MediLink/__init__.py +93 -1
  48. MediLink/insurance_type_integration_test.py +13 -3
  49. MediLink/test.py +1 -1
  50. MediLink/test_timing.py +59 -0
  51. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/METADATA +1 -1
  52. medicafe-0.250805.2.dist-info/RECORD +81 -0
  53. medicafe-0.250805.2.dist-info/entry_points.txt +2 -0
  54. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/top_level.txt +1 -0
  55. medicafe-0.250728.9.dist-info/RECORD +0 -59
  56. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/LICENSE +0 -0
  57. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/WHEEL +0 -0
@@ -11,6 +11,8 @@ Functions included:
11
11
  - File/path handling utilities
12
12
  - Processing utilities
13
13
  - Validation utilities
14
+ - Insurance matching utilities
15
+ - EDI segment utilities
14
16
 
15
17
  Import Strategy:
16
18
  This module only imports base Python modules and MediLink_ConfigLoader to avoid
@@ -21,12 +23,11 @@ from datetime import datetime
21
23
  import sys
22
24
  import os
23
25
  import re
26
+ import difflib
24
27
 
25
28
  # Import MediLink_ConfigLoader for logging functionality
26
- try:
27
- from MediLink import MediLink_ConfigLoader
28
- except ImportError:
29
- import MediLink_ConfigLoader
29
+ from MediCafe.core_utils import get_shared_config_loader
30
+ MediLink_ConfigLoader = get_shared_config_loader()
30
31
 
31
32
  # =============================================================================
32
33
  # DATE/TIME UTILITIES
@@ -246,6 +247,126 @@ def handle_validation_errors(transaction_set_control_number, validation_errors,
246
247
  MediLink_ConfigLoader.log("HALT: Processing halted at transaction set {} due to unresolved validation errors.".format(transaction_set_control_number), config, level="ERROR")
247
248
  sys.exit() # Optionally halt further processing
248
249
 
250
+ # =============================================================================
251
+ # INSURANCE MATCHING UTILITIES
252
+ # =============================================================================
253
+
254
+ def find_closest_insurance_matches(insurance_name, insurance_to_id, max_matches=3):
255
+ """
256
+ Find the closest matches for an insurance name in the MAINS data.
257
+
258
+ Args:
259
+ insurance_name (str): The insurance name to find matches for
260
+ insurance_to_id (dict): Dictionary mapping insurance names to IDs from MAINS
261
+ max_matches (int): Maximum number of matches to return
262
+
263
+ Returns:
264
+ list: List of tuples (insurance_name, similarity_score) sorted by similarity
265
+ """
266
+ if not insurance_to_id:
267
+ return []
268
+
269
+ # Normalize the search term
270
+ normalized_search = insurance_name.upper().strip()
271
+
272
+ # Calculate similarity scores for all insurance names
273
+ matches = []
274
+ for mains_insurance_name in insurance_to_id.keys():
275
+ normalized_mains = mains_insurance_name.upper().strip()
276
+
277
+ # Use difflib for similarity scoring
278
+ similarity = difflib.SequenceMatcher(None, normalized_search, normalized_mains).ratio()
279
+
280
+ # Boost score for partial matches
281
+ if normalized_search in normalized_mains or normalized_mains in normalized_search:
282
+ similarity += 0.2
283
+
284
+ # Boost score for word matches
285
+ search_words = set(normalized_search.split())
286
+ mains_words = set(normalized_mains.split())
287
+ word_overlap = len(search_words.intersection(mains_words))
288
+ if word_overlap > 0:
289
+ similarity += 0.1 * word_overlap
290
+
291
+ matches.append((mains_insurance_name, similarity))
292
+
293
+ # Sort by similarity score (highest first) and return top matches
294
+ matches.sort(key=lambda x: x[1], reverse=True)
295
+ return matches[:max_matches]
296
+
297
+ def prompt_for_insurance_selection(insurance_name, closest_matches, config):
298
+ """
299
+ Prompt the user to select from the closest matches or choose manual intervention.
300
+
301
+ Args:
302
+ insurance_name (str): The original insurance name that wasn't found
303
+ closest_matches (list): List of tuples (insurance_name, similarity_score)
304
+ config (dict): Configuration object
305
+
306
+ Returns:
307
+ str or None: Selected insurance name or None for manual intervention
308
+ """
309
+ print("\n" + "="*60)
310
+ print("INSURANCE NAME NOT FOUND IN MAINS")
311
+ print("="*60)
312
+ print("Original insurance name: '{}'".format(insurance_name))
313
+ print("\nClosest matches found in MAINS:")
314
+ print("-" * 40)
315
+
316
+ for i, (match_name, similarity) in enumerate(closest_matches, 1):
317
+ print("{}. {} (similarity: {:.1%})".format(i, match_name, similarity))
318
+
319
+ print("\n{}. None of these - I need to fix this manually".format(len(closest_matches) + 1))
320
+ print("-" * 40)
321
+
322
+ while True:
323
+ try:
324
+ choice = input("\nPlease select an option (1-{}): ".format(len(closest_matches) + 1))
325
+ choice_num = int(choice)
326
+
327
+ if 1 <= choice_num <= len(closest_matches):
328
+ selected_name = closest_matches[choice_num - 1][0]
329
+ print("\nSelected: '{}'".format(selected_name))
330
+ MediLink_ConfigLoader.log("User selected insurance name '{}' for original '{}'".format(selected_name, insurance_name), config, level="INFO")
331
+ return selected_name
332
+ elif choice_num == len(closest_matches) + 1:
333
+ print("\n" + "="*60)
334
+ print("MANUAL INTERVENTION REQUIRED")
335
+ print("="*60)
336
+ print("To resolve this issue, you may need to:")
337
+ print("1. Check if the insurance name is spelled correctly in your source data")
338
+ print("2. Verify the insurance exists in MAINS with a different name")
339
+ print("3. Add the insurance to MAINS if it's missing")
340
+ print("4. Update the crosswalk with the correct payer ID mapping")
341
+ print("\nThe process will continue with other claims, but this claim will be skipped.")
342
+ print("="*60)
343
+ MediLink_ConfigLoader.log("User chose manual intervention for insurance name '{}'".format(insurance_name), config, level="WARNING")
344
+ return None
345
+ else:
346
+ print("Invalid choice. Please enter a number between 1 and {}.".format(len(closest_matches) + 1))
347
+ except ValueError:
348
+ print("Invalid input. Please enter a number.")
349
+ except KeyboardInterrupt:
350
+ print("\n\nProcess interrupted by user.")
351
+ return None
352
+
353
+ # =============================================================================
354
+ # EDI SEGMENT UTILITIES
355
+ # =============================================================================
356
+
357
+ def build_nm1_segment(payer_name, payer_id):
358
+ """
359
+ Build NM1 segment using payer name and ID.
360
+
361
+ Args:
362
+ payer_name (str): Name of the payer
363
+ payer_id (str): Payer ID
364
+
365
+ Returns:
366
+ str: Formatted NM1 segment
367
+ """
368
+ return "NM1*PR*2*{}*****PI*{}~".format(payer_name, payer_id)
369
+
249
370
  # =============================================================================
250
371
  # UTILITY FUNCTION REGISTRY
251
372
  # =============================================================================
@@ -260,5 +381,8 @@ __all__ = [
260
381
  'winscp_validate_output_directory',
261
382
  'get_output_directory',
262
383
  'generate_segment_counts',
263
- 'handle_validation_errors'
384
+ 'handle_validation_errors',
385
+ 'find_closest_insurance_matches',
386
+ 'prompt_for_insurance_selection',
387
+ 'build_nm1_segment'
264
388
  ]
@@ -1,29 +1,47 @@
1
1
  # This script requires Python 3.11 and is to be used for intalling new API clients.
2
2
  import os, time, subprocess, shutil, tempfile, shlex, re
3
3
  from pathlib import Path
4
- from watchdog.observers import Observer
5
- from watchdog.events import FileSystemEventHandler
6
- from MediLink_API_v3 import ConfigLoader
7
4
 
8
- class SwaggerHandler(FileSystemEventHandler):
5
+ # Add workspace directory to Python path for MediCafe imports
6
+ current_dir = os.path.dirname(os.path.abspath(__file__))
7
+ workspace_dir = os.path.dirname(current_dir)
8
+ if workspace_dir not in sys.path:
9
+ sys.path.insert(0, workspace_dir)
10
+
11
+ # Safe import for watchdog with fallback
12
+ try:
13
+ from watchdog.observers import Observer
14
+ from watchdog.events import FileSystemEventHandler
15
+ WATCHDOG_AVAILABLE = True
16
+ except ImportError:
17
+ Observer = None
18
+ FileSystemEventHandler = None
19
+ WATCHDOG_AVAILABLE = False
20
+ print("Warning: watchdog module not available. File monitoring functionality will be limited.")
21
+
22
+ from MediCafe.api_core import ConfigLoader
23
+
24
+ class SwaggerHandler(FileSystemEventHandler if WATCHDOG_AVAILABLE else object):
9
25
  def __init__(self, json_folder):
26
+ if WATCHDOG_AVAILABLE:
27
+ super().__init__()
10
28
  self.json_folder = json_folder
11
29
 
12
30
  def on_created(self, event):
13
31
  if event.is_directory:
14
32
  return
15
33
  if event.src_path.endswith('.yaml') or event.src_path.endswith('.json'):
16
- print(f"New file detected: {event.src_path}")
34
+ print("New file detected: {}".format(event.src_path))
17
35
  time.sleep(2) # Add a short delay to ensure the file is ready for reading
18
36
  self.process_swagger_file(event.src_path)
19
37
 
20
38
  def process_swagger_file(self, file_path):
21
- print(f"Processing file: {file_path}")
39
+ print("Processing file: {}".format(file_path))
22
40
  # Sanitize filename to replace spaces with underscores
23
41
  sanitized_file_path = os.path.join(os.path.dirname(file_path), sanitize_filename(os.path.basename(file_path)))
24
42
  if sanitized_file_path != file_path:
25
43
  os.rename(file_path, sanitized_file_path)
26
- print(f"Renamed file to: {sanitized_file_path}")
44
+ print("Renamed file to: {}".format(sanitized_file_path))
27
45
  file_path = sanitized_file_path
28
46
 
29
47
  with tempfile.TemporaryDirectory() as temp_dir:
@@ -35,17 +53,17 @@ class SwaggerHandler(FileSystemEventHandler):
35
53
  shutil.copy(file_path, temp_file_path)
36
54
  config_source_path = Path(__file__).parent.parent / 'json' / 'openapi_config.json'
37
55
  shutil.copy(config_source_path, config_path)
38
- print(f"Copied files to: {temp_file_path} and {config_path}")
56
+ print("Copied files to: {} and {}".format(temp_file_path, config_path))
39
57
 
40
58
  swagger_definitions = ConfigLoader.load_swagger_file(str(temp_file_path))
41
59
  if swagger_definitions:
42
- print(f"Swagger definitions loaded successfully from: {temp_file_path}")
60
+ print("Swagger definitions loaded successfully from: {}".format(temp_file_path))
43
61
  if generate_api_client(temp_file_path, output_dir, config_path):
44
62
  backport_code(output_dir)
45
63
  move_generated_client(temp_dir, file_path)
46
64
  provide_instructions(file_path)
47
65
  else:
48
- print(f"Failed to load Swagger definitions from: {temp_file_path}")
66
+ print("Failed to load Swagger definitions from: {}".format(temp_file_path))
49
67
 
50
68
  def sanitize_filename(filename):
51
69
  return filename.replace(' ', '_')
@@ -76,7 +94,7 @@ def generate_api_client(swagger_path, output_path, config_path):
76
94
  '-c', str(config_path)
77
95
  ]
78
96
 
79
- print(f"Attempting command: {' '.join(command)}")
97
+ print("Attempting command: {}".format(' '.join(command)))
80
98
 
81
99
  try:
82
100
  result = subprocess.run(command, check=True, capture_output=True, text=True)
@@ -112,7 +130,7 @@ def generate_api_client(swagger_path, output_path, config_path):
112
130
  '-o', shlex.quote(str(output_path)),
113
131
  '-c', shlex.quote(str(config_path))
114
132
  ]
115
- print(f"Attempting quoted command: {' '.join(quoted_command)}")
133
+ print("Attempting quoted command: {}".format(' '.join(quoted_command)))
116
134
  result = subprocess.run(' '.join(quoted_command), check=True, shell=True, capture_output=True, text=True)
117
135
  print("API client generated successfully with quoted command.")
118
136
  print(result.stdout)
@@ -126,10 +144,10 @@ def generate_api_client(swagger_path, output_path, config_path):
126
144
 
127
145
  try:
128
146
  print("Attempting with batch script.")
129
- batch_script_content = f"""
130
- @echo off
131
- {openapi_generator_path} generate -i "{swagger_path}" -g python -o "{output_path}" -c "{config_path}"
132
- """
147
+ batch_script_content = """
148
+ @echo off
149
+ {} generate -i "{}" -g python -o "{}" -c "{}"
150
+ """.format(openapi_generator_path, swagger_path, output_path, config_path)
133
151
  batch_script_path = Path(tempfile.gettempdir()) / "generate_client.bat"
134
152
  with open(batch_script_path, 'w') as batch_script:
135
153
  batch_script.write(batch_script_content)
@@ -156,8 +174,8 @@ def backport_code(output_dir):
156
174
  with open(file_path, 'r') as f:
157
175
  code = f.read()
158
176
 
159
- code = re.sub(r'f"(.*?)"', r'"\1".format', code) # Replace f-strings
160
- code = re.sub(r'async def', 'def', code) # Remove async syntax
177
+ code = re.sub(r'"(.*?)".format()', r'"\1".format', code) # Replace f-strings
178
+ code = re.sub(r'async de', 'def', code) # Remove async syntax
161
179
  code = re.sub(r'await ', '', code) # Remove await syntax
162
180
  code = re.sub(r':\s*\w+ =', '=', code) # Remove type hints in variable assignments
163
181
  code = re.sub(r'def (\w+)\(.*?\) -> .*?:', r'def \1(', code) # Remove type hints in function definitions
@@ -166,7 +184,7 @@ def backport_code(output_dir):
166
184
 
167
185
  with open(file_path, 'w') as f:
168
186
  f.write(code)
169
- print(f"Backported {file_path}")
187
+ print("Backported {}".format(file_path))
170
188
 
171
189
  def move_generated_client(temp_dir, original_file_path):
172
190
  api_name = Path(original_file_path).stem
@@ -175,60 +193,65 @@ def move_generated_client(temp_dir, original_file_path):
175
193
  shutil.rmtree(destination_dir)
176
194
  try:
177
195
  shutil.move(str(Path(temp_dir) / 'generated_client'), str(destination_dir))
178
- print(f"Moved generated client to {destination_dir}")
196
+ print("Moved generated client to {}".format(destination_dir))
179
197
  except FileNotFoundError as e:
180
- print(f"Error moving generated client: {e}")
198
+ print("Error moving generated client: {}".format(e))
181
199
  except Exception as e:
182
- print(f"Unexpected error moving generated client: {e}")
200
+ print("Unexpected error moving generated client: {}".format(e))
183
201
 
184
202
  def provide_instructions(swagger_path):
185
203
  api_name = Path(swagger_path).stem
186
- instructions = f"""
187
- API Client for {api_name} has been generated successfully.
188
-
189
- Integration Steps:
190
- 1. Locate the generated client code in the 'generated_clients/{api_name}' directory.
191
- 2. Integrate the generated client code into your project by following these steps:
192
- a. Copy the generated client directory to your project's client directory.
193
- b. Import the client classes in your project as needed.
194
- c. Ensure that the generated client adheres to the BaseAPIClient interface if integrating with the existing system.
195
- 3. Update your configuration to include the new API endpoint details.
196
- 4. Test the integration thoroughly to ensure compatibility and functionality.
197
-
198
- Example Integration:
199
- from clients.generated.{api_name}.api_client import ApiClient as {api_name}Client
200
-
201
- class New{api_name}APIClient(BaseAPIClient):
202
- def __init__(self, config):
203
- super().__init__(config)
204
- self.generated_client = {api_name}Client()
205
-
206
- def get_access_token(self, endpoint_name):
207
- # Implement token retrieval logic
208
- pass
209
-
210
- def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None):
211
- # Use the generated client to make the API call
212
- if call_type == 'GET':
213
- response = self.generated_client.call_api(url_extension, 'GET', params=params, header_params=headers)
214
- elif call_type == 'POST':
215
- response = self.generated_client.call_api(url_extension, 'POST', body=data, header_params=headers)
216
- elif call_type == 'DELETE':
217
- response = self.generated_client.call_api(url_extension, 'DELETE', header_params=headers)
218
- else:
219
- raise ValueError("Unsupported call type")
220
- return response
221
-
222
- """
204
+ instructions = """
205
+ API Client for {} has been generated successfully.
206
+
207
+ Integration Steps:
208
+ 1. Locate the generated client code in the 'generated_clients/{}' directory.
209
+ 2. Integrate the generated client code into your project by following these steps:
210
+ a. Copy the generated client directory to your project's client directory.
211
+ b. Import the client classes in your project as needed.
212
+ c. Ensure that the generated client adheres to the BaseAPIClient interface if integrating with the existing system.
213
+ 3. Update your configuration to include the new API endpoint details.
214
+ 4. Test the integration thoroughly to ensure compatibility and functionality.
215
+
216
+ Example Integration:
217
+ from clients.generated.{}.api_client import ApiClient as {}Client
218
+
219
+ class New{}APIClient(BaseAPIClient):
220
+ def __init__(self, config):
221
+ super().__init__(config)
222
+ self.generated_client = {}Client()
223
+
224
+ def get_access_token(self, endpoint_name):
225
+ # Implement token retrieval logic
226
+ pass
227
+
228
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None):
229
+ # Use the generated client to make the API call
230
+ if call_type == 'GET':
231
+ response = self.generated_client.call_api(url_extension, 'GET', params=params, header_params=headers)
232
+ elif call_type == 'POST':
233
+ response = self.generated_client.call_api(url_extension, 'POST', body=data, header_params=headers)
234
+ elif call_type == 'DELETE':
235
+ response = self.generated_client.call_api(url_extension, 'DELETE', header_params=headers)
236
+ else:
237
+ raise ValueError("Unsupported call type")
238
+ return response
239
+
240
+ """.format(api_name, api_name, api_name, api_name, api_name, api_name)
223
241
  print(instructions)
224
242
 
225
243
  def main():
226
244
  json_folder = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'json'))
245
+ if not WATCHDOG_AVAILABLE:
246
+ print("Watchdog is not available. Skipping file monitoring.")
247
+ print("Please ensure watchdog is installed and in your PATH.")
248
+ return
249
+
227
250
  event_handler = SwaggerHandler(json_folder)
228
251
  observer = Observer()
229
252
  observer.schedule(event_handler, path=json_folder, recursive=False)
230
253
  observer.start()
231
- print(f"Monitoring folder: {json_folder}")
254
+ print("Monitoring folder: {}".format(json_folder))
232
255
  try:
233
256
  while True:
234
257
  time.sleep(1)
@@ -1,4 +1,4 @@
1
- # MediLink_API_v3.py
1
+ # api_core.py
2
2
  import time, requests, yaml, json, os, traceback
3
3
 
4
4
  try: