medicafe 0.250920.0__tar.gz → 0.251014.0__tar.gz

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.
Files changed (84) hide show
  1. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/__init__.py +1 -1
  2. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/__init__.py +1 -1
  3. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_core.py +302 -38
  4. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/deductible_utils.py +21 -9
  5. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/graphql_utils.py +143 -1
  6. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_encoder_library.py +18 -15
  7. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_ClaimStatus.py +11 -0
  8. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Deductible.py +1 -1
  9. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Deductible_Validator.py +2 -1
  10. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Display_Utils.py +3 -2
  11. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/__init__.py +1 -1
  12. {medicafe-0.250920.0/medicafe.egg-info → medicafe-0.251014.0}/PKG-INFO +1 -1
  13. {medicafe-0.250920.0 → medicafe-0.251014.0/medicafe.egg-info}/PKG-INFO +1 -1
  14. {medicafe-0.250920.0 → medicafe-0.251014.0}/setup.py +1 -1
  15. {medicafe-0.250920.0 → medicafe-0.251014.0}/LICENSE +0 -0
  16. {medicafe-0.250920.0 → medicafe-0.251014.0}/MANIFEST.in +0 -0
  17. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot.bat +0 -0
  18. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot.py +0 -0
  19. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Charges.py +0 -0
  20. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
  21. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
  22. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Notepad_Utils.py +0 -0
  23. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Post.py +0 -0
  24. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Preprocessor.py +0 -0
  25. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Preprocessor_lib.py +0 -0
  26. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_UI.py +0 -0
  27. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_dataformat_library.py +0 -0
  28. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_debug.bat +0 -0
  29. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_docx_decoder.py +0 -0
  30. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_smart_import.py +0 -0
  31. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/clear_cache.bat +0 -0
  32. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/crash_diagnostic.bat +0 -0
  33. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/f_drive_diagnostic.bat +0 -0
  34. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/full_debug_suite.bat +0 -0
  35. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/get_medicafe_version.py +0 -0
  36. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/process_csvs.bat +0 -0
  37. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/update_json.py +0 -0
  38. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/update_medicafe.py +0 -0
  39. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/MediLink_ConfigLoader.py +0 -0
  40. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/__main__.py +0 -0
  41. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_factory.py +0 -0
  42. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_utils.py +0 -0
  43. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/core_utils.py +0 -0
  44. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/error_reporter.py +0 -0
  45. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/logging_config.py +0 -0
  46. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/logging_demo.py +0 -0
  47. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/migration_helpers.py +0 -0
  48. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/smart_import.py +0 -0
  49. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/submission_index.py +0 -0
  50. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/InsuranceTypeService.py +0 -0
  51. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_cob_library.py +0 -0
  52. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_encoder.py +0 -0
  53. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_utilities.py +0 -0
  54. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_API_Generator.py +0 -0
  55. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Azure.py +0 -0
  56. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Charges.py +0 -0
  57. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_DataMgmt.py +0 -0
  58. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Decoder.py +0 -0
  59. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Down.py +0 -0
  60. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Gmail.py +0 -0
  61. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Mailer.py +0 -0
  62. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Parser.py +0 -0
  63. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_PatientProcessor.py +0 -0
  64. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Scan.py +0 -0
  65. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Scheduler.py +0 -0
  66. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_UI.py +0 -0
  67. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Up.py +0 -0
  68. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_insurance_utils.py +0 -0
  69. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_main.py +0 -0
  70. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_smart_import.py +0 -0
  71. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/Soumit_api.py +0 -0
  72. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/gmail_http_utils.py +0 -0
  73. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/gmail_oauth_utils.py +0 -0
  74. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/openssl.cnf +0 -0
  75. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/test.py +0 -0
  76. {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/webapp.html +0 -0
  77. {medicafe-0.250920.0 → medicafe-0.251014.0}/README.md +0 -0
  78. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/SOURCES.txt +0 -0
  79. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/dependency_links.txt +0 -0
  80. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/entry_points.txt +0 -0
  81. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/not-zip-safe +0 -0
  82. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/requires.txt +0 -0
  83. {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/top_level.txt +0 -0
  84. {medicafe-0.250920.0 → medicafe-0.251014.0}/setup.cfg +0 -0
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.250920.0"
22
+ __version__ = "0.251014.0"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25
 
@@ -27,7 +27,7 @@ Smart Import System:
27
27
  api_suite = get_api_access()
28
28
  """
29
29
 
30
- __version__ = "0.250920.0"
30
+ __version__ = "0.251014.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33
 
@@ -6,7 +6,7 @@ Moved from MediLink to centralize shared API operations.
6
6
  COMPATIBILITY: Python 3.4.4 and Windows XP compatible
7
7
  """
8
8
 
9
- import time, json, os, traceback, sys, requests
9
+ import time, json, os, traceback, sys
10
10
 
11
11
  # Import centralized logging configuration
12
12
  try:
@@ -52,6 +52,16 @@ except ImportError:
52
52
  import graphql_utils as MediLink_GraphQL
53
53
  except ImportError:
54
54
  # Create a dummy module if graphql_utils is not available
55
+ # BUG (minimally fixed): When graphql_utils is unavailable, DummyGraphQL
56
+ # lacked OPTUMAI claims inquiry helpers used by get_claim_summary_by_provider.
57
+ # Minimal fix implemented elsewhere: a capability guard disables OPTUMAI when
58
+ # these methods are missing so fallback to UHCAPI proceeds without AttributeError.
59
+ # Recommended next steps:
60
+ # - Provide explicit no-op stubs here that raise NotImplementedError for clarity.
61
+ # - Add a small shim that always exposes required methods to decouple import shapes.
62
+ # - Add a config feature flag to enable/disable OPTUMAI claims inquiry.
63
+ # - Add environment-based guard (dev/test default off) and metrics counter for use.
64
+ # - Add unit tests for both capability-present and capability-absent paths.
55
65
  class DummyGraphQL:
56
66
  @staticmethod
57
67
  def transform_eligibility_response(response):
@@ -155,8 +165,9 @@ def _get_default_endpoint_payer_ids(endpoint_name):
155
165
 
156
166
  # OPTUMAI – augmented list (subject to growth once the API adds a payer-list endpoint)
157
167
  optumai_payer_ids = [
158
- "87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400",
159
- "03432", "86050", "86047", "95378", "95467", "LIFE1", "WELM2"
168
+ # Supported Payer IDs per Optum Real Claims Inquiry (partial swagger)
169
+ "87726", "25463", "39026", "74227", "LIFE1", "WELM2", "06111",
170
+ "96385", "37602", "03432", "95467", "86050", "86047", "95378"
160
171
  ]
161
172
 
162
173
  if endpoint_name == 'OPTUMAI':
@@ -689,21 +700,61 @@ class APIClient(BaseAPIClient):
689
700
  MediLink_ConfigLoader.log(log_message, level="ERROR")
690
701
  raise
691
702
 
692
- def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
703
+ def fetch_payer_name_from_api(*args, **kwargs):
693
704
  """
694
- Fetches the payer name using the provided APIClient instance.
705
+ Fetch payer name by Payer ID with backward-compatible calling styles.
695
706
 
696
- :param client: An instance of APIClient
697
- :param payer_id: The payer ID to fetch
698
- :param primary_endpoint: The primary endpoint to use
699
- :return: The payer name if found
707
+ Supported call signatures:
708
+ - fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY')
709
+ - fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY') # client inferred via factory
710
+ - fetch_payer_name_from_api(payer_id=payer_id, config=config, client=client, primary_endpoint='AVAILITY')
700
711
  """
701
- # Ensure client is an instance of APIClient
712
+ # Normalize arguments
713
+ client = None
714
+ payer_id = None
715
+ config = None
716
+ primary_endpoint = kwargs.get('primary_endpoint', 'AVAILITY')
717
+
718
+ if 'client' in kwargs:
719
+ client = kwargs.get('client')
720
+ if 'payer_id' in kwargs:
721
+ payer_id = kwargs.get('payer_id')
722
+ if 'config' in kwargs:
723
+ config = kwargs.get('config')
724
+
725
+ if len(args) >= 3 and isinstance(args[0], APIClient):
726
+ client = args[0]
727
+ payer_id = args[1]
728
+ config = args[2]
729
+ if len(args) >= 4 and 'primary_endpoint' not in kwargs:
730
+ primary_endpoint = args[3]
731
+ elif len(args) >= 2 and isinstance(args[0], str) and client is None:
732
+ # Called as (payer_id, config, [primary_endpoint])
733
+ payer_id = args[0]
734
+ config = args[1]
735
+ if len(args) >= 3 and 'primary_endpoint' not in kwargs:
736
+ primary_endpoint = args[2]
737
+ elif len(args) == 1 and isinstance(args[0], APIClient) and (payer_id is None or config is None):
738
+ # Partial positional with client first, other params via kwargs
739
+ client = args[0]
740
+
741
+ # Acquire client via factory if not provided
742
+ if client is None:
743
+ try:
744
+ from MediCafe.core_utils import get_api_client
745
+ client = get_api_client()
746
+ if client is None:
747
+ client = APIClient()
748
+ except Exception:
749
+ client = APIClient()
750
+
751
+ # Basic validation
702
752
  if not isinstance(client, APIClient):
703
753
  error_message = "Invalid client provided. Expected an instance of APIClient."
704
- print(error_message)
705
754
  MediLink_ConfigLoader.log(error_message, level="ERROR")
706
- exit(1) # Exit the script
755
+ raise ValueError(error_message)
756
+ if payer_id is None or config is None:
757
+ raise ValueError("Missing required arguments: payer_id and config are required")
707
758
 
708
759
  # TODO: FUTURE IMPLEMENTATION - Remove AVAILITY default when other endpoints have payer-list APIs
709
760
  # Currently defaulting to AVAILITY as it's the only endpoint with confirmed payer-list functionality
@@ -750,6 +801,66 @@ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILI
750
801
  # 3. Fall back to endpoints with basic payer lookup if available
751
802
  # 4. Use AVAILITY as final fallback
752
803
 
804
+ # If HTTP client is unavailable or endpoints missing, use offline fallback only when allowed (TestMode or env flag)
805
+ try:
806
+ http_unavailable = (requests is None) # type: ignore[name-defined]
807
+ except Exception:
808
+ http_unavailable = True
809
+ # Determine whether offline fallback is permitted
810
+ # Align with main flow: default True when TestMode key is absent
811
+ offline_allowed = True
812
+ try:
813
+ from MediCafe.core_utils import extract_medilink_config
814
+ medi_local = extract_medilink_config(config)
815
+ offline_allowed = bool(medi_local.get('TestMode', True))
816
+ except Exception:
817
+ offline_allowed = True
818
+ try:
819
+ if os.environ.get('MEDICAFE_OFFLINE_PERMISSIVE', '').strip().lower() in ('1', 'true', 'yes', 'y'):
820
+ offline_allowed = True
821
+ except Exception:
822
+ pass
823
+
824
+ if offline_allowed and (http_unavailable or not isinstance(endpoints, dict) or not endpoints):
825
+ try:
826
+ # Prefer crosswalk mapping when available
827
+ try:
828
+ _, cw = MediLink_ConfigLoader.load_configuration()
829
+ except Exception:
830
+ cw = {}
831
+ payer_mappings = {}
832
+ try:
833
+ if isinstance(cw, dict):
834
+ payer_mappings = cw.get('payer_mappings', {}) or {}
835
+ except Exception:
836
+ payer_mappings = {}
837
+
838
+ if payer_id in payer_mappings:
839
+ resolved = payer_mappings.get(payer_id)
840
+ MediLink_ConfigLoader.log(
841
+ "Using crosswalk mapping for payer {} -> {} (offline)".format(payer_id, resolved),
842
+ level="WARNING",
843
+ console_output=CONSOLE_LOGGING
844
+ )
845
+ return resolved
846
+ # Safe minimal hardcoded map as last resort (test/offline only)
847
+ fallback_map = {
848
+ '87726': 'UnitedHealthcare',
849
+ '06111': 'Aetna',
850
+ '03432': 'Cigna',
851
+ '95378': 'Anthem Blue Cross',
852
+ '95467': 'Blue Shield',
853
+ }
854
+ if payer_id in fallback_map:
855
+ MediLink_ConfigLoader.log(
856
+ "Using offline fallback for payer {} -> {}".format(payer_id, fallback_map[payer_id]),
857
+ level="WARNING",
858
+ console_output=CONSOLE_LOGGING
859
+ )
860
+ return fallback_map[payer_id]
861
+ except Exception:
862
+ pass
863
+
753
864
  # Define endpoint rotation logic with payer-list capability detection
754
865
  available_endpoints = []
755
866
 
@@ -820,6 +931,47 @@ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILI
820
931
  error_message = "Error calling {0} for Payer ID {1}. Exception: {2}".format(endpoint_name, payer_id, e)
821
932
  MediLink_ConfigLoader.log(error_message, level="INFO")
822
933
 
934
+ # Offline/local fallback mapping for common payer IDs when API endpoints are unavailable
935
+ # Only when offline fallback is permitted
936
+ if offline_allowed:
937
+ try:
938
+ # Prefer crosswalk mapping first
939
+ try:
940
+ _, cw = MediLink_ConfigLoader.load_configuration()
941
+ except Exception:
942
+ cw = {}
943
+ payer_mappings = {}
944
+ try:
945
+ if isinstance(cw, dict):
946
+ payer_mappings = cw.get('payer_mappings', {}) or {}
947
+ except Exception:
948
+ payer_mappings = {}
949
+ if payer_id in payer_mappings:
950
+ resolved = payer_mappings.get(payer_id)
951
+ MediLink_ConfigLoader.log(
952
+ "Using crosswalk mapping for payer {} -> {} (offline)".format(payer_id, resolved),
953
+ level="WARNING",
954
+ console_output=CONSOLE_LOGGING
955
+ )
956
+ return resolved
957
+ # Minimal fallback map if crosswalk has no mapping (still offline-only)
958
+ fallback_map = {
959
+ '87726': 'UnitedHealthcare',
960
+ '06111': 'Aetna',
961
+ '03432': 'Cigna',
962
+ '95378': 'Anthem Blue Cross',
963
+ '95467': 'Blue Shield',
964
+ }
965
+ if payer_id in fallback_map:
966
+ MediLink_ConfigLoader.log(
967
+ "Using offline fallback for payer {} -> {}".format(payer_id, fallback_map[payer_id]),
968
+ level="WARNING",
969
+ console_output=CONSOLE_LOGGING
970
+ )
971
+ return fallback_map[payer_id]
972
+ except Exception:
973
+ pass
974
+
823
975
  # If all endpoints fail
824
976
  final_error_message = "All endpoints exhausted for Payer ID {0}.".format(payer_id)
825
977
  print(final_error_message)
@@ -827,30 +979,147 @@ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILI
827
979
  raise ValueError(final_error_message)
828
980
 
829
981
  def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false', transaction_id=None, env=None):
830
- # VERBOSE LOGGING FOR CLAIM SUMMARY
982
+ """
983
+ Unified Claims Inquiry that prefers OPTUMAI GraphQL searchClaim with
984
+ legacy response mapping, and falls back to legacy UHCAPI REST endpoint
985
+ to preserve current downstream flows and UI.
986
+ """
987
+ # Verbose input logging
831
988
  if DEBUG:
832
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
833
- MediLink_ConfigLoader.log("GET CLAIM SUMMARY BY PROVIDER - VERBOSE DETAILS", level="INFO")
834
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
835
- MediLink_ConfigLoader.log("TIN: {}".format(tin), level="INFO")
836
- MediLink_ConfigLoader.log("First Service Date: {}".format(first_service_date), level="INFO")
837
- MediLink_ConfigLoader.log("Last Service Date: {}".format(last_service_date), level="INFO")
838
- MediLink_ConfigLoader.log("Payer ID: {}".format(payer_id), level="INFO")
839
- MediLink_ConfigLoader.log("Get Standard Error: {}".format(get_standard_error), level="INFO")
840
- MediLink_ConfigLoader.log("Transaction ID: {}".format(transaction_id), level="INFO")
841
- MediLink_ConfigLoader.log("Environment: {}".format(env), level="INFO")
842
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
843
-
844
- endpoint_name = 'UHCAPI'
989
+ MediLink_ConfigLoader.log("Claims Inquiry inputs: tin={} start={} end={} payer={} tx={}"
990
+ .format(tin, first_service_date, last_service_date, payer_id, transaction_id),
991
+ level="INFO")
992
+
845
993
  from MediCafe.core_utils import extract_medilink_config
846
994
  medi = extract_medilink_config(client.config)
847
- url_extension = medi.get('endpoints', {}).get(endpoint_name, {}).get('additional_endpoints', {}).get('claim_summary_by_provider', '')
848
-
995
+
996
+ # Determine whether OPTUMAI is available/configured
997
+ endpoints_cfg = medi.get('endpoints', {}) if isinstance(medi, dict) else {}
998
+ optumai_cfg = endpoints_cfg.get('OPTUMAI', {}) if isinstance(endpoints_cfg, dict) else {}
999
+ uhcapi_cfg = endpoints_cfg.get('UHCAPI', {}) if isinstance(endpoints_cfg, dict) else {}
1000
+
1001
+ optumai_path = (optumai_cfg.get('additional_endpoints', {}) or {}).get('claims_inquiry')
1002
+ uhc_path = (uhcapi_cfg.get('additional_endpoints', {}) or {}).get('claim_summary_by_provider')
1003
+
1004
+ optumai_api_url = optumai_cfg.get('api_url')
1005
+ use_optumai = bool(optumai_api_url and optumai_path)
1006
+ # Capability guard: disable OPTUMAI if required helpers are unavailable
1007
+ if use_optumai:
1008
+ has_build = hasattr(MediLink_GraphQL, 'build_optumai_claims_inquiry_request')
1009
+ has_transform = hasattr(MediLink_GraphQL, 'transform_claims_inquiry_response_to_legacy')
1010
+ if not (has_build and has_transform):
1011
+ if DEBUG:
1012
+ MediLink_ConfigLoader.log(
1013
+ "Disabling OPTUMAI claims inquiry: missing GraphQL helpers (build/transform)",
1014
+ level="WARNING",
1015
+ console_output=CONSOLE_LOGGING
1016
+ )
1017
+ use_optumai = False
1018
+
1019
+ # Validate payer ID list based on endpoint we will attempt
1020
+ # BUG (minimally fixed): Previously a broad except masked invalid payer IDs and config issues.
1021
+ # Minimal fix: narrow exceptions and log a clear warning; proceed to maintain backward compatibility.
1022
+ # Recommended next steps:
1023
+ # - In non-production environments, fail fast on invalid payer IDs.
1024
+ # - Return a structured error object for invalid payers to avoid silent pass-through.
1025
+ # - Add unit tests covering valid/invalid payers and config load failures.
1026
+ try:
1027
+ target_endpoint = 'OPTUMAI' if use_optumai else 'UHCAPI'
1028
+ valid_payers = get_valid_payer_ids_for_endpoint(client, target_endpoint)
1029
+ if payer_id not in valid_payers:
1030
+ raise ValueError("Invalid payer_id: {} for endpoint {}. Must be one of: {}".format(
1031
+ payer_id, target_endpoint, ", ".join(valid_payers)))
1032
+ except (ValueError, KeyError) as e:
1033
+ MediLink_ConfigLoader.log(
1034
+ "Payer validation warning: {}".format(e),
1035
+ level="WARNING",
1036
+ console_output=CONSOLE_LOGGING
1037
+ )
1038
+ except Exception as e:
1039
+ MediLink_ConfigLoader.log(
1040
+ "Payer validation unexpected error: {}".format(e),
1041
+ level="ERROR",
1042
+ console_output=CONSOLE_LOGGING
1043
+ )
1044
+
1045
+ # Build OPTUMAI GraphQL request if configured
1046
+ if use_optumai:
1047
+ try:
1048
+ # Compose headers (providerTaxId is required per Optum Real API)
1049
+ headers = {
1050
+ 'Content-Type': 'application/json',
1051
+ 'Accept': 'application/json'
1052
+ }
1053
+ # Map billing_provider_tin to providerTaxId
1054
+ provider_tin = medi.get('billing_provider_tin')
1055
+ if provider_tin:
1056
+ headers['providerTaxId'] = str(provider_tin)
1057
+ # correlation id for tracing
1058
+ try:
1059
+ corr_id = 'mc-ci-{}'.format(int(time.time() * 1000))
1060
+ except Exception:
1061
+ corr_id = 'mc-ci-{}'.format(int(time.time()))
1062
+ headers['x-optum-consumer-correlation-id'] = corr_id
1063
+
1064
+ # Environment header for sandbox/stage if URL indicates
1065
+ try:
1066
+ api_url_lower = str(optumai_api_url).lower()
1067
+ if any(tag in api_url_lower for tag in ['sandbox', 'stg', 'stage', 'test']):
1068
+ headers['environment'] = 'sandbox'
1069
+ except Exception:
1070
+ pass
1071
+
1072
+ # Build searchClaimInput per partial swagger
1073
+ search_claim_input = {
1074
+ 'payerId': str(payer_id)
1075
+ }
1076
+ # Map dates MM/DD/YYYY -> expected strings for GraphQL (examples show MM/DD/YYYY and also 01/01/2025)
1077
+ if first_service_date:
1078
+ search_claim_input['serviceStartDate'] = first_service_date
1079
+ if last_service_date:
1080
+ search_claim_input['serviceEndDate'] = last_service_date
1081
+
1082
+ # Note: our pagination param is nextPageToken header in swagger; we surface as transaction_id in legacy code
1083
+ # For subsequent pages, pass it in headers; GraphQL spec shows header, but our call builder only handles body.
1084
+ # We'll set header when calling API; the transformer maps nextPageToken -> transactionId for downstream.
1085
+ if transaction_id:
1086
+ headers['nextPageToken'] = transaction_id
1087
+
1088
+ # Build GraphQL body
1089
+ graphql_body = MediLink_GraphQL.build_optumai_claims_inquiry_request(search_claim_input)
1090
+
1091
+ # Make the call
1092
+ response = client.make_api_call('OPTUMAI', 'POST', optumai_path, params=None, data=graphql_body, headers=headers)
1093
+
1094
+ # Transform to legacy format
1095
+ transformed = MediLink_GraphQL.transform_claims_inquiry_response_to_legacy(response)
1096
+ status = transformed.get('statuscode') if isinstance(transformed, dict) else None
1097
+ if status == '200':
1098
+ # Add note so UI can message that Optum Real is active (non-breaking)
1099
+ try:
1100
+ transformed['data_source'] = 'OPTUMAI'
1101
+ except Exception:
1102
+ pass
1103
+ return transformed
1104
+ # If not 200, fall through to UHC fallback when permitted
1105
+ except Exception as e:
1106
+ # If capability guard failed upstream for any reason, handle missing attribute gracefully here too
1107
+ if isinstance(e, AttributeError):
1108
+ MediLink_ConfigLoader.log(
1109
+ "OPTUMAI disabled due to missing GraphQL helpers; falling back to UHCAPI.",
1110
+ level="WARNING",
1111
+ console_output=CONSOLE_LOGGING
1112
+ )
1113
+ else:
1114
+ MediLink_ConfigLoader.log("OPTUMAI Claims Inquiry failed: {}".format(e), level="WARNING")
1115
+
1116
+ # Fallback to existing UHC REST behavior to preserve current flows
1117
+ endpoint_name = 'UHCAPI'
1118
+ url_extension = uhc_path or ''
1119
+
849
1120
  if DEBUG:
850
- MediLink_ConfigLoader.log("URL Extension: {}".format(url_extension), level="INFO")
851
-
852
- # Build headers according to official API documentation
853
- # Note: Environment detection is now handled automatically by the API client
1121
+ MediLink_ConfigLoader.log("Falling back to UHCAPI claim_summary_by_provider path: {}".format(url_extension), level="INFO")
1122
+
854
1123
  headers = {
855
1124
  'tin': tin,
856
1125
  'firstServiceDt': first_service_date,
@@ -859,14 +1128,9 @@ def get_claim_summary_by_provider(client, tin, first_service_date, last_service_
859
1128
  'getStandardError': get_standard_error,
860
1129
  'Accept': 'application/json'
861
1130
  }
862
-
863
- # Add transactionId if provided (for pagination)
864
1131
  if transaction_id:
865
1132
  headers['transactionId'] = transaction_id
866
-
867
- if DEBUG:
868
- MediLink_ConfigLoader.log("Headers: {}".format(json.dumps(headers, indent=2)), level="INFO")
869
-
1133
+
870
1134
  return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
871
1135
 
872
1136
  def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
@@ -612,7 +612,8 @@ def extract_super_connector_insurance_info(eligibility_data):
612
612
  if insurance_info:
613
613
  return {
614
614
  'insuranceType': insurance_info.get("planTypeDescription", ""),
615
- 'insuranceTypeCode': insurance_info.get("productServiceCode", ""),
615
+ # Prefer new insuranceTypeCode when available; fallback to productServiceCode
616
+ 'insuranceTypeCode': insurance_info.get("insuranceTypeCode") or insurance_info.get("productServiceCode", ""),
616
617
  'memberId': insurance_info.get("memberId", ""),
617
618
  'payerId': insurance_info.get("payerId", "")
618
619
  }
@@ -634,7 +635,8 @@ def extract_super_connector_insurance_info(eligibility_data):
634
635
 
635
636
  return {
636
637
  'insuranceType': insurance_type,
637
- 'insuranceTypeCode': detail.get("productServiceCode", ""),
638
+ # Prefer insuranceTypeCode if present in extensions; fallback to productServiceCode
639
+ 'insuranceTypeCode': detail.get("insuranceTypeCode") or detail.get("productServiceCode", ""),
638
640
  'memberId': detail.get("memberId", ""),
639
641
  'payerId': detail.get("payerId", "")
640
642
  }
@@ -656,8 +658,10 @@ def extract_super_connector_insurance_info(eligibility_data):
656
658
  elif "POS" in insurance_type:
657
659
  insurance_type = "Point of Service (POS)"
658
660
 
659
- # Get insurance type code from multiple possible locations
660
- insurance_type_code = eligibility_data.get("productServiceCode", "")
661
+ # Get insurance type code from multiple possible locations (prefer insuranceTypeCode)
662
+ insurance_type_code = eligibility_data.get("insuranceTypeCode", "")
663
+ if not insurance_type_code:
664
+ insurance_type_code = eligibility_data.get("productServiceCode", "")
661
665
  if not insurance_type_code:
662
666
  # Try to get from coverageTypes
663
667
  coverage_types = eligibility_data.get("coverageTypes", [])
@@ -767,7 +771,8 @@ def convert_eligibility_to_enhanced_format(data, dob, member_id, patient_id="",
767
771
  'service_date_display': service_date,
768
772
  'service_date_sort': datetime.min, # Will be enhanced later
769
773
  'status': 'Processed',
770
- 'insurance_type': insurance_info['insuranceType'],
774
+ # Prefer insurance type code over description for downstream validation/UI
775
+ 'insurance_type': insurance_info.get('insuranceTypeCode') or insurance_info.get('insuranceType', ''),
771
776
  'policy_status': policy_status,
772
777
  'remaining_amount': remaining_amount,
773
778
  'data_source': 'Legacy',
@@ -803,7 +808,8 @@ def convert_eligibility_to_enhanced_format(data, dob, member_id, patient_id="",
803
808
  'service_date_display': service_date,
804
809
  'service_date_sort': datetime.min, # Will be enhanced later
805
810
  'status': 'Processed',
806
- 'insurance_type': insurance_info['insuranceType'],
811
+ # Prefer insurance type code over description for downstream validation/UI
812
+ 'insurance_type': insurance_info.get('insuranceTypeCode') or insurance_info.get('insuranceType', ''),
807
813
  'policy_status': policy_status,
808
814
  'remaining_amount': remaining_amount,
809
815
  'data_source': 'OptumAI',
@@ -975,7 +981,13 @@ def merge_responses(optumai_data, legacy_data, dob, member_id):
975
981
  merged['patient_name'] = "{} {}".format(selected.get('firstName', ''), selected.get('lastName', '')).strip()
976
982
  merged['dob'] = selected.get('dateOfBirth', dob)
977
983
  merged['member_id'] = selected.get('memberId', member_id)
978
- merged['insurance_type'] = selected.get('insuranceType', selected.get('coverageType', ''))
984
+ # Prefer insurance type code from extensions when available
985
+ merged['insurance_type'] = (
986
+ selected.get('insuranceTypeCode') or
987
+ selected.get('productServiceCode') or
988
+ selected.get('insuranceType') or
989
+ selected.get('coverageType', '')
990
+ )
979
991
  merged['policy_status'] = selected.get('policyStatus', 'Active')
980
992
  merged['payer_id'] = selected.get('payerId', '')
981
993
  # Extract deductible from plan levels if available
@@ -1008,8 +1020,8 @@ def merge_responses(optumai_data, legacy_data, dob, member_id):
1008
1020
  merged['remaining_amount'] = extract_super_connector_remaining_amount(primary)
1009
1021
  merged['policy_status'] = primary.get('policyStatus', '')
1010
1022
 
1011
- # OptumAI doesn't have insurance_type yet, so we'll backfill from Legacy
1012
- merged['insurance_type'] = ''
1023
+ # Prefer insurance type code directly from OptumAI response
1024
+ merged['insurance_type'] = primary.get('insuranceTypeCode') or primary.get('productServiceCode', '')
1013
1025
  merged['data_source'] = 'OptumAI'
1014
1026
 
1015
1027
  elif is_legacy_response_format(primary) and primary:
@@ -218,6 +218,56 @@ class GraphQLQueryBuilder:
218
218
  }
219
219
  }
220
220
 
221
+ # ------------------------------------------------------------------
222
+ # OPTUMAI Claims Inquiry (Real Claim Inquiry) - minimal searchClaim
223
+ # ------------------------------------------------------------------
224
+ @staticmethod
225
+ def get_optumai_claims_inquiry_query():
226
+ """
227
+ Returns a minimal GraphQL query for searchClaim that includes fields
228
+ necessary to map into the existing legacy claims summary structure
229
+ (memberInfo, claimSummary, crosswalk data).
230
+ """
231
+ return (
232
+ "query searchClaim($searchClaimInput: SearchClaimInput!) {\n"
233
+ " searchClaimResponse(input: $searchClaimInput) {\n"
234
+ " claims {\n"
235
+ " claimNumber\n"
236
+ " claimStatus\n"
237
+ " member { firstName lastName }\n"
238
+ " claimEvents { processedDate serviceStartDate }\n"
239
+ " claimLevelTotalAmount {\n"
240
+ " totalBilledChargeAmount\n"
241
+ " totalAllowedAmount\n"
242
+ " totalPaidAmount\n"
243
+ " totalPatientResponsibilityAmount\n"
244
+ " }\n"
245
+ " claimStatusCrosswalkData {\n"
246
+ " claim507Code claim507CodeDesc claim508Code claim508CodeDesc adjudicatedClaimSuffixCode\n"
247
+ " }\n"
248
+ " }\n"
249
+ " pagination { hasMoreRecords nextPageToken }\n"
250
+ " }\n"
251
+ "}"
252
+ )
253
+
254
+ @staticmethod
255
+ def build_optumai_claims_inquiry_request(search_claim_input):
256
+ """
257
+ Build the GraphQL request body for searchClaim.
258
+
259
+ Args:
260
+ search_claim_input: dict with keys matching SearchClaimInput (e.g.,
261
+ payerId (required), serviceStartDate, serviceEndDate, claimNumber, etc.)
262
+ """
263
+ return {
264
+ "query": GraphQLQueryBuilder.get_optumai_claims_inquiry_query(),
265
+ "operationName": "searchClaim",
266
+ "variables": {
267
+ "searchClaimInput": search_claim_input or {}
268
+ }
269
+ }
270
+
221
271
  class GraphQLResponseTransformer:
222
272
  """Transforms GraphQL responses to match REST API format"""
223
273
 
@@ -331,7 +381,10 @@ class GraphQLResponseTransformer:
331
381
  'productCode': insurance_info.get('productCode'),
332
382
  'lineOfBusiness': insurance_info.get('lineOfBusiness'),
333
383
  'lineOfBusinessCode': insurance_info.get('lineOfBusinessCode'),
334
- 'coverageTypes': insurance_info.get('coverageTypes', [])
384
+ 'coverageTypes': insurance_info.get('coverageTypes', []),
385
+ # Expose insurance type fields for downstream logic/UI
386
+ 'insuranceTypeCode': insurance_info.get('insuranceTypeCode'),
387
+ 'insuranceType': insurance_info.get('insuranceType')
335
388
  })
336
389
 
337
390
  # Safely extract associated IDs
@@ -480,6 +533,87 @@ class GraphQLResponseTransformer:
480
533
  'rawGraphQLResponse': graphql_response
481
534
  }
