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,13 +1,11 @@
1
+ # MediLink_837p_encoder_library.py
1
2
  from datetime import datetime
2
- import sys
3
- from MediLink import MediLink_ConfigLoader, MediLink_UI
4
-
5
- # Add parent directory of the project to the Python path
6
- import sys
7
- import os
3
+ import sys, os
4
+ from MediLink import MediLink_ConfigLoader
8
5
 
9
6
  project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
10
- sys.path.append(project_dir)
7
+ if project_dir not in sys.path:
8
+ sys.path.append(project_dir)
11
9
 
12
10
  from MediBot import MediBot_Preprocessor_lib
13
11
  load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
@@ -154,9 +152,10 @@ def create_1000A_submitter_name_segment(patient_data, config, endpoint):
154
152
  # Submitter contact details
155
153
  contact_name = config.get('submitter_name', 'NONE')
156
154
  contact_telephone_number = config.get('submitter_tel', 'NONE')
155
+ entity_type_qualifier = '2' # if contact_name else '1' BUG - 1 if submitter_first_name is not empty.
157
156
 
158
157
  # Construct NM1 segment for the submitter
159
- nm1_segment = "NM1*41*2*{}*****{}*{}~".format(submitter_name, submitter_id_qualifier, submitter_id)
158
+ nm1_segment = "NM1*41*{}*{}*****{}*{}~".format(entity_type_qualifier, submitter_name, submitter_id_qualifier, submitter_id)
160
159
 
161
160
  # Construct PER segment for the submitter's contact information
162
161
  per_segment = "PER*IC*{}*TE*{}~".format(contact_name, contact_telephone_number)
@@ -182,7 +181,7 @@ def create_1000B_receiver_name_segment(config, endpoint):
182
181
  receiver_edi=receiver_edi
183
182
  )
184
183
 
