medicafe 0.250930.0__py3-none-any.whl → 0.251014.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
MediBot/__init__.py CHANGED
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.250930.0"
22
+ __version__ = "0.251014.0"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25
 
MediCafe/__init__.py CHANGED
@@ -27,7 +27,7 @@ Smart Import System:
27
27
  api_suite = get_api_access()
28
28
  """
29
29
 
30
- __version__ = "0.250930.0"
30
+ __version__ = "0.251014.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33
 
MediCafe/api_core.py CHANGED
@@ -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':
@@ -968,30 +979,147 @@ def fetch_payer_name_from_api(*args, **kwargs):
968
979
  raise ValueError(final_error_message)
969
980
 
970
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):
971
- # 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
972
988
  if DEBUG:
973
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
974
- MediLink_ConfigLoader.log("GET CLAIM SUMMARY BY PROVIDER - VERBOSE DETAILS", level="INFO")
975
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
976
- MediLink_ConfigLoader.log("TIN: {}".format(tin), level="INFO")
977
- MediLink_ConfigLoader.log("First Service Date: {}".format(first_service_date), level="INFO")
978
- MediLink_ConfigLoader.log("Last Service Date: {}".format(last_service_date), level="INFO")
979
- MediLink_ConfigLoader.log("Payer ID: {}".format(payer_id), level="INFO")
980
- MediLink_ConfigLoader.log("Get Standard Error: {}".format(get_standard_error), level="INFO")
981
- MediLink_ConfigLoader.log("Transaction ID: {}".format(transaction_id), level="INFO")
982
- MediLink_ConfigLoader.log("Environment: {}".format(env), level="INFO")
983
- MediLink_ConfigLoader.log("=" * 80, level="INFO")
984
-
985
- 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
+
986
993
  from MediCafe.core_utils import extract_medilink_config
987
994
  medi = extract_medilink_config(client.config)
988
- url_extension = medi.get('endpoints', {}).get(endpoint_name, {}).get('additional_endpoints', {}).get('claim_summary_by_provider', '')
989
-
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
+
990
1120
  if DEBUG:
991
- MediLink_ConfigLoader.log("URL Extension: {}".format(url_extension), level="INFO")
992
-
993
- # Build headers according to official API documentation
994
- # 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
+
995
1123
  headers = {
996
1124
  'tin': tin,
997
1125
  'firstServiceDt': first_service_date,
@@ -1000,14 +1128,9 @@ def get_claim_summary_by_provider(client, tin, first_service_date, last_service_
1000
1128
  'getStandardError': get_standard_error,
1001
1129
  'Accept': 'application/json'
1002
1130
  }
1003
-
1004
- # Add transactionId if provided (for pagination)
1005
1131
  if transaction_id:
1006
1132
  headers['transactionId'] = transaction_id
1007
-
1008
- if DEBUG:
1009
- MediLink_ConfigLoader.log("Headers: {}".format(json.dumps(headers, indent=2)), level="INFO")
1010
-
1133
+
1011
1134
  return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
1012
1135
 
1013
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:
MediCafe/graphql_utils.py CHANGED
@@ -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()
@@ -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
  )
MediLink/__init__.py CHANGED
@@ -22,7 +22,7 @@ Smart Import Integration:
22
22
  datamgmt = get_components('medilink_datamgmt')
23
23
  """
24
24
 
25
- __version__ = "0.250930.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.250930.0
3
+ Version: 0.251014.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -12,7 +12,7 @@ MediBot/MediBot_dataformat_library.py,sha256=D46fdPtxcgfWTzaLBtSvjtozzZBNqNiODgu
12
12
  MediBot/MediBot_debug.bat,sha256=F5Lfi3nFEEo4Ddx9EbX94u3fNAMgzMp3wsn-ULyASTM,6017
13
13
  MediBot/MediBot_docx_decoder.py,sha256=9BSjV-kB90VHnqfL_5iX4zl5u0HcHvHuL7YNfx3gXpQ,33143
14
14
  MediBot/MediBot_smart_import.py,sha256=Emvz7NwemHGCHvG5kZcUyXMcCheidbGKaPfOTg-YCEs,6684
15
- MediBot/__init__.py,sha256=TD2TjOOLwIklKHXMC7OU3WamrLSJqF8vdQFmdzOhuAg,3192
15
+ MediBot/__init__.py,sha256=TyG5kcEesXMqTWWrLSP5qp6C3KEvEHDd5liMjhDfYjo,3192
16
16
  MediBot/clear_cache.bat,sha256=F6-VhETWw6xDdGWG2wUqvtXjCl3lY4sSUFqF90bM8-8,1860
