medicafe 0.250728.7__tar.gz → 0.250728.9__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.
Potentially problematic release.
This version of medicafe might be problematic. Click here for more details.
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink.py +92 -10
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_837p_encoder_library.py +35 -5
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_API_v3.py +62 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_DataMgmt.py +27 -9
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_UI.py +20 -10
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Up.py +6 -1
- medicafe-0.250728.9/MediLink/MediLink_api_utils.py +305 -0
- medicafe-0.250728.9/MediLink/MediLink_insurance_utils.py +231 -0
- medicafe-0.250728.9/MediLink/__init__.py +1 -0
- medicafe-0.250728.9/MediLink/insurance_type_integration_test.py +348 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/PKG-INFO +1 -1
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/PKG-INFO +1 -1
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/SOURCES.txt +3 -1
- {medicafe-0.250728.7 → medicafe-0.250728.9}/setup.py +1 -1
- medicafe-0.250728.7/MediLink/__init__.py +0 -0
- medicafe-0.250728.7/README.md +0 -58
- {medicafe-0.250728.7 → medicafe-0.250728.9}/LICENSE +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MANIFEST.in +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot.bat +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_Crosswalk_Library.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_Preprocessor_lib.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_UI.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_dataformat_library.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/MediBot_docx_decoder.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/PDF_to_CSV_Cleaner.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/__init__.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/update_json.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_API_v2.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_APIs.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_ClaimStatus.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_ConfigLoader.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Decoder.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Deductible.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Deductible_Validator.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Gmail.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_GraphQL.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Parser.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/test.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/test_cob_library.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/test_validation.py +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/MediLink/webapp.html +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/medicafe.egg-info/top_level.txt +0 -0
- {medicafe-0.250728.7 → medicafe-0.250728.9}/setup.cfg +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# MediLink.py
|
|
2
|
-
import os, sys
|
|
2
|
+
import os, sys, time
|
|
3
3
|
import MediLink_Down
|
|
4
4
|
import MediLink_Up
|
|
5
5
|
import MediLink_ConfigLoader
|
|
@@ -7,7 +7,12 @@ import MediLink_DataMgmt
|
|
|
7
7
|
|
|
8
8
|
# For UI Functions
|
|
9
9
|
import MediLink_UI # Import UI module for handling all user interfaces
|
|
10
|
-
|
|
10
|
+
try:
|
|
11
|
+
from tqdm import tqdm
|
|
12
|
+
except ImportError:
|
|
13
|
+
# Fallback for when tqdm is not available
|
|
14
|
+
def tqdm(iterable, **kwargs):
|
|
15
|
+
return iterable
|
|
11
16
|
|
|
12
17
|
# Add parent directory of the project to the Python path
|
|
13
18
|
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
@@ -28,17 +33,25 @@ def collect_detailed_patient_data(selected_files, config, crosswalk):
|
|
|
28
33
|
"""
|
|
29
34
|
Collects detailed patient data from the selected files.
|
|
30
35
|
|
|
36
|
+
DATA FLOW CLARIFICATION:
|
|
37
|
+
This function processes fixed-width files through extract_and_suggest_endpoint(),
|
|
38
|
+
which creates data structures with 'patient_id' field (sourced from 'CHART' field).
|
|
39
|
+
This is DIFFERENT from MediBot's parse_z_dat() flow which uses 'PATID' field.
|
|
40
|
+
|
|
31
41
|
:param selected_files: List of selected file paths.
|
|
32
42
|
:param config: Configuration settings loaded from a JSON file.
|
|
33
43
|
:param crosswalk: Crosswalk data for mapping purposes.
|
|
34
|
-
:return: A list of detailed patient data.
|
|
44
|
+
:return: A list of detailed patient data with 'patient_id' field populated.
|
|
35
45
|
"""
|
|
36
46
|
detailed_patient_data = []
|
|
37
47
|
for file_path in selected_files:
|
|
48
|
+
# IMPORTANT: extract_and_suggest_endpoint creates data with 'patient_id' field
|
|
49
|
+
# sourced from the 'CHART' field in fixed-width files
|
|
38
50
|
detailed_data = extract_and_suggest_endpoint(file_path, config, crosswalk)
|
|
39
51
|
detailed_patient_data.extend(detailed_data) # Accumulate detailed data for processing
|
|
40
52
|
|
|
41
53
|
# Enrich the detailed patient data with insurance type
|
|
54
|
+
# NOTE: This receives data from extract_and_suggest_endpoint with 'patient_id' field
|
|
42
55
|
detailed_patient_data = enrich_with_insurance_type(detailed_patient_data, insurance_options)
|
|
43
56
|
|
|
44
57
|
# Display summaries and provide an option for bulk edit
|
|
@@ -49,9 +62,18 @@ def collect_detailed_patient_data(selected_files, config, crosswalk):
|
|
|
49
62
|
def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_mapping=None):
|
|
50
63
|
"""
|
|
51
64
|
Enriches the detailed patient data with insurance type based on patient ID.
|
|
65
|
+
Enhanced with optional API integration and comprehensive logging.
|
|
66
|
+
|
|
67
|
+
DATA FLOW CLARIFICATION:
|
|
68
|
+
This function receives data from collect_detailed_patient_data() -> extract_and_suggest_endpoint().
|
|
69
|
+
The patient ID field is 'patient_id' (NOT 'PATID').
|
|
70
|
+
|
|
71
|
+
IMPORTANT: Do not confuse with MediBot's parse_z_dat() flow which uses 'PATID'.
|
|
72
|
+
MediLink flow: fixed-width files -> extract_and_suggest_endpoint() -> 'patient_id' field (from 'CHART')
|
|
73
|
+
MediBot flow: Z.dat files -> parse_z_dat() -> 'PATID' field
|
|
52
74
|
|
|
53
75
|
Parameters:
|
|
54
|
-
- detailed_patient_data: List of dictionaries containing detailed patient data.
|
|
76
|
+
- detailed_patient_data: List of dictionaries containing detailed patient data with 'patient_id' field.
|
|
55
77
|
- patient_insurance_mapping: Dictionary mapping patient IDs to their insurance types.
|
|
56
78
|
|
|
57
79
|
Returns:
|
|
@@ -64,14 +86,62 @@ def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_map
|
|
|
64
86
|
UHC and which ones are not yet supported so they know which ones they need to edit. It is possible that we may want to isolate the
|
|
65
87
|
patient data that is already pulled from UHC so that the user can see which ones are already using the enriched data.
|
|
66
88
|
"""
|
|
89
|
+
# Enhanced mode check with graceful degradation
|
|
90
|
+
enhanced_mode = False
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
from MediLink_insurance_utils import (
|
|
94
|
+
get_feature_flag,
|
|
95
|
+
validate_insurance_type_from_config
|
|
96
|
+
)
|
|
97
|
+
enhanced_mode = get_feature_flag('enhanced_insurance_enrichment', default=False)
|
|
98
|
+
MediLink_ConfigLoader.log("Insurance enhancement utilities loaded successfully", level="DEBUG")
|
|
99
|
+
except ImportError as e:
|
|
100
|
+
MediLink_ConfigLoader.log("Insurance utils not available: {}. Using legacy mode.".format(str(e)), level="INFO")
|
|
101
|
+
enhanced_mode = False
|
|
102
|
+
except Exception as e:
|
|
103
|
+
MediLink_ConfigLoader.log("Error initializing insurance enhancements: {}. Using legacy mode.".format(str(e)), level="ERROR")
|
|
104
|
+
enhanced_mode = False
|
|
105
|
+
|
|
67
106
|
if patient_insurance_type_mapping is None:
|
|
68
|
-
MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.", level="
|
|
107
|
+
MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.", level="INFO")
|
|
69
108
|
patient_insurance_type_mapping = {}
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
# Enhanced mode with validation
|
|
111
|
+
if enhanced_mode:
|
|
112
|
+
MediLink_ConfigLoader.log("Using enhanced insurance type enrichment", level="INFO")
|
|
113
|
+
|
|
114
|
+
for data in detailed_patient_data:
|
|
115
|
+
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
|
116
|
+
# This field contains the value from the 'CHART' field in the original fixed-width file
|
|
117
|
+
patient_id = data.get('patient_id')
|
|
118
|
+
if patient_id:
|
|
119
|
+
raw_insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
|
120
|
+
validated_insurance_type = validate_insurance_type_from_config(raw_insurance_type, patient_id)
|
|
121
|
+
data['insurance_type'] = validated_insurance_type
|
|
122
|
+
data['insurance_type_source'] = 'MANUAL' if patient_id in patient_insurance_type_mapping else 'DEFAULT'
|
|
123
|
+
else:
|
|
124
|
+
# Handle case where patient_id is missing or empty
|
|
125
|
+
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
|
126
|
+
data['insurance_type'] = '12' # SBR09 default PPO
|
|
127
|
+
data['insurance_type_source'] = 'DEFAULT_FALLBACK'
|
|
128
|
+
|
|
129
|
+
else:
|
|
130
|
+
# Legacy mode (preserve existing behavior exactly)
|
|
131
|
+
MediLink_ConfigLoader.log("Using legacy insurance type enrichment", level="INFO")
|
|
132
|
+
for data in detailed_patient_data:
|
|
133
|
+
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
|
134
|
+
# This field contains the value from the 'CHART' field in the original fixed-width file
|
|
135
|
+
patient_id = data.get('patient_id')
|
|
136
|
+
if patient_id:
|
|
137
|
+
insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
|
138
|
+
else:
|
|
139
|
+
# Handle case where patient_id is missing or empty
|
|
140
|
+
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
|
141
|
+
insurance_type = '12' # Default when no patient ID available
|
|
142
|
+
|
|
143
|
+
data['insurance_type'] = insurance_type
|
|
144
|
+
|
|
75
145
|
return detailed_patient_data
|
|
76
146
|
|
|
77
147
|
def extract_and_suggest_endpoint(file_path, config, crosswalk):
|
|
@@ -81,6 +151,16 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
|
|
|
81
151
|
an endpoint based on insurance provider information found in the crosswalk and prepares
|
|
82
152
|
detailed patient data for processing.
|
|
83
153
|
|
|
154
|
+
DATA FLOW CLARIFICATION:
|
|
155
|
+
This function is the PRIMARY data source for MediLink patient processing.
|
|
156
|
+
It creates the 'patient_id' field by extracting the 'CHART' field from fixed-width files.
|
|
157
|
+
|
|
158
|
+
IMPORTANT: This is DIFFERENT from MediBot's parse_z_dat() which extracts 'PATID'.
|
|
159
|
+
|
|
160
|
+
Field mapping for MediLink flow:
|
|
161
|
+
- Fixed-width file 'CHART' field -> detailed_data['patient_id']
|
|
162
|
+
- This 'patient_id' is then used by enrich_with_insurance_type()
|
|
163
|
+
|
|
84
164
|
Parameters:
|
|
85
165
|
- file_path: Path to the fixed-width file.
|
|
86
166
|
- crosswalk: Crosswalk dictionary loaded from a JSON file.
|
|
@@ -135,7 +215,9 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
|
|
|
135
215
|
detailed_data = parsed_data.copy() # Copy parsed_data to avoid modifying the original dictionary
|
|
136
216
|
detailed_data.update({
|
|
137
217
|
'file_path': file_path,
|
|
138
|
-
|
|
218
|
+
# CRITICAL FIELD MAPPING: 'CHART' field from fixed-width file becomes 'patient_id'
|
|
219
|
+
# This is the field that enrich_with_insurance_type() will use
|
|
220
|
+
'patient_id': parsed_data.get('CHART'), # ← This is the key field mapping for MediLink flow
|
|
139
221
|
'surgery_date': parsed_data.get('DATE'),
|
|
140
222
|
'patient_name': ' '.join([parsed_data.get(key, '') for key in ['FIRST', 'MIDDLE', 'LAST']]),
|
|
141
223
|
'amount': parsed_data.get('AMOUNT'),
|
|
@@ -572,7 +572,8 @@ def create_sbr_segment(config, parsed_data, endpoint):
|
|
|
572
572
|
|
|
573
573
|
def insurance_type_selection(parsed_data):
|
|
574
574
|
"""
|
|
575
|
-
|
|
575
|
+
Enhanced insurance type selection with optional API integration and safe fallbacks.
|
|
576
|
+
Maintains exact same signature as existing implementation.
|
|
576
577
|
|
|
577
578
|
TODO (HIGH SBR09) Finish making this function.
|
|
578
579
|
This should eventually integrate into a menu upstream. This menu flow probably needs to be alongside the suggested endpoint flow.
|
|
@@ -602,6 +603,22 @@ def insurance_type_selection(parsed_data):
|
|
|
602
603
|
"""
|
|
603
604
|
MediLink_ConfigLoader.log("insurance_type_selection(parsed_data): {}".format(parsed_data), level="DEBUG")
|
|
604
605
|
|
|
606
|
+
# Try enhanced selection with safe fallback
|
|
607
|
+
try:
|
|
608
|
+
from MediLink_insurance_utils import safe_insurance_type_selection
|
|
609
|
+
return safe_insurance_type_selection(parsed_data, _original_insurance_type_selection_logic)
|
|
610
|
+
except ImportError as e:
|
|
611
|
+
MediLink_ConfigLoader.log("Enhanced insurance selection not available: {}. Using original logic".format(str(e)), level="INFO")
|
|
612
|
+
return _original_insurance_type_selection_logic(parsed_data)
|
|
613
|
+
except Exception as e:
|
|
614
|
+
MediLink_ConfigLoader.log("Error in enhanced insurance selection: {}. Using original logic".format(str(e)), level="ERROR")
|
|
615
|
+
return _original_insurance_type_selection_logic(parsed_data)
|
|
616
|
+
|
|
617
|
+
def _original_insurance_type_selection_logic(parsed_data):
|
|
618
|
+
"""
|
|
619
|
+
Original insurance type selection logic extracted to preserve exact behavior.
|
|
620
|
+
This ensures backward compatibility when enhanced features are not available.
|
|
621
|
+
"""
|
|
605
622
|
# Check if insurance type is already assigned and is valid
|
|
606
623
|
insurance_type_code = parsed_data.get('insurance_type')
|
|
607
624
|
if insurance_type_code and len(insurance_type_code) == 2 and insurance_type_code.isalnum():
|
|
@@ -639,11 +656,24 @@ def insurance_type_selection(parsed_data):
|
|
|
639
656
|
# User input for insurance type
|
|
640
657
|
user_input = input("Enter the 2-character code for the insurance type (or press Enter to default to '12' for PPO): ").strip().upper()
|
|
641
658
|
|
|
642
|
-
#
|
|
643
|
-
if user_input
|
|
644
|
-
|
|
659
|
+
# Input validation and assignment
|
|
660
|
+
if user_input:
|
|
661
|
+
# Basic format validation
|
|
662
|
+
if len(user_input) > 3 or not user_input.isalnum():
|
|
663
|
+
print("Invalid format: Insurance codes should be 1-3 alphanumeric characters. Defaulting to PPO.")
|
|
664
|
+
elif user_input in insurance_options:
|
|
665
|
+
insurance_type_code = user_input
|
|
666
|
+
print("Selected: {} - {}".format(user_input, insurance_options[user_input]))
|
|
667
|
+
else:
|
|
668
|
+
# User wants to use a code not in config - confirm with them
|
|
669
|
+
confirm = input("Code '{}' not found in configuration. Use it anyway? (y/n): ".format(user_input)).strip().lower()
|
|
670
|
+
if confirm in ['y', 'yes']:
|
|
671
|
+
insurance_type_code = user_input
|
|
672
|
+
print("Using code: {}".format(user_input))
|
|
673
|
+
else:
|
|
674
|
+
print("Defaulting to PPO (Preferred Provider Organization)")
|
|
645
675
|
else:
|
|
646
|
-
print("
|
|
676
|
+
print("Using default: PPO (Preferred Provider Organization)")
|
|
647
677
|
|
|
648
678
|
return insurance_type_code
|
|
649
679
|
|
|
@@ -112,6 +112,29 @@ class APIClient(BaseAPIClient):
|
|
|
112
112
|
def __init__(self):
|
|
113
113
|
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
114
114
|
super().__init__(config)
|
|
115
|
+
|
|
116
|
+
# Add enhanced features if available
|
|
117
|
+
try:
|
|
118
|
+
from MediLink_api_utils import APICircuitBreaker, APICache, APIRateLimiter
|
|
119
|
+
from MediLink_insurance_utils import get_feature_flag
|
|
120
|
+
|
|
121
|
+
# Initialize enhancements if enabled
|
|
122
|
+
enable_circuit_breaker = get_feature_flag('api_circuit_breaker', default=False)
|
|
123
|
+
enable_caching = get_feature_flag('api_caching', default=False)
|
|
124
|
+
enable_rate_limiting = get_feature_flag('api_rate_limiting', default=False)
|
|
125
|
+
|
|
126
|
+
self.circuit_breaker = APICircuitBreaker() if enable_circuit_breaker else None
|
|
127
|
+
self.api_cache = APICache() if enable_caching else None
|
|
128
|
+
self.rate_limiter = APIRateLimiter() if enable_rate_limiting else None
|
|
129
|
+
|
|
130
|
+
if any([enable_circuit_breaker, enable_caching, enable_rate_limiting]):
|
|
131
|
+
MediLink_ConfigLoader.log("Enhanced API client initialized with circuit_breaker={}, caching={}, rate_limiting={}".format(
|
|
132
|
+
enable_circuit_breaker, enable_caching, enable_rate_limiting), level="INFO")
|
|
133
|
+
except ImportError:
|
|
134
|
+
MediLink_ConfigLoader.log("API enhancements not available, using standard client", level="DEBUG")
|
|
135
|
+
self.circuit_breaker = None
|
|
136
|
+
self.api_cache = None
|
|
137
|
+
self.rate_limiter = None
|
|
115
138
|
|
|
116
139
|
def get_access_token(self, endpoint_name):
|
|
117
140
|
MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
|
|
@@ -177,6 +200,45 @@ class APIClient(BaseAPIClient):
|
|
|
177
200
|
return None
|
|
178
201
|
|
|
179
202
|
def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
|
|
203
|
+
# Try enhanced API call if available
|
|
204
|
+
if hasattr(self, 'circuit_breaker') and self.circuit_breaker:
|
|
205
|
+
try:
|
|
206
|
+
return self._make_enhanced_api_call(endpoint_name, call_type, url_extension, params, data, headers)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
MediLink_ConfigLoader.log("Enhanced API call failed, falling back to standard: {}".format(str(e)), level="WARNING")
|
|
209
|
+
|
|
210
|
+
# Standard API call logic
|
|
211
|
+
return self._make_standard_api_call(endpoint_name, call_type, url_extension, params, data, headers)
|
|
212
|
+
|
|
213
|
+
def _make_enhanced_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
|
|
214
|
+
"""Enhanced API call with circuit breaker, caching, and rate limiting"""
|
|
215
|
+
# Check cache first (for GET requests)
|
|
216
|
+
if self.api_cache and call_type == 'GET':
|
|
217
|
+
cached_result = self.api_cache.get(endpoint_name, call_type, url_extension, params)
|
|
218
|
+
if cached_result is not None:
|
|
219
|
+
MediLink_ConfigLoader.log("Cache hit for {} {} {}".format(call_type, endpoint_name, url_extension), level="DEBUG")
|
|
220
|
+
return cached_result
|
|
221
|
+
|
|
222
|
+
# Check rate limits
|
|
223
|
+
if self.rate_limiter:
|
|
224
|
+
self.rate_limiter.wait_if_needed()
|
|
225
|
+
|
|
226
|
+
# Make call with circuit breaker protection
|
|
227
|
+
result = self.circuit_breaker.call_with_breaker(
|
|
228
|
+
self._make_standard_api_call, endpoint_name, call_type, url_extension, params, data, headers)
|
|
229
|
+
|
|
230
|
+
# Record rate limit call
|
|
231
|
+
if self.rate_limiter:
|
|
232
|
+
self.rate_limiter.record_call()
|
|
233
|
+
|
|
234
|
+
# Cache result (for GET requests)
|
|
235
|
+
if self.api_cache and call_type == 'GET':
|
|
236
|
+
self.api_cache.set(result, endpoint_name, call_type, url_extension, params)
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
def _make_standard_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
|
|
241
|
+
"""Standard API call logic preserved for compatibility"""
|
|
180
242
|
token = self.get_access_token(endpoint_name)
|
|
181
243
|
if token:
|
|
182
244
|
MediLink_ConfigLoader.log("[Make API Call] Token found for {}".format(endpoint_name), level="DEBUG")
|
|
@@ -616,25 +616,43 @@ def confirm_all_suggested_endpoints(detailed_patient_data):
|
|
|
616
616
|
return detailed_patient_data
|
|
617
617
|
|
|
618
618
|
def bulk_edit_insurance_types(detailed_patient_data, insurance_options):
|
|
619
|
-
|
|
620
|
-
print("
|
|
619
|
+
"""Allow user to edit insurance types in a table-like format with validation"""
|
|
620
|
+
print("\nEdit Insurance Type (Enter the code). Enter 'LIST' to display available insurance types.")
|
|
621
621
|
|
|
622
622
|
for data in detailed_patient_data:
|
|
623
|
-
|
|
623
|
+
patient_id = data.get('patient_id', 'Unknown')
|
|
624
|
+
patient_name = data.get('patient_name', 'Unknown')
|
|
625
|
+
current_insurance_type = data.get('insurance_type', '12')
|
|
624
626
|
current_insurance_description = insurance_options.get(current_insurance_type, "Unknown")
|
|
627
|
+
|
|
625
628
|
print("({}) {:<25} | Current Ins. Type: {} - {}".format(
|
|
626
|
-
|
|
629
|
+
patient_id, patient_name, current_insurance_type, current_insurance_description))
|
|
627
630
|
|
|
628
631
|
while True:
|
|
629
|
-
new_insurance_type = input("Enter new insurance type (or press Enter to keep current): ").upper()
|
|
632
|
+
new_insurance_type = input("Enter new insurance type (or press Enter to keep current): ").strip().upper()
|
|
633
|
+
|
|
630
634
|
if new_insurance_type == 'LIST':
|
|
631
635
|
MediLink_UI.display_insurance_options(insurance_options)
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
636
|
+
continue
|
|
637
|
+
|
|
638
|
+
elif not new_insurance_type:
|
|
639
|
+
# Keep current insurance type
|
|
640
|
+
break
|
|
641
|
+
|
|
642
|
+
elif new_insurance_type in insurance_options:
|
|
643
|
+
# Valid insurance type from config
|
|
644
|
+
data['insurance_type'] = new_insurance_type
|
|
635
645
|
break
|
|
646
|
+
|
|
636
647
|
else:
|
|
637
|
-
|
|
648
|
+
# User wants to use a code not in config - confirm with them
|
|
649
|
+
confirm = input("Code '{}' not found in configuration. Use it anyway? (y/n): ".format(new_insurance_type)).strip().lower()
|
|
650
|
+
if confirm in ['y', 'yes']:
|
|
651
|
+
data['insurance_type'] = new_insurance_type
|
|
652
|
+
break
|
|
653
|
+
else:
|
|
654
|
+
print("Invalid insurance type. Please enter a valid code or type 'LIST' to see options.")
|
|
655
|
+
continue
|
|
638
656
|
|
|
639
657
|
def review_and_confirm_changes(detailed_patient_data, insurance_options):
|
|
640
658
|
# Review and confirm changes
|
|
@@ -102,14 +102,18 @@ def get_new_endpoint_choice():
|
|
|
102
102
|
|
|
103
103
|
# Function to display full list of insurance options
|
|
104
104
|
def display_insurance_options(insurance_options=None):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
"""Display insurance options, loading from config if not provided"""
|
|
106
|
+
|
|
107
|
+
if insurance_options is None:
|
|
108
|
+
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
109
|
+
insurance_options = config.get('MediLink_Config', {}).get('insurance_options', {})
|
|
109
110
|
|
|
110
|
-
print("\nInsurance Type Options:")
|
|
111
|
+
print("\nInsurance Type Options (SBR09 Codes):")
|
|
112
|
+
print("-" * 50)
|
|
111
113
|
for code, description in sorted(insurance_options.items()):
|
|
112
|
-
print("{}: {}".format(code, description))
|
|
114
|
+
print("{:>3}: {}".format(code, description))
|
|
115
|
+
print("-" * 50)
|
|
116
|
+
print("Note: '12' (PPO) is the default if no selection is made.")
|
|
113
117
|
print() # Add a blank line for better readability
|
|
114
118
|
|
|
115
119
|
def display_patient_summaries(detailed_patient_data):
|
|
@@ -130,10 +134,10 @@ def display_file_summary(index, summary):
|
|
|
130
134
|
|
|
131
135
|
# Add header row if it's the first index
|
|
132
136
|
if index == 1:
|
|
133
|
-
print("{:<3} {:5} {:<10} {:20} {:15} {:
|
|
137
|
+
print("{:<3} {:5} {:<10} {:20} {:15} {:3} {:20}".format(
|
|
134
138
|
"No.", "Date", "ID", "Name", "Primary Ins.", "IT", "Current Endpoint"
|
|
135
139
|
))
|
|
136
|
-
print("-"*
|
|
140
|
+
print("-"*82)
|
|
137
141
|
|
|
138
142
|
# Check if insurance_type is available; if not, set a default placeholder (this should already be '12' at this point)
|
|
139
143
|
insurance_type = summary.get('insurance_type', '--')
|
|
@@ -143,14 +147,20 @@ def display_file_summary(index, summary):
|
|
|
143
147
|
summary.get('user_preferred_endpoint') or
|
|
144
148
|
summary.get('suggested_endpoint', 'AVAILITY'))
|
|
145
149
|
|
|
150
|
+
# Format insurance type for display - handle both 2 and 3 character codes
|
|
151
|
+
if insurance_type and len(insurance_type) <= 3:
|
|
152
|
+
insurance_display = insurance_type
|
|
153
|
+
else:
|
|
154
|
+
insurance_display = insurance_type[:3] if insurance_type else '--'
|
|
155
|
+
|
|
146
156
|
# Displays the summary of a file.
|
|
147
|
-
print("{:02d}. {:5} ({:<8}) {:20} {:15} {:
|
|
157
|
+
print("{:02d}. {:5} ({:<8}) {:20} {:15} {:3} {:20}".format(
|
|
148
158
|
index,
|
|
149
159
|
surgery_date.strftime("%m-%d"),
|
|
150
160
|
summary['patient_id'],
|
|
151
161
|
summary['patient_name'][:20],
|
|
152
162
|
summary['primary_insurance'][:15],
|
|
153
|
-
|
|
163
|
+
insurance_display,
|
|
154
164
|
effective_endpoint[:20])
|
|
155
165
|
)
|
|
156
166
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# MediLink_Up.py
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import os, re, subprocess, traceback
|
|
4
|
-
|
|
4
|
+
try:
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
except ImportError:
|
|
7
|
+
# Fallback for when tqdm is not available
|
|
8
|
+
def tqdm(iterable, **kwargs):
|
|
9
|
+
return iterable
|
|
5
10
|
import MediLink_837p_encoder
|
|
6
11
|
from MediLink_ConfigLoader import log, load_configuration
|
|
7
12
|
from MediLink_DataMgmt import operate_winscp
|