185
- def payer_id_to_payer_name(parsed_data, config, endpoint):
184
+ def payer_id_to_payer_name(parsed_data, config, endpoint, crosswalk, client):
186
185
  """
187
186
  Preprocesses payer information from parsed data and enriches parsed_data with the payer name and ID.
188
187
 
@@ -198,7 +197,7 @@ def payer_id_to_payer_name(parsed_data, config, endpoint):
198
197
  insurance_name = parsed_data.get('INAME', '')
199
198
 
200
199
  # Step 2: Map insurance name to payer ID
201
- payer_id = map_insurance_name_to_payer_id(insurance_name, config)
200
+ payer_id = map_insurance_name_to_payer_id(insurance_name, config, client)
202
201
 
203
202
  # Step 3: Validate payer_id
204
203
  if payer_id is None:
@@ -207,7 +206,7 @@ def payer_id_to_payer_name(parsed_data, config, endpoint):
207
206
  raise ValueError(error_message)
208
207
 
209
208
  # Step 4: Resolve payer name using payer ID
210
- payer_name = resolve_payer_name(payer_id, config, endpoint, insurance_name, parsed_data)
209
+ payer_name = resolve_payer_name(payer_id, config, endpoint, insurance_name, parsed_data, crosswalk, client)
211
210
 
212
211
  # Enrich parsed_data with payer name and payer ID
213
212
  parsed_data['payer_name'] = payer_name
@@ -238,74 +237,141 @@ def create_2010BB_payer_information_segment(parsed_data):
238
237
  # Build NM1 segment using provided payer name and payer ID
239
238
  return build_nm1_segment(payer_name, payer_id)
240
239
 
241
- def resolve_payer_name(payer_id, config, primary_endpoint, insurance_name, parsed_data):
242
- """
243
- Resolves the payer name using the provided payer ID.
244
-
245
- Args:
246
- payer_id (str): The ID of the payer.
247
- config (dict): Configuration settings.
248
- primary_endpoint (str): The primary endpoint for resolving payer information.
249
- insurance_name (str): The name of the insurance.
250
- parsed_data (dict): Parsed data containing patient information.
251
-
252
- Returns:
253
- str: The resolved payer name.
254
- """
240
+ def get_user_confirmation(prompt_message):
241
+ while True:
242
+ response = input(prompt_message).strip().lower()
243
+ if response in ['yes', 'y']:
244
+ return True
245
+ elif response in ['no', 'n']:
246
+ return False
247
+ else:
248
+ print("Please respond with 'yes' or 'no'.")
255
249
 
250
+ def resolve_payer_name(payer_id, config, primary_endpoint, insurance_name, parsed_data, crosswalk, client):
251
+ # Check if the payer_id is in the crosswalk with a name already attached to it.
252
+ if payer_id in crosswalk.get('payer_id', {}):
253
+ payer_info = crosswalk['payer_id'][payer_id]
254
+ MediLink_ConfigLoader.log("Payer ID {} found in crosswalk with name: {}".format(payer_id, payer_info['name']), level="DEBUG")
255
+ return payer_info['name'] # Return the name from the crosswalk directly.
256
+
256
257
  # Step 1: Attempt to fetch payer name from API using primary endpoint
257
258
  MediLink_ConfigLoader.log("Attempting to resolve Payer ID {} via API.".format(payer_id), level="INFO")
258
259
  try:
259
- return fetch_payer_name_from_api(payer_id, config, primary_endpoint)
260
+ return fetch_payer_name_from_api(client, payer_id, config, primary_endpoint)
260
261
  except Exception as api_error:
261
262
  # Step 2: Log API resolution failure and initiate user intervention
262
263
  MediLink_ConfigLoader.log("API resolution failed for {}: {}. Initiating user intervention.".format(payer_id, str(api_error)), config, level="WARNING")
263
264
 
264
265
  # Step 3: Print warning message for user intervention
265
266
  print("\n\nWARNING: Unable to verify Payer ID '{}' for patient '{}'!".format(payer_id, parsed_data.get('CHART', 'unknown')))
266
- print(" Claims for '{}' may be incorrectly\nrouted or fail without intervention.".format(insurance_name))
267
- print("\nACTION REQUIRED: Please verify the internet connection and the Payer ID by searching for it")
268
- print("at the expected endpoint's website or using Google.")
267
+ print(" Claims for '{}' may be incorrectly routed or fail without intervention.".format(insurance_name))
268
+ print("\nACTION REQUIRED: Please verify the internet connection and the Payer ID by searching for it at the expected endpoint's website or using Google.")
269
269
  print("\nNote: If the Payer ID '{}' is incorrect for '{}', \nit may need to be manually corrected.".format(payer_id, insurance_name))
270
270
  print("If the Payer ID appears correct, you may skip the correction and force-continue with this one.")
271
271
  print("\nPlease check the Payer ID in the Crosswalk and the initial \ndata source (e.g., Carol's CSV) as needed.")
272
272
  print("If unsure, llamar a Dani for guidance on manual corrections.")
273
-
273
+
274
274
  # Step 4: Integrate user input logic
275
- user_decision = input("\nType 'FORCE' to force-continue with the Medisoft name, \nor press Enter to pause processing and make corrections: ")
276
- user_decision = user_decision.strip().lower() # Convert to lowercase and remove leading/trailing whitespace
275
+ user_decision = input("\nType 'FORCE' to force-continue with the Medisoft name, or press Enter to pause processing and make corrections: ").strip().lower()
276
+
277
277
  if user_decision == 'force':
278
278
  # Step 5: Fallback to truncated insurance name
279
279
  truncated_name = insurance_name[:10] # Temporary fallback
280
280
  MediLink_ConfigLoader.log("Using truncated insurance name '{}' as a fallback for {}".format(truncated_name, payer_id), config, level="WARNING")
281
281
  return truncated_name
282
- elif not user_decision: # Check if user pressed Enter
282
+ elif not user_decision:
283
+ # Step 6: Prompt user for corrected payer ID
283
284
  corrected_payer_id = prompt_user_for_payer_id(insurance_name)
284
285
  if corrected_payer_id:
285
286
  try:
286
- resolved_name = fetch_payer_name_from_api(corrected_payer_id, config, primary_endpoint)
287
+ resolved_name = fetch_payer_name_from_api(client, corrected_payer_id, config, primary_endpoint)
287
288
  print("API resolved to insurance name: {}".format(resolved_name))
288
289
  MediLink_ConfigLoader.log("API Resolved to standard insurance name: {} for corrected payer ID: {}".format(resolved_name, corrected_payer_id), config, level="INFO")
289
-
290
- confirmation = input("Is the standard insurance name '{}' correct? (yes/no): ".format(resolved_name)).strip().lower()
291
- # BUG There is duplication of code here.
292
- if confirmation in ['yes', 'y']:
293
- if MediBot_Crosswalk_Library.update_crosswalk_with_corrected_payer_id(payer_id, corrected_payer_id):
290
+
291
+ # Step 7: Ask for user confirmation using the helper
292
+ confirmation_prompt = "Is the standard insurance name '{}' correct? (yes/no): ".format(resolved_name)
293
+ if get_user_confirmation(confirmation_prompt):
294
+ # Step 8: Load crosswalk
295
+ try:
296
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
297
+ except Exception as e:
298
+ print("Failed to load configuration and crosswalk: {}".format(e))
299
+ MediLink_ConfigLoader.log("Failed to load configuration and crosswalk: {}".format(e), config, level="ERROR")
300
+ exit(1)
301
+
302
+ # Step 9: Update the crosswalk with the corrected Payer ID
303
+ if MediBot_Crosswalk_Library.update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk):
294
304
  return resolved_name
295
305
  else:
296
306
  print("Failed to update crosswalk with the corrected Payer ID.")
297
- exit(1) # probably needs a different failure direction here.
307
+ MediLink_ConfigLoader.log("Failed to update crosswalk with the corrected Payer ID.", config, level="ERROR")
308
+ exit(1) # Consider handling failure differently.
298
309
  else:
310
+ # Step 10: Handle rejection
299
311
  print("User did not confirm the standard insurance name. Manual intervention is required.")
300
312
  MediLink_ConfigLoader.log("User did not confirm the standard insurance name. Manual intervention is required.", config, level="CRITICAL")
301
- exit(1) # probably needs a different failure direction here.
313
+ exit(1) # Consider handling differently.
302
314
  except Exception as e:
315
+ # Step 11: Handle exceptions during resolution
303
316
  print("Failed to resolve corrected payer ID to standard insurance name: {}".format(e))
304
317
  MediLink_ConfigLoader.log("Failed to resolve corrected payer ID to standard insurance name: {}".format(e), config, level="ERROR")
305
- exit(1) # probably needs a different failure direction here.
318
+ exit(1) # Consider handling differently.
306
319
  else:
320
+ # Step 12: Handle absence of corrected payer ID
307
321
  print("Exiting script. Please make the necessary corrections and retry.")
308
- exit(1) # probably needs a different failure direction here.
322
+ MediLink_ConfigLoader.log("Exiting script due to absence of corrected Payer ID.", config, level="CRITICAL")
323
+ exit(1) # Consider handling differently.
324
+ else:
325
+ # Optional: Handle unexpected user input
326
+ print("Invalid input. Manual intervention is required.")
327
+ MediLink_ConfigLoader.log("Invalid user input during payer name resolution.", config, level="CRITICAL")
328
+ exit(1) # Consider handling differently.
329
+
330
+ def handle_missing_payer_id(insurance_name, config, crosswalk, client):
331
+ # Step 1: Inform about the missing Payer ID
332
+ print("Missing Payer ID for insurance name: {}".format(insurance_name))
333
+ MediLink_ConfigLoader.log("Missing Payer ID for insurance name: {}".format(insurance_name), config, level="WARNING")
334
+
335
+ # Step 2: Prompt the user for manual payer ID input
336
+ payer_id = prompt_user_for_payer_id(insurance_name)
337
+
338
+ if not payer_id:
339
+ # Step 3: Handle absence of payer ID input
340
+ message = "Unable to resolve missing Payer ID. Manual intervention is required."
341
+ MediLink_ConfigLoader.log(message, config, level="CRITICAL")
342
+ print(message)
343
+ return None
344
+
345
+ # Step 4: Resolve the payer ID to a standard insurance name via API
346
+ try:
347
+ # primary_endpoint=None should kick to the default in the API function.
348
+ standard_insurance_name = resolve_payer_name(payer_id, config, primary_endpoint=None, insurance_name=insurance_name, parsed_data={}, crosswalk=crosswalk, client=client)
349
+ message = "Resolved to standard insurance name: {} for payer ID: {}".format(standard_insurance_name, payer_id)
350
+ print(message)
351
+ MediLink_ConfigLoader.log(message, config, level="INFO")
352
+ except Exception as e:
353
+ # Step 5: Handle exceptions during resolution
354
+ message = "Failed to resolve payer ID to standard insurance name: {}".format(e)
355
+ print(message)
356
+ MediLink_ConfigLoader.log(message, config, level="ERROR")
357
+ return None
358
+
359
+ # Step 6: Ask for user confirmation
360
+ confirmation_prompt = "Is the standard insurance name '{}' correct? (yes/no): ".format(standard_insurance_name)
361
+ if get_user_confirmation(confirmation_prompt):
362
+ # Step 7: Update the crosswalk with the new payer ID and insurance name mapping
363
+ try:
364
+ MediBot_Crosswalk_Library.update_crosswalk_with_new_payer_id(insurance_name, payer_id, config)
365
+ return payer_id
366
+ except Exception as e:
367
+ print("Failed to update crosswalk with new Payer ID: {}".format(e))
368
+ MediLink_ConfigLoader.log("Failed to update crosswalk with new Payer ID: {}".format(e), config, level="ERROR")
369
+ return None
370
+ else:
371
+ # Step 8: Handle rejection
372
+ print("User did not confirm the standard insurance name. Manual intervention is required.")
373
+ MediLink_ConfigLoader.log("User did not confirm the standard insurance name. Manual intervention is required.", config, level="CRITICAL")
374
+ return None
309
375
 
310
376
  def prompt_user_for_payer_id(insurance_name):
311
377
  """