482
535
 
536
+ @staticmethod
537
+ def transform_claims_inquiry_response_to_legacy(graphql_response):
538
+ """
539
+ Transform OPTUMAI searchClaim GraphQL response into the legacy
540
+ claims summary format expected by downstream code (e.g.,
541
+ MediLink_ClaimStatus.extract_claim_data).
542
+ """
543
+ try:
544
+ # Handle GraphQL errors
545
+ if isinstance(graphql_response, dict) and 'errors' in graphql_response:
546
+ first_err = graphql_response['errors'][0] if graphql_response['errors'] else {}
547
+ code = (first_err.get('extensions', {}) or {}).get('code') or first_err.get('code') or 'GRAPHQL_ERROR'
548
+ msg = first_err.get('message') or first_err.get('description') or 'GraphQL error'
549
+ status = '401' if 'AUTH' in str(code).upper() else '500'
550
+ return {
551
+ 'statuscode': status,
552
+ 'message': '{}: {}'.format(code, msg),
553
+ 'rawGraphQLResponse': graphql_response
554
+ }
555
+
556
+ data = (graphql_response or {}).get('data') or {}
557
+ search_resp = data.get('searchClaimResponse') or {}
558
+ claims = search_resp.get('claims') or []
559
+ pagination = search_resp.get('pagination') or {}
560
+
561
+ legacy_claims = []
562
+ for c in claims:
563
+ member = c.get('member') or {}
564
+ events = c.get('claimEvents') or {}
565
+ totals = c.get('claimLevelTotalAmount') or {}
566
+ xwalk_list = c.get('claimStatusCrosswalkData') or []
567
+
568
+ # Map crosswalk to legacy keys list
569
+ legacy_xwalk = []
570
+ for x in xwalk_list:
571
+ legacy_xwalk.append({
572
+ 'clm507Cd': x.get('claim507Code'),
573
+ 'clm507CdDesc': x.get('claim507CodeDesc'),
574
+ 'clm508Cd': x.get('claim508Code'),
575
+ 'clm508CdDesc': x.get('claim508CodeDesc'),
576
+ 'clmIcnSufxCd': x.get('adjudicatedClaimSuffixCode')
577
+ })
578
+
579
+ legacy_claims.append({
580
+ 'claimNumber': c.get('claimNumber'),
581
+ 'claimStatus': c.get('claimStatus'),
582
+ 'memberInfo': {
583
+ 'ptntFn': member.get('firstName'),
584
+ 'ptntLn': member.get('lastName')
585
+ },
586
+ 'claimSummary': {
587
+ 'processedDt': events.get('processedDate') or '',
588
+ 'firstSrvcDt': events.get('serviceStartDate') or '',
589
+ 'totalChargedAmt': (totals.get('totalBilledChargeAmount') or ''),
590
+ 'totalAllowdAmt': (totals.get('totalAllowedAmount') or ''),
591
+ 'totalPaidAmt': (totals.get('totalPaidAmount') or ''),
592
+ 'totalPtntRespAmt': (totals.get('totalPatientResponsibilityAmount') or ''),
593
+ 'clmXWalkData': legacy_xwalk
594
+ }
595
+ })
596
+
597
+ next_token = pagination.get('nextPageToken')
598
+ has_more = bool(pagination.get('hasMoreRecords'))
599
+
600
+ transformed = {
601
+ 'statuscode': '200',
602
+ 'message': 'Claims found' if legacy_claims else 'No claims found',
603
+ 'claims': legacy_claims,
604
+ # Maintain legacy pagination signaling by mapping to 'transactionId'
605
+ 'transactionId': next_token if has_more else None,
606
+ 'rawGraphQLResponse': graphql_response
607
+ }
608
+ return transformed
609
+ except Exception as e:
610
+ print("Error transforming Claims Inquiry response: {}".format(str(e)))
611
+ return {
612
+ 'statuscode': '500',
613
+ 'message': 'Error processing Claims Inquiry response: {}'.format(str(e)),
614
+ 'rawGraphQLResponse': graphql_response
615
+ }
616
+
483
617
  # Convenience functions for easy access