17
17
  MediBot/crash_diagnostic.bat,sha256=j8kUtyBg6NOWbXpeFuEqIRHOkVzgUrLOqO3FBMfNxTo,9268
18
18
  MediBot/f_drive_diagnostic.bat,sha256=4572hZaiwZ5wVAarPcZJQxkOSTwAdDuT_X914noARak,6878
@@ -22,15 +22,15 @@ MediBot/process_csvs.bat,sha256=3tI7h1z9eRj8rUUL4wJ7dy-Qrak20lRmpAPtGbUMbVQ,3489
22
22
  MediBot/update_json.py,sha256=vvUF4mKCuaVly8MmoadDO59M231fCIInc0KI1EtDtPA,3704
23
23
  MediBot/update_medicafe.py,sha256=G1lyvVOHYuho1d-TJQNN6qaB4HBWaJ2PpXqemBoPlRQ,17937
24
24
  MediCafe/MediLink_ConfigLoader.py,sha256=NoLb2YiJwlkrRYCt2PHvcFJ7yTIRWQCrsvkZIJWreM4,11141
25
- MediCafe/__init__.py,sha256=B2m0aF3q1XVoCTmPK4wgbWtROmUsTwEiuyD7LH9VnOA,5721
25
+ MediCafe/__init__.py,sha256=3WhV7uJK9-E-3-URJ1WbMddv6uTZJcgFAuTtIXU9uvE,5721
26
26
  MediCafe/__main__.py,sha256=mRNyk3D9Ilnu2XhgVI_rut7r5Ro7UIKtwV871giAHI8,12992
27
- MediCafe/api_core.py,sha256=tzCMB1fO7aLpfzlGxbU0qmhd9nD26YoIUohgDkXzNgA,84383
27
+ MediCafe/api_core.py,sha256=r9cqa5-HU4A7iz3NLxzRwuxsxOfDiJn9SRgtPjT83qU,90764
28
28
  MediCafe/api_factory.py,sha256=I5AeJoyu6m7oCrjc2OvVvO_4KSBRutTsR1riiWhTZV0,12086
29
29
  MediCafe/api_utils.py,sha256=KWQB0q1k5E6frOFFlKWcFpHNcqfrS7KJ_82672wbupw,14041
30
30
  MediCafe/core_utils.py,sha256=XKUpyv7yKjIQ8iNrhD76PIURyt6GZxb98v0daiI7aaw,27303
31
- MediCafe/deductible_utils.py,sha256=bsI5YRO8QzaEU-sxi2F5zIx6k4D53rYUt0mlg-6-Jc8,57644
31
+ MediCafe/deductible_utils.py,sha256=1d0ui0-S0B-Bv2iDbYIujy_L_Y4XphW98Iu59GYLc8A,58722
32
32
  MediCafe/error_reporter.py,sha256=t9AAkkVsmZMxPMSA6DW7wDf2cxXpFfA9oJW5-thg5VQ,8176
33
- MediCafe/graphql_utils.py,sha256=xrREl0mqktEBkV6SZeAImuuDc8Sp2Q80rWxKIh-zD7Q,44499
33
+ MediCafe/graphql_utils.py,sha256=3BYl4wrox40063RwCtIqsrxSMA5rdchCP69RGi-U4aY,51317
34
34
  MediCafe/logging_config.py,sha256=auT65LN5oDEXVhkMeLke63kJHTWxYf2o8YihAfQFgzU,5493
35
35
  MediCafe/logging_demo.py,sha256=TwUhzafna5pMdN3zSKGrpUWRqX96F1JGGsSUtr3dygs,1975
36
36
  MediCafe/migration_helpers.py,sha256=48GnP4xcgvDNNlzoWsKASCpF4H0KnyveHPbz6kjQy50,17737
@@ -44,12 +44,12 @@ MediLink/MediLink_837p_utilities.py,sha256=AJ0F22LoF8du20zPBH4TZXgsdXCD-1qYKBnHJ
44
44
  MediLink/MediLink_API_Generator.py,sha256=VZBL9W8yFTMJ4ikSwbUI8ANtaJCLnUyqoF8xQEJQn_E,11176
45
45
  MediLink/MediLink_Azure.py,sha256=Ow70jctiHFIylskBExN7WUoRgrKOvBR6jNTnQMk6lJA,210
46
46
  MediLink/MediLink_Charges.py,sha256=82fnqHGvT7tfdfjucnFHiLdUE0WhHDXrcS0k_Ln3c8U,19951