@@ -324,7 +390,7 @@ def build_nm1_segment(payer_name, payer_id):
324
390
  # Step 1: Build NM1 segment using payer name and ID
325
391
  return "NM1*PR*2*{}*****PI*{}~".format(payer_name, payer_id)
326
392
 
327
- def map_insurance_name_to_payer_id(insurance_name, config):
393
+ def map_insurance_name_to_payer_id(insurance_name, config, client):
328
394
  """
329
395
  Maps insurance name to payer ID using the crosswalk configuration.
330
396
 
@@ -335,13 +401,15 @@ def map_insurance_name_to_payer_id(insurance_name, config):
335
401
  Returns:
336
402
  str: The payer ID corresponding to the insurance name.
337
403
  """
338
- try:
339
- # Ensure crosswalk is initialized and 'payer_id' key is available
340
- MediBot_Crosswalk_Library.check_and_initialize_crosswalk(config)
341
-
342
- # Load crosswalk configuration
404
+ try:
405
+ # Load crosswalk configuration // BUG This should already be passed in before we get here.
343
406
  _, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
344
407
 
408
+ # Ensure crosswalk is initialized and 'payer_id' key is available
409
+ if 'payer_id' not in crosswalk:
410
+ print("Crosswalk 'payer_id' not found. Please run MediBot_Preprocessor.py with the --update-crosswalk argument.")
411
+ exit() # BUG Halting the script execution here for now, should be handled in the preprocessor script.
412
+
345
413
  # Load insurance data from MAINS to get insurance ID