484
618
 
485
619
  def get_eligibility_query():
@@ -502,10 +636,18 @@ def build_optumai_enriched_request(variables):
502
636
  """Build enriched GraphQL request body for OPTUMAI"""
503
637
  return GraphQLQueryBuilder.build_optumai_enriched_request(variables)
504
638
 
639
+ def build_optumai_claims_inquiry_request(search_claim_input):
640
+ """Build GraphQL request body for OPTUMAI searchClaim"""
641
+ return GraphQLQueryBuilder.build_optumai_claims_inquiry_request(search_claim_input)
642
+
505
643
  def transform_eligibility_response(graphql_response):
506
644
  """Transform GraphQL eligibility response to REST format"""
507
645
  return GraphQLResponseTransformer.transform_eligibility_response(graphql_response)
508
646
 
647
+ def transform_claims_inquiry_response_to_legacy(graphql_response):
648
+ """Transform OPTUMAI searchClaim response to legacy claims summary format"""
649
+ return GraphQLResponseTransformer.transform_claims_inquiry_response_to_legacy(graphql_response)
650
+
509
651
  def get_sample_eligibility_request():
510
652
  """Get the sample GraphQL request from swagger documentation"""
511
653
  return GraphQLQueryBuilder.get_sample_eligibility_request()
@@ -87,21 +87,24 @@ except (ImportError, SystemError):
87
87
  except ImportError:
88
88
  MediLink_UI = None
89
89
 
90
- # Import MediBot crosswalk utilities with fallback - using dynamic imports to avoid circular dependencies
91
- try:
92
- import MediBot_Crosswalk_Utils
93
- update_crosswalk_with_corrected_payer_id = MediBot_Crosswalk_Utils.update_crosswalk_with_corrected_payer_id
94
- update_crosswalk_with_new_payer_id = MediBot_Crosswalk_Utils.update_crosswalk_with_new_payer_id
95
- except ImportError:
96
- # Fallback to main library if utility import fails
97
- try:
98
- import MediBot_Crosswalk_Library
99
- update_crosswalk_with_corrected_payer_id = MediBot_Crosswalk_Library.update_crosswalk_with_corrected_payer_id
100
- update_crosswalk_with_new_payer_id = MediBot_Crosswalk_Library.update_crosswalk_with_new_payer_id
101
- except (ImportError, AttributeError):
102
- # If functions are not available, set to None for graceful handling
103
- update_crosswalk_with_corrected_payer_id = None
104
- update_crosswalk_with_new_payer_id = None
90
+ # Import MediBot crosswalk utilities via centralized import helpers
91
+ from MediCafe.core_utils import import_medibot_module
92
+
93
+ # Resolve required functions directly at import-time using centralized helper (Py 3.4.4 friendly)
94
+ update_crosswalk_with_new_payer_id = (
95
+ import_medibot_module('MediBot_Crosswalk_Utils', 'update_crosswalk_with_new_payer_id') or
96
+ import_medibot_module('MediBot_Crosswalk_Library', 'update_crosswalk_with_new_payer_id')
97
+ )
98
+
99
+ update_crosswalk_with_corrected_payer_id = (
100
+ import_medibot_module('MediBot_Crosswalk_Utils', 'update_crosswalk_with_corrected_payer_id') or
101
+ import_medibot_module('MediBot_Crosswalk_Library', 'update_crosswalk_with_corrected_payer_id')
102
+ )
103
+
104
+ if not callable(update_crosswalk_with_new_payer_id):
105
+ raise RuntimeError("Crosswalk update function not available (new payer id). Ensure MediBot_Crosswalk_Utils or MediBot_Crosswalk_Library is importable.")
106
+ if not callable(update_crosswalk_with_corrected_payer_id):
107
+ raise RuntimeError("Crosswalk update function not available (corrected payer id). Ensure MediBot_Crosswalk_Utils or MediBot_Crosswalk_Library is importable.")
105
108
 