47
- MediLink/MediLink_ClaimStatus.py,sha256=75LzWfYaxrLWelId-8rHHN_lchHXxAe34pRgvKPdP2o,22118
47
+ MediLink/MediLink_ClaimStatus.py,sha256=nKX7QymhCULiaGfISYw_P0Eqy0TP6qKG4C2d3TdGFVo,22720
48
48
  MediLink/MediLink_DataMgmt.py,sha256=9hc5jyWU65nYT66afDybOyYAcW-DvEYuHpWTun96U50,52407
49
49
  MediLink/MediLink_Decoder.py,sha256=1gzdybNg4Vv69s5PNbX8bPNrXT_N_kPpFpt2HpkauWA,16430
50
- MediLink/MediLink_Deductible.py,sha256=8EaJpJICcWcWL_Oge646bXE9STJhvbfIEmHl-5y-DMo,57937
51
- MediLink/MediLink_Deductible_Validator.py,sha256=1h5GJ9jbcVarxd6a-lKPEOhJYz4QkeeBlHw0Zdd9vUU,24855
52
- MediLink/MediLink_Display_Utils.py,sha256=pZqd3KRck27zzwnGh_qN1NN1Q-2FF6NFBtRPSO-syiI,22899
50
+ MediLink/MediLink_Deductible.py,sha256=jr-TrfOTxQDc4dwvwUy4jSSb_AnsU0pWLq_kyktAgZk,57942
51
+ MediLink/MediLink_Deductible_Validator.py,sha256=x6tHJOi88TblUpDPSH6QhIdXXRgr3rXI7kYPVGZYCgU,24998
52
+ MediLink/MediLink_Display_Utils.py,sha256=zIuXGmHz0aHs3FD6JVNptj0FjJuUO9i9QAx-kGsmI_s,22988
53
53
  MediLink/MediLink_Down.py,sha256=s4_z-RaqHYanjwbQCl-OSkg4XIpcIQ2Q6jXa8-q_QXw,28111
54
54
  MediLink/MediLink_Gmail.py,sha256=muqZI3girXDu92KT5Nft7dLHtpnQAjH-QfvdcGo28_Y,26801
55
55
  MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -63,15 +63,15 @@ MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJY
63
63
  MediLink/MediLink_main.py,sha256=CAXu0IRzhra3ppIFDcCppFNAZp7kCuN6gPtJSdFqGzs,24857
64
64
  MediLink/MediLink_smart_import.py,sha256=B5SfBn_4bYEWJJDolXbjnwKx_-MaqGZ76LyXQwWDV80,9838
65
65
  MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
66
- MediLink/__init__.py,sha256=rSHkkkTfid24CUGEFtuMAh8ebWQ41_Tew1vYYlb9hfo,3888
66
+ MediLink/__init__.py,sha256=PIA4bRbuHFfSmHUsG0GHI6KIAO-_nmLOfbm-tdjtOug,3888
67
67
  MediLink/gmail_http_utils.py,sha256=gtqCCrzJC7e8JFQzMNrf7EbK8na2h4sfTu-NMaZ_UHc,4006
68
68
  MediLink/gmail_oauth_utils.py,sha256=Ugr-DEqs4_RddRMSCJ_dbgA3TVeaxpbAor-dktcTIgY,3713
69
69
  MediLink/openssl.cnf,sha256=76VdcGCykf0Typyiv8Wd1mMVKixrQ5RraG6HnfKFqTo,887
70
70
  MediLink/test.py,sha256=DM_E8gEbhbVfTAm3wTMiNnK2GCD1e5eH6gwTk89QIc4,3116
71
71
  MediLink/webapp.html,sha256=MI9zZ4y1_h5Ji3nz2fmm6Q29AsPunks-wR-R8Ct73-w,19856
72
- medicafe-0.250930.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
73
- medicafe-0.250930.0.dist-info/METADATA,sha256=FAUuG1PCF_01NP77xNySzjfAvSMLtppmiLmlqANYTdw,3414
74
- medicafe-0.250930.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
75
- medicafe-0.250930.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
76
- medicafe-0.250930.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
77
- medicafe-0.250930.0.dist-info/RECORD,,
72
+ medicafe-0.251014.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
73
+ medicafe-0.251014.0.dist-info/METADATA,sha256=gmDGZvMH-ZkpsXBbZSL85cG_WmrrH8CnyeMj_ZV5Mdg,3414
74
+ medicafe-0.251014.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
75
+ medicafe-0.251014.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
76
+ medicafe-0.251014.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
77
+ medicafe-0.251014.0.dist-info/RECORD,,