346
414
  insurance_to_id = load_insurance_data_from_mains(config)
347
415
 
@@ -350,6 +418,7 @@ def map_insurance_name_to_payer_id(insurance_name, config):
350
418
  if medisoft_id is None:
351
419
  error_message = "No Medisoft ID found for insurance name: {}. Consider checking MAINS directly.".format(insurance_name)
352
420
  MediLink_ConfigLoader.log(error_message, config, level="ERROR")
421
+ # Asking for payer ID here is not the right approach.
353
422
  raise ValueError(error_message)
354
423
 
355
424
  # Convert medisoft_id to string to match the JSON data type
@@ -367,7 +436,7 @@ def map_insurance_name_to_payer_id(insurance_name, config):
367
436
  error_message = "No payer ID found for Medisoft ID: {}".format(medisoft_id)
368
437
  MediLink_ConfigLoader.log(error_message, config, level="ERROR")
369
438
  print(error_message)
370
- payer_id = handle_missing_payer_id(insurance_name, config)
439
+ payer_id = handle_missing_payer_id(insurance_name, config, crosswalk, client)
371
440
 
372
441
  return payer_id
373
442
 
@@ -381,47 +450,6 @@ def map_insurance_name_to_payer_id(insurance_name, config):
381
450
  MediLink_ConfigLoader.log(error_message, config, level="ERROR")