106
109
  # Import enhanced insurance selection with fallback
107
110
  # XP/Python34 Compatibility: Enhanced error handling with verbose output
@@ -263,6 +263,17 @@ def process_claims_with_pagination(client, billing_provider_tin, start_date_str,
263
263
  client, billing_provider_tin, start_date_str, end_date_str,
264
264
  payer_id=payer_id, transaction_id=transaction_id
265
265
  )
266
+ # Informational notice if new OPTUM Real endpoint is being used
267
+ try:
268
+ if isinstance(claim_summary, dict) and claim_summary.get('data_source') == 'OPTUMAI':
269
+ MediLink_ConfigLoader.log(
270
+ "Claims Inquiry via Optum Real endpoint (OPTUMAI) with legacy-compatible output.",
271
+ level="INFO"
272
+ )
273
+ if DEBUG:
274
+ print("[Info] Using Optum Real Claims Inquiry for payer {}".format(payer_id))
275
+ except Exception:
276
+ pass
266
277
 
267
278
  # Extract claims from this page
268
279
  claims = claim_summary.get('claims', [])
@@ -1027,7 +1027,7 @@ if __name__ == "__main__":
1027
1027
  output_file_path = os.path.join(os.getenv('TEMP'), 'eligibility_report.txt')
1028
1028
  with open(output_file_path, 'w') as output_file:
1029
1029
  table_header = "{:<20} | {:<10} | {:<40} | {:<5} | {:<14} | {:<14}".format(
1030
- "Patient Name", "DOB", "Insurance Type", "PayID", "Policy Status", "Remaining Amt")
1030
+ "Patient Name", "DOB", "Insurance Type Code", "PayID", "Policy Status", "Remaining Amt")
1031
1031
  output_file.write(table_header + "\n")
1032
1032
  output_file.write("-" * len(table_header) + "\n")
1033
1033
 
@@ -72,7 +72,8 @@ def extract_legacy_values_for_comparison(legacy_data):
72
72
 
73
73
  # Extract insurance info
74
74
  insurance_info = policy.get("insuranceInfo", {})
75
- comparison_values["insurance_type"] = insurance_info.get("insuranceType", "")
75
+ # Prefer standardized insuranceTypeCode for comparisons; keep description as secondary
76
+ comparison_values["insurance_type"] = insurance_info.get("insuranceTypeCode", "") or insurance_info.get("insuranceType", "")
76
77
  comparison_values["insurance_typeCode"] = insurance_info.get("insuranceTypeCode", "")
77
78
  comparison_values["insurance_memberId"] = insurance_info.get("memberId", "")
78
79
  comparison_values["insurance_payerId"] = insurance_info.get("payerId", "")
