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.
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/__init__.py +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/__init__.py +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_core.py +302 -38
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/deductible_utils.py +21 -9
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/graphql_utils.py +143 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_encoder_library.py +18 -15
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_ClaimStatus.py +11 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Deductible.py +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Deductible_Validator.py +2 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Display_Utils.py +3 -2
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/__init__.py +1 -1
- {medicafe-0.250920.0/medicafe.egg-info → medicafe-0.251014.0}/PKG-INFO +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0/medicafe.egg-info}/PKG-INFO +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/setup.py +1 -1
- {medicafe-0.250920.0 → medicafe-0.251014.0}/LICENSE +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MANIFEST.in +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Crosswalk_Utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Notepad_Utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_Preprocessor_lib.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_UI.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_dataformat_library.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_debug.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_docx_decoder.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/MediBot_smart_import.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/clear_cache.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/crash_diagnostic.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/f_drive_diagnostic.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/full_debug_suite.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/get_medicafe_version.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/process_csvs.bat +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/update_json.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/MediLink_ConfigLoader.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/__main__.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_factory.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/api_utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/core_utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/error_reporter.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/logging_config.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/logging_demo.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/migration_helpers.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/smart_import.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediCafe/submission_index.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/InsuranceTypeService.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Charges.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_DataMgmt.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Decoder.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Gmail.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Parser.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_PatientProcessor.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_UI.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_Up.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_insurance_utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_main.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/MediLink_smart_import.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/gmail_http_utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/gmail_oauth_utils.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/test.py +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/MediLink/webapp.html +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/README.md +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/SOURCES.txt +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/entry_points.txt +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/medicafe.egg-info/top_level.txt +0 -0
- {medicafe-0.250920.0 → medicafe-0.251014.0}/setup.cfg +0 -0
@@ -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
|
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
|
-
|
159
|
-
"
|
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(
|
703
|
+
def fetch_payer_name_from_api(*args, **kwargs):
|
693
704
|
"""
|
694
|
-
|
705
|
+
Fetch payer name by Payer ID with backward-compatible calling styles.
|
695
706
|
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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("=
|
833
|
-
|
834
|
-
|
835
|
-
|
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
|
-
|
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("
|
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
|
-
|
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
|
-
|
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("
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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 -
|
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
|
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|