382
451
  raise e
383
452
 
384
- def handle_missing_payer_id(insurance_name, config):
385
- """
386
- Handles cases where the payer ID is not found for a given Medisoft ID.
387
-
388
- """
389
- print("Missing Payer ID for insurance name: {}".format(insurance_name))
390
- MediLink_ConfigLoader.log("Missing Payer ID for insurance name: {}".format(insurance_name), config, level="WARNING")
391
-
392
- # Prompt the user for manual intervention to input the payer ID
393
- payer_id = prompt_user_for_payer_id(insurance_name)
394
-
395
- if not payer_id:
396
- message = "Unable to resolve missing Payer ID. Manual intervention is required."
397
- MediLink_ConfigLoader.log(message, config, level="CRITICAL")
398
- return None
399
-
400
- # Resolve the payer ID to a standard insurance name via API
401
- try:
402
- # primary_endpoint=None should kick to the default in the api function.
403
- standard_insurance_name = resolve_payer_name(payer_id, config, primary_endpoint=None, insurance_name=insurance_name, parsed_data={})
404
- message = "Resolved to standard insurance name: {} for payer ID: {}".format(standard_insurance_name, payer_id)
405
- print(message)
406
- MediLink_ConfigLoader.log(message, config, level="INFO")
407
- except Exception as e:
408
- message = "Failed to resolve payer ID to standard insurance name: {}".format(e)
409
- print(message)
410
- MediLink_ConfigLoader.log(message, config, level="ERROR")
411
- return None
412
-
413
- # Ask for user confirmation
414
- confirmation = input("Is the standard insurance name '{}' correct? (yes/no): ".format(standard_insurance_name)).strip().lower() or 'yes'
415
- # BUG There is duplication of code here.
416
- if confirmation in ['yes', 'y']:
417
- # Update the crosswalk with the new payer ID and insurance name mapping
418
- MediBot_Crosswalk_Library.update_crosswalk_with_new_payer_id(insurance_name, payer_id, config)
419
- return payer_id
420
- else:
421
- print("User did not confirm the standard insurance name. Manual intervention is required.")
422
- MediLink_ConfigLoader.log("User did not confirm the standard insurance name. Manual intervention is required.", config, level="CRITICAL")
423
- return None
424
-
425
453
  def create_nm1_payto_address_segments(config):
426
454
  """
427
455
  Constructs the NM1 segment for the Pay-To Address, N3 for street address, and N4 for city, state, and ZIP.
@@ -632,7 +660,7 @@ def format_claim_number(chart_number, date_of_service):
632
660
  return formatted_claim_number
633
661
 
634
662
  # Constructs the CLM and related segments based on parsed data and configuration.
635
- def create_clm_and_related_segments(parsed_data, config):
663
+ def create_clm_and_related_segments(parsed_data, config, crosswalk):
636
664
  """
637
665
  Insert the claim information (2300 loop),
638
666
  ensuring that details such as claim ID, total charge amount,and service date are included.
@@ -658,9 +686,23 @@ def create_clm_and_related_segments(parsed_data, config):
658
686
  parsed_data['TOS']))
659
687
 
660
688
  # HI - Health Care Diagnosis Code
661
- # The code value should be preceded by the qualifier "ABK" or "BK" to specify the type of
662
- # diagnosis code being used. "ABK" typically indicates ICD-10 codes, while "BK" indicates ICD-9 codes.
663
- segments.append("HI*ABK:H{}~".format(''.join(char for char in parsed_data['DIAG'] if char.isalnum() or char.isdigit())))
689
+ # Hardcoding "ABK" for ICD-10 codes as they are the only ones used now.
690
+ medisoft_code = ''.join(filter(str.isalnum, parsed_data['DIAG']))
691
+ diagnosis_code = next((key for key, value in crosswalk.get('diagnosis_to_medisoft', {}).items() if value == medisoft_code), None)
692
+
693
+ if diagnosis_code is None:
694
+ error_message = "Diagnosis code is empty for chart number: {}. Please verify. Medisoft code is {}".format(chart_number, medisoft_code)
695
+ MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
696
+ print(error_message)
697
+ diagnosis_code = input("Enter the complete diagnosis code: ")
698
+ # Update the crosswalk dictionary with the new pairing of diagnosis_code and medisoft_code.
699
+ crosswalk['diagnosis_to_medisoft'][diagnosis_code] = medisoft_code
700
+ MediLink_ConfigLoader.log("Updated crosswalk with new diagnosis code: {}, for Medisoft code {}".format(diagnosis_code, medisoft_code), config, level="INFO")
701
+ # TODO This needs to actually save the .json though which right now I'd like to route through the dedicated function for updating the crosswalk.
702
+ # TODO This should have been a validation exercise upstream and not a last minute check like this.
703
+
704
+ cleaned_diagnosis_code = ''.join(char for char in diagnosis_code if char.isalnum())
705
+ segments.append("HI*ABK:{}~".format(cleaned_diagnosis_code))
664
706
 
665
707
  # (2310C Loop) Service Facility Location NPI and Address Information
666
708
  segments.extend(create_service_facility_location_npi_segment(config))
@@ -680,7 +722,10 @@ def create_clm_and_related_segments(parsed_data, config):
680
722
  # DTP - Date
681
723
  segments.append("DTP*472*D8*{}~".format(convert_date_format(parsed_data['DATE'])))
682
724
 
683
- # Is there REF - Line Item Control Number missing here?
725
+ # Is there REF - Line Item Control Number missing here? Private insurance doesn't need it, but Medicare does?
726
+ # segments.append("REF*6R*1~") # REF01, Reference Identification Qualifier; REF02, Line Item Control Number.
727
+ # 6R - Provider Control Number (Number assigned by information provider company for tracking and billing purposes)
728
+ # 1 - Reference information as defined for a particular Transaction Set or as specified by the Reference Identification Qualifier
684
729
 
685
730
  return segments
686
731
 
@@ -1,15 +1,9 @@
1
1
  # This script requires Python 3.11 and is to be used for intalling new API clients.
2
- import os
3
- import time
4
- import subprocess
5
- import shutil
6
- import tempfile
2
+ import os, time, subprocess, shutil, tempfile, shlex, re
7
3
  from pathlib import Path
8
4
  from watchdog.observers import Observer
9
5
  from watchdog.events import FileSystemEventHandler
10
6
  from MediLink_API_v3 import ConfigLoader
11
- import shlex
12
- import re
13
7
 
14
8
  class SwaggerHandler(FileSystemEventHandler):
15
9
  def __init__(self, json_folder):