@@ -66,10 +66,11 @@ def display_file_summary(index, summary):
66
66
  summary.get('user_preferred_endpoint') or
67
67
  summary.get('suggested_endpoint', 'AVAILITY'))
68
68
 
69
- # Format insurance type for display - handle both 2 and 3 character codes
69
+ # Format insurance type for display - prioritize code (SBR09/insuranceTypeCode)
70
70
  if insurance_type and len(insurance_type) <= 3:
71
71
  insurance_display = insurance_type
72
72
  else:
73
+ # If description was provided instead of code, truncate respectfully
73
74
  insurance_display = insurance_type[:3] if insurance_type else '--'
74
75
 
75
76
  # Shorten source for compact display
@@ -384,7 +385,7 @@ def _display_table_header(col_widths, context):
384
385
  "Member ID", col_widths['member_id'],
385
386
  "Payer ID", col_widths['payer_id'],
386
387
  "Service Date", col_widths['service_date'],
387
- "Insurance Type", col_widths['insurance_type'],
388
+ "Insurance Type Code", col_widths['insurance_type'],
388
389
  "Policy Status", col_widths['policy_status'],
389
390
  "Remaining Amt", col_widths['remaining_amount']
390
391
  )
@@ -22,7 +22,7 @@ Smart Import Integration:
22
22
  datamgmt = get_components('medilink_datamgmt')
23
23
  """
24
24
 
25
- __version__ = "0.250920.0"
25
+ __version__ = "0.251014.0"
26
26
  __author__ = "Daniel Vidaud"
27
27
  __email__ = "daniel@personalizedtransformation.com"
28
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250920.0
3
+ Version: 0.251014.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250920.0
3
+ Version: 0.251014.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -54,7 +54,7 @@ if long_description_text is None:
54
54
 
55
55
  setup(
56
56
  name='medicafe',
57
- version="0.250920.0",
57
+ version="0.251014.0",
58
58
  description='MediCafe',
59
59
  long_description=long_description_text,
60
60
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes