medicafe 0.251027.1__py3-none-any.whl → 0.251027.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of medicafe might be problematic. Click here for more details.
- MediBot/MediBot.bat +36 -6
- MediBot/__init__.py +1 -1
- MediCafe/__init__.py +1 -1
- MediCafe/__main__.py +6 -4
- MediCafe/error_reporter.py +323 -81
- MediLink/MediLink_Gmail.py +65 -0
- MediLink/__init__.py +1 -1
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/METADATA +1 -1
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/RECORD +13 -13
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/LICENSE +0 -0
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/WHEEL +0 -0
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251027.1.dist-info → medicafe-0.251027.3.dist-info}/top_level.txt +0 -0
MediBot/MediBot.bat
CHANGED
|
@@ -729,18 +729,20 @@ echo 1. Open Latest Log File
|
|
|
729
729
|
echo 2. Open WinSCP Logs
|
|
730
730
|
echo 3. Clear Python Cache
|
|
731
731
|
echo 4. Toggle Performance Logging ^(session^)
|
|
732
|
-
echo 5.
|
|
733
|
-
echo 6.
|
|
734
|
-
echo 7.
|
|
732
|
+
echo 5. Send TEST error report (email)
|
|
733
|
+
echo 6. Forced MediCafe version rollback
|
|
734
|
+
echo 7. Process CSV Files
|
|
735
|
+
echo 8. Back to Main Menu
|
|
735
736
|
echo.
|
|
736
737
|
set /p tchoice=Enter your choice:
|
|
737
738
|
if "%tchoice%"=="1" goto open_latest_log
|
|
738
739
|
if "%tchoice%"=="2" goto open_winscp_logs
|
|
739
740
|
if "%tchoice%"=="3" goto clear_cache_menu
|
|
740
741
|
if "%tchoice%"=="4" goto toggle_perf_logging
|
|
741
|
-
if "%tchoice%"=="5" goto
|
|
742
|
-
if "%tchoice%"=="6" goto
|
|
743
|
-
if "%tchoice%"=="7" goto
|
|
742
|
+
if "%tchoice%"=="5" goto send_test_error_report
|
|
743
|
+
if "%tchoice%"=="6" goto forced_version_rollback
|
|
744
|
+
if "%tchoice%"=="7" goto process_csvs
|
|
745
|
+
if "%tchoice%"=="8" goto main_menu
|
|
744
746
|
echo Invalid choice. Please try again.
|
|
745
747
|
pause
|
|
746
748
|
goto troubleshooting_menu
|
|
@@ -788,6 +790,34 @@ if "%_winscp_found%"=="0" (
|
|
|
788
790
|
pause >nul
|
|
789
791
|
goto troubleshooting_menu
|
|
790
792
|
|
|
793
|
+
:::: Send TEST error report via MediCafe CLI
|
|
794
|
+
:send_test_error_report
|
|
795
|
+
cls
|
|
796
|
+
echo ========================================
|
|
797
|
+
echo Send TEST Error Report (no real traceback)
|
|
798
|
+
echo ========================================
|
|
799
|
+
echo.
|
|
800
|
+
if "!internet_available!"=="0" (
|
|
801
|
+
echo [WARNING] No internet connection available.
|
|
802
|
+
echo This feature requires internet to email the test report.
|
|
803
|
+
pause >nul
|
|
804
|
+
goto troubleshooting_menu
|
|
805
|
+
)
|
|
806
|
+
echo Building and sending test bundle via MediCafe...
|
|
807
|
+
cd /d "%~dp0.."
|
|
808
|
+
python -m MediCafe send_test_error_report
|
|
809
|
+
if errorlevel 1 (
|
|
810
|
+
echo.
|
|
811
|
+
echo [ERROR] Test error report failed to send.
|
|
812
|
+
echo The bundle, if created, remains in reports_queue for manual retry.
|
|
813
|
+
pause >nul
|
|
814
|
+
goto troubleshooting_menu
|
|
815
|
+
)
|
|
816
|
+
echo.
|
|
817
|
+
echo [OK] Test error report sent.
|
|
818
|
+
pause >nul
|
|
819
|
+
goto troubleshooting_menu
|
|
820
|
+
|
|
791
821
|
::: End Script
|
|
792
822
|
:end_script
|
|
793
823
|
echo Exiting MediBot
|
MediBot/__init__.py
CHANGED
MediCafe/__init__.py
CHANGED
MediCafe/__main__.py
CHANGED
|
@@ -15,6 +15,7 @@ Commands:
|
|
|
15
15
|
claims_status - Run United Claims Status checker
|
|
16
16
|
deductible - Run United Deductible checker
|
|
17
17
|
download_emails - Run email download functionality
|
|
18
|
+
send_test_error_report - Create and email a TEST support bundle
|
|
18
19
|
version - Show MediCafe version information
|
|
19
20
|
|
|
20
21
|
The entry point preserves user choices and navigational flow from the
|
|
@@ -252,9 +253,6 @@ def run_download_emails():
|
|
|
252
253
|
except ImportError as e:
|
|
253
254
|
print("Error: Unable to import MediLink_Gmail: {}".format(e))
|
|
254
255
|
return 1
|
|
255
|
-
except Exception as e:
|
|
256
|
-
print("Error running email download: {}".format(e))
|
|
257
|
-
return 1
|
|
258
256
|
|
|
259
257
|
def show_version():
|
|
260
258
|
"""Show MediCafe version information"""
|
|
@@ -280,7 +278,7 @@ def main():
|
|
|
280
278
|
|
|
281
279
|
parser.add_argument(
|
|
282
280
|
'command',
|
|
283
|
-
choices=['medibot', 'medilink', 'claims_status', 'deductible', 'download_emails', 'version'],
|
|
281
|
+
choices=['medibot', 'medilink', 'claims_status', 'deductible', 'download_emails', 'send_test_error_report', 'version'],
|
|
284
282
|
help='Command to execute'
|
|
285
283
|
)
|
|
286
284
|
|
|
@@ -309,6 +307,10 @@ def main():
|
|
|
309
307
|
return run_deductible()
|
|
310
308
|
elif args.command == 'download_emails':
|
|
311
309
|
return run_download_emails()
|
|
310
|
+
elif args.command == 'send_test_error_report':
|
|
311
|
+
# Import lazily and call directly to avoid middlemen
|
|
312
|
+
from MediCafe.error_reporter import email_test_error_report_flow
|
|
313
|
+
return email_test_error_report_flow()
|
|
312
314
|
elif args.command == 'version':
|
|
313
315
|
return show_version()
|
|
314
316
|
else:
|
MediCafe/error_reporter.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import hashlib
|
|
3
2
|
import json
|
|
4
3
|
import os
|
|
5
4
|
import platform
|
|
@@ -8,6 +7,7 @@ import time
|
|
|
8
7
|
import zipfile
|
|
9
8
|
|
|
10
9
|
import requests
|
|
10
|
+
import traceback
|
|
11
11
|
|
|
12
12
|
from email.mime.application import MIMEApplication
|
|
13
13
|
from email.mime.multipart import MIMEMultipart
|
|
@@ -99,72 +99,153 @@ def _ensure_dir(path):
|
|
|
99
99
|
return False
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
# Resolve a writable queue directory with fallback to ./reports_queue
|
|
103
|
+
def _resolve_queue_dir(medi_config):
|
|
104
|
+
try:
|
|
105
|
+
local_storage_path = medi_config.get('local_storage_path', '.') if isinstance(medi_config, dict) else '.'
|
|
106
|
+
except Exception:
|
|
107
|
+
local_storage_path = '.'
|
|
108
|
+
primary = os.path.join(local_storage_path, 'reports_queue')
|
|
109
|
+
if _ensure_dir(primary):
|
|
110
|
+
return primary
|
|
111
|
+
fallback = os.path.join('.', 'reports_queue')
|
|
112
|
+
if _ensure_dir(fallback):
|
|
113
|
+
try:
|
|
114
|
+
mc_log("Queue directory fallback to ./reports_queue due to path/permission issue.", level="WARNING")
|
|
115
|
+
print("Falling back to ./reports_queue for support bundles (check local_storage_path permissions).")
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return fallback
|
|
119
|
+
return primary
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _build_support_zip(zip_path, local_storage_path, max_log_lines, traceback_text, include_winscp, meta):
|
|
123
|
+
latest_log = _get_latest_log_path(local_storage_path)
|
|
124
|
+
log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
|
|
125
|
+
log_tail = _redact(log_tail)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
|
|
129
|
+
z.writestr('meta.json', json.dumps(meta, ensure_ascii=True, indent=2))
|
|
130
|
+
if latest_log and log_tail:
|
|
131
|
+
z.writestr('log_tail.txt', log_tail)
|
|
132
|
+
if traceback_text:
|
|
133
|
+
z.writestr('traceback.txt', _redact(traceback_text))
|
|
134
|
+
if include_winscp:
|
|
135
|
+
upload_log = os.path.join(local_storage_path, 'winscp_upload.log')
|
|
136
|
+
download_log = os.path.join(local_storage_path, 'winscp_download.log')
|
|
137
|
+
winscp_logs = [(p, os.path.getmtime(p)) for p in [upload_log, download_log] if os.path.exists(p)]
|
|
138
|
+
if winscp_logs:
|
|
139
|
+
latest_winscp = max(winscp_logs, key=lambda x: x[1])[0]
|
|
140
|
+
winscp_tail = _tail_file(latest_winscp, max_log_lines) if latest_winscp else ''
|
|
141
|
+
winscp_tail = _redact(winscp_tail)
|
|
142
|
+
if winscp_tail:
|
|
143
|
+
z.writestr('winscp_log_tail.txt', winscp_tail)
|
|
144
|
+
return True
|
|
145
|
+
except Exception as e:
|
|
146
|
+
mc_log('Error creating support bundle at {}: {}'.format(zip_path, e), level='ERROR')
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Centralized self-healing helpers for email reporting
|
|
150
|
+
def _attempt_gmail_reauth_interactive(max_wait_seconds=120):
|
|
151
|
+
"""
|
|
152
|
+
Delegate to MediLink.MediLink_Gmail.ensure_authenticated_for_gmail_send to
|
|
153
|
+
reuse existing OAuth/server logic without duplicating implementations.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
from MediLink.MediLink_Gmail import ensure_authenticated_for_gmail_send
|
|
157
|
+
return bool(ensure_authenticated_for_gmail_send(max_wait_seconds=max_wait_seconds))
|
|
158
|
+
except Exception as e:
|
|
159
|
+
try:
|
|
160
|
+
mc_log("Failed to initiate Gmail re-authorization: {0}".format(e), level="ERROR")
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
return False
|
|
164
|
+
|
|
102
165
|
# _compute_report_id removed (unused)
|
|
103
166
|
|
|
104
167
|
|
|
105
168
|
def collect_support_bundle(include_traceback=True, max_log_lines=500):
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
_ensure_dir(queue_dir)
|
|
111
|
-
|
|
112
|
-
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
113
|
-
bundle_name = 'support_report_{}.zip'.format(stamp)
|
|
114
|
-
zip_path = os.path.join(queue_dir, bundle_name)
|
|
115
|
-
|
|
116
|
-
latest_log = _get_latest_log_path(local_storage_path)
|
|
117
|
-
log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
|
|
118
|
-
log_tail = _redact(log_tail)
|
|
119
|
-
|
|
120
|
-
traceback_txt = ''
|
|
121
|
-
if include_traceback:
|
|
122
|
-
try:
|
|
123
|
-
trace_path = os.path.join(local_storage_path, 'traceback.txt')
|
|
124
|
-
if os.path.exists(trace_path):
|
|
125
|
-
with open(trace_path, 'r') as tf:
|
|
126
|
-
traceback_txt = _redact(tf.read())
|
|
127
|
-
except Exception:
|
|
128
|
-
traceback_txt = ''
|
|
129
|
-
|
|
130
|
-
meta = {
|
|
131
|
-
'app_version': _safe_ascii(_get_version()),
|
|
132
|
-
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
133
|
-
'platform': _safe_ascii(platform.platform()),
|
|
134
|
-
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
135
|
-
'error_summary': _safe_ascii(_first_line(traceback_txt)),
|
|
136
|
-
'traceback_present': bool(traceback_txt),
|
|
137
|
-
'config_flags': {
|
|
138
|
-
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
139
|
-
'test_mode': bool(medi.get('TestMode', False))
|
|
140
|
-
}
|
|
141
|
-
}
|
|
169
|
+
config, _ = load_configuration()
|
|
170
|
+
medi = config.get('MediLink_Config', {})
|
|
171
|
+
local_storage_path = medi.get('local_storage_path', '.')
|
|
172
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
142
173
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
z.writestr('meta.json', json.dumps(meta, ensure_ascii=True, indent=2))
|
|
146
|
-
if latest_log and log_tail:
|
|
147
|
-
z.writestr('log_tail.txt', log_tail)
|
|
148
|
-
if traceback_txt:
|
|
149
|
-
z.writestr('traceback.txt', traceback_txt)
|
|
150
|
-
# Get latest WinSCP log
|
|
151
|
-
upload_log = os.path.join(local_storage_path, 'winscp_upload.log')
|
|
152
|
-
download_log = os.path.join(local_storage_path, 'winscp_download.log')
|
|
153
|
-
winscp_logs = [(p, os.path.getmtime(p)) for p in [upload_log, download_log] if os.path.exists(p)]
|
|
154
|
-
if winscp_logs:
|
|
155
|
-
latest_winscp = max(winscp_logs, key=lambda x: x[1])[0]
|
|
156
|
-
winscp_tail = _tail_file(latest_winscp, max_log_lines) if latest_winscp else ''
|
|
157
|
-
winscp_tail = _redact(winscp_tail)
|
|
158
|
-
else:
|
|
159
|
-
winscp_tail = ''
|
|
160
|
-
if winscp_tail:
|
|
161
|
-
z.writestr('winscp_log_tail.txt', winscp_tail)
|
|
162
|
-
except Exception as e:
|
|
163
|
-
mc_log('Error creating support bundle: {}'.format(e), level='ERROR')
|
|
164
|
-
return None
|
|
174
|
+
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
175
|
+
zip_path = os.path.join(queue_dir, 'support_report_{}.zip'.format(stamp))
|
|
165
176
|
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
traceback_txt = ''
|
|
178
|
+
if include_traceback:
|
|
179
|
+
try:
|
|
180
|
+
trace_path = os.path.join(local_storage_path, 'traceback.txt')
|
|
181
|
+
if os.path.exists(trace_path):
|
|
182
|
+
with open(trace_path, 'r') as tf:
|
|
183
|
+
traceback_txt = tf.read()
|
|
184
|
+
except Exception:
|
|
185
|
+
traceback_txt = ''
|
|
186
|
+
|
|
187
|
+
meta = {
|
|
188
|
+
'app_version': _safe_ascii(_get_version()),
|
|
189
|
+
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
190
|
+
'platform': _safe_ascii(platform.platform()),
|
|
191
|
+
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
192
|
+
'error_summary': _safe_ascii(_first_line(traceback_txt)),
|
|
193
|
+
'traceback_present': bool(traceback_txt),
|
|
194
|
+
'config_flags': {
|
|
195
|
+
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
196
|
+
'test_mode': bool(medi.get('TestMode', False))
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, traceback_txt, True, meta)
|
|
201
|
+
return zip_path if ok else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def collect_test_support_bundle(max_log_lines=500):
|
|
205
|
+
"""
|
|
206
|
+
Build a support bundle using the latest available logs and a placeholder
|
|
207
|
+
(fake) traceback to exercise the reporting pipeline without exposing
|
|
208
|
+
real exception data.
|
|
209
|
+
|
|
210
|
+
Returns absolute path to the created ZIP, or None on failure.
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
config, _ = load_configuration()
|
|
214
|
+
medi = config.get('MediLink_Config', {})
|
|
215
|
+
local_storage_path = medi.get('local_storage_path', '.')
|
|
216
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
217
|
+
|
|
218
|
+
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
219
|
+
zip_path = os.path.join(queue_dir, 'support_report_TEST_{}.zip'.format(stamp))
|
|
220
|
+
|
|
221
|
+
# Build a placeholder traceback - ASCII-only, no real data
|
|
222
|
+
fake_tb = (
|
|
223
|
+
"Traceback (most recent call last):\n"
|
|
224
|
+
" File \"MediCafe/test_runner.py\", line 42, in <module>\n"
|
|
225
|
+
" File \"MediCafe/error_reporter.py\", line 123, in simulate_error\n"
|
|
226
|
+
"Exception: This is a TEST placeholder traceback for pipeline verification only.\n"
|
|
227
|
+
"-- No real patient or PHI data is included. --\n"
|
|
228
|
+
)
|
|
229
|
+
meta = {
|
|
230
|
+
'app_version': _safe_ascii(_get_version()),
|
|
231
|
+
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
232
|
+
'platform': _safe_ascii(platform.platform()),
|
|
233
|
+
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
234
|
+
'error_summary': 'TEST: Placeholder traceback',
|
|
235
|
+
'traceback_present': True,
|
|
236
|
+
'config_flags': {
|
|
237
|
+
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
238
|
+
'test_mode': True
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, fake_tb, True, meta)
|
|
242
|
+
return zip_path if ok else None
|
|
243
|
+
except Exception as e:
|
|
244
|
+
try:
|
|
245
|
+
mc_log('Error creating TEST support bundle: {}'.format(e), level='ERROR')
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
return None
|
|
168
249
|
|
|
169
250
|
|
|
170
251
|
def _first_line(text):
|
|
@@ -192,12 +273,11 @@ def capture_unhandled_traceback(exc_type, exc_value, exc_traceback):
|
|
|
192
273
|
medi = config.get('MediLink_Config', {})
|
|
193
274
|
local_storage_path = medi.get('local_storage_path', '.')
|
|
194
275
|
trace_path = os.path.join(local_storage_path, 'traceback.txt')
|
|
195
|
-
import traceback
|
|
196
276
|
text = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
|
|
197
277
|
text = _redact(text)
|
|
198
278
|
with open(trace_path, 'w') as f:
|
|
199
279
|
f.write(text)
|
|
200
|
-
|
|
280
|
+
print("An error occurred. A traceback was saved to {}".format(trace_path))
|
|
201
281
|
except Exception:
|
|
202
282
|
try:
|
|
203
283
|
mc_log('Failed to capture traceback to file', level='WARNING')
|
|
@@ -209,6 +289,10 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
209
289
|
zip_path = collect_support_bundle(include_traceback)
|
|
210
290
|
if not zip_path:
|
|
211
291
|
mc_log("Failed to create bundle.", level="ERROR")
|
|
292
|
+
try:
|
|
293
|
+
print("Failed to create support bundle. Ensure 'MediLink_Config.local_storage_path' is writable and logs exist.")
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
212
296
|
return False
|
|
213
297
|
bundle_size = os.path.getsize(zip_path)
|
|
214
298
|
config, _ = load_configuration()
|
|
@@ -220,18 +304,56 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
220
304
|
max_bytes = 1572864
|
|
221
305
|
if bundle_size > max_bytes:
|
|
222
306
|
mc_log("Bundle too large ({} bytes > {} bytes) - leaving in queue.".format(bundle_size, max_bytes), level="WARNING")
|
|
307
|
+
try:
|
|
308
|
+
print("Bundle too large to email ({} KB > {} KB). Left in 'reports_queue'. Path: {}".format(int(bundle_size/1024), int(max_bytes/1024), zip_path))
|
|
309
|
+
print("Attempting to create and send a smaller LITE bundle (reduced logs, no traceback).")
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
# Attempt a smaller bundle automatically
|
|
313
|
+
lite_zip = collect_support_bundle_lite(max_log_lines=200)
|
|
314
|
+
if lite_zip and os.path.exists(lite_zip):
|
|
315
|
+
try:
|
|
316
|
+
lite_size = os.path.getsize(lite_zip)
|
|
317
|
+
except Exception:
|
|
318
|
+
lite_size = -1
|
|
319
|
+
if 0 < lite_size <= max_bytes:
|
|
320
|
+
return submit_support_bundle_email(zip_path=lite_zip, include_traceback=False)
|
|
321
|
+
else:
|
|
322
|
+
try:
|
|
323
|
+
print("LITE bundle is still too large ({} KB).".format(int(max(lite_size, 0)/1024)))
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
try:
|
|
327
|
+
print("Tip: Reduce log size or increase 'MediLink_Config.error_reporting.email.max_bundle_bytes'.")
|
|
328
|
+
except Exception:
|
|
329
|
+
pass
|
|
223
330
|
return False
|
|
224
331
|
# Feature is always available; proceed if recipients and token are available
|
|
225
332
|
# Normalize and validate recipients
|
|
226
333
|
to_emails = _normalize_recipients(email_config.get('to', []))
|
|
227
334
|
if not to_emails:
|
|
228
335
|
mc_log("No valid recipients configured in error_reporting.email.to", level="ERROR")
|
|
336
|
+
try:
|
|
337
|
+
print("No recipients configured. Set 'MediLink_Config.error_reporting.email.to' to one or more email addresses.")
|
|
338
|
+
except Exception:
|
|
339
|
+
pass
|
|
229
340
|
return False
|
|
230
341
|
subject_prefix = email_config.get('subject_prefix', 'MediCafe Error Report')
|
|
231
342
|
access_token = get_access_token()
|
|
232
343
|
if not access_token:
|
|
233
|
-
mc_log("No access token -
|
|
234
|
-
|
|
344
|
+
mc_log("No access token - attempting Gmail re-authorization.", level="ERROR")
|
|
345
|
+
try:
|
|
346
|
+
print("No Gmail token found. Starting re-authorization...")
|
|
347
|
+
except Exception:
|
|
348
|
+
pass
|
|
349
|
+
if _attempt_gmail_reauth_interactive():
|
|
350
|
+
access_token = get_access_token()
|
|
351
|
+
if not access_token:
|
|
352
|
+
try:
|
|
353
|
+
print("Authentication incomplete. Please finish Gmail consent, then retry.")
|
|
354
|
+
except Exception:
|
|
355
|
+
pass
|
|
356
|
+
return False
|
|
235
357
|
mc_log("Building email...", level="INFO")
|
|
236
358
|
msg = MIMEMultipart()
|
|
237
359
|
msg['To'] = ', '.join(to_emails)
|
|
@@ -246,7 +368,16 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
246
368
|
mc_log("Sending report...", level="INFO")
|
|
247
369
|
headers = {'Authorization': 'Bearer {}'.format(access_token), 'Content-Type': 'application/json'}
|
|
248
370
|
data = {'raw': raw}
|
|
249
|
-
|
|
371
|
+
try:
|
|
372
|
+
resp = requests.post('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', headers=headers, json=data)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
mc_log("Network error during Gmail send: {0}".format(e), level="ERROR")
|
|
375
|
+
try:
|
|
376
|
+
print("Network error while sending report: {0}".format(e))
|
|
377
|
+
print("Check internet connectivity or proxy settings and retry.")
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
return False
|
|
250
381
|
if resp.status_code == 200:
|
|
251
382
|
mc_log("Report sent successfully!", level="INFO")
|
|
252
383
|
try:
|
|
@@ -255,20 +386,62 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
255
386
|
pass
|
|
256
387
|
return True
|
|
257
388
|
else:
|
|
258
|
-
# Handle auth errors by prompting re-consent using existing OAuth helpers
|
|
389
|
+
# Handle auth errors by prompting re-consent using existing OAuth helpers, then retry once
|
|
259
390
|
if resp.status_code in (401, 403):
|
|
391
|
+
mc_log("Gmail send unauthorized ({}). Attempting re-authorization and retry.".format(resp.status_code), level="WARNING")
|
|
260
392
|
try:
|
|
261
|
-
|
|
262
|
-
auth_url = get_authorization_url()
|
|
263
|
-
mc_log("Gmail send unauthorized ({}). Opening browser for re-consent.".format(resp.status_code), level="WARNING")
|
|
264
|
-
try:
|
|
265
|
-
print("Your Google session needs to be refreshed to gain gmail.send permission. A browser will open to re-authorize.")
|
|
266
|
-
except Exception:
|
|
267
|
-
pass
|
|
268
|
-
open_browser_with_executable(auth_url)
|
|
393
|
+
print("Gmail permission issue detected ({}). Starting re-authorization...".format(resp.status_code))
|
|
269
394
|
except Exception:
|
|
270
395
|
pass
|
|
396
|
+
if _attempt_gmail_reauth_interactive():
|
|
397
|
+
new_token = get_access_token()
|
|
398
|
+
if new_token:
|
|
399
|
+
headers['Authorization'] = 'Bearer {}'.format(new_token)
|
|
400
|
+
try:
|
|
401
|
+
resp = requests.post('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', headers=headers, json=data)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
mc_log("Network error on retry: {0}".format(e), level="ERROR")
|
|
404
|
+
try:
|
|
405
|
+
print("Network error on retry: {0}".format(e))
|
|
406
|
+
except Exception:
|
|
407
|
+
pass
|
|
408
|
+
return False
|
|
409
|
+
if resp.status_code == 200:
|
|
410
|
+
mc_log("Report sent successfully after re-authorization!", level="INFO")
|
|
411
|
+
try:
|
|
412
|
+
os.remove(zip_path)
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
return True
|
|
416
|
+
# Map common Gmail errors to actionable hints
|
|
417
|
+
hint = ''
|
|
418
|
+
try:
|
|
419
|
+
body = resp.json()
|
|
420
|
+
err = body.get('error', {}) if isinstance(body, dict) else {}
|
|
421
|
+
status = (err.get('status') or '').upper()
|
|
422
|
+
message = err.get('message') or ''
|
|
423
|
+
reasons = ','.join([e.get('reason') for e in err.get('errors', []) if isinstance(e, dict) and e.get('reason')])
|
|
424
|
+
if 'RATELIMIT' in status or 'rateLimitExceeded' in reasons:
|
|
425
|
+
hint = 'Quota exceeded. Wait and retry later.'
|
|
426
|
+
elif 'DAILY' in status or 'dailyLimitExceeded' in reasons:
|
|
427
|
+
hint = 'Daily quota exceeded. Try again tomorrow.'
|
|
428
|
+
elif status in ('PERMISSION_DENIED', 'FORBIDDEN'):
|
|
429
|
+
hint = 'Permissions insufficient. Re-authorize Gmail with required scopes.'
|
|
430
|
+
elif status == 'INVALID_ARGUMENT':
|
|
431
|
+
hint = 'Invalid request. Check recipient emails and attachment size.'
|
|
432
|
+
elif status == 'UNAUTHENTICATED':
|
|
433
|
+
hint = 'Authentication required. Re-authorize and retry.'
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
271
436
|
mc_log("Failed to send: {} - {}".format(resp.status_code, _redact(resp.text)), level="ERROR")
|
|
437
|
+
try:
|
|
438
|
+
base_msg = "Failed to send report: HTTP {}.".format(resp.status_code)
|
|
439
|
+
if hint:
|
|
440
|
+
base_msg += " Hint: {}".format(hint)
|
|
441
|
+
print(base_msg)
|
|
442
|
+
print("The bundle remains in 'reports_queue'. See latest log for details.")
|
|
443
|
+
except Exception:
|
|
444
|
+
pass
|
|
272
445
|
# Preserve bundle in queue for manual retry
|
|
273
446
|
return False
|
|
274
447
|
|
|
@@ -277,7 +450,8 @@ def _normalize_recipients(to_field):
|
|
|
277
450
|
try:
|
|
278
451
|
# Flatten to a list of strings
|
|
279
452
|
if isinstance(to_field, str):
|
|
280
|
-
|
|
453
|
+
separators_normalized = to_field.replace(';', ',').replace('\n', ',').replace('\r', ',')
|
|
454
|
+
candidates = [p.strip() for p in separators_normalized.split(',')]
|
|
281
455
|
elif isinstance(to_field, list):
|
|
282
456
|
candidates = [str(p).strip() for p in to_field]
|
|
283
457
|
else:
|
|
@@ -301,16 +475,48 @@ def _normalize_recipients(to_field):
|
|
|
301
475
|
return []
|
|
302
476
|
|
|
303
477
|
|
|
478
|
+
def collect_support_bundle_lite(max_log_lines=200):
|
|
479
|
+
"""Create a smaller 'lite' support bundle with reduced logs and no traceback/winscp logs."""
|
|
480
|
+
try:
|
|
481
|
+
config, _ = load_configuration()
|
|
482
|
+
medi = config.get('MediLink_Config', {})
|
|
483
|
+
local_storage_path = medi.get('local_storage_path', '.')
|
|
484
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
485
|
+
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
486
|
+
zip_path = os.path.join(queue_dir, 'support_report_LITE_{}.zip'.format(stamp))
|
|
487
|
+
meta = {
|
|
488
|
+
'app_version': _safe_ascii(_get_version()),
|
|
489
|
+
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
490
|
+
'platform': _safe_ascii(platform.platform()),
|
|
491
|
+
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
492
|
+
'error_summary': 'LITE: No traceback included',
|
|
493
|
+
'traceback_present': False,
|
|
494
|
+
'config_flags': {
|
|
495
|
+
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
496
|
+
'test_mode': bool(medi.get('TestMode', False))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, None, False, meta)
|
|
500
|
+
return zip_path if ok else None
|
|
501
|
+
except Exception:
|
|
502
|
+
return None
|
|
503
|
+
|
|
504
|
+
|
|
304
505
|
def list_queued_bundles():
|
|
305
506
|
try:
|
|
306
507
|
config, _ = load_configuration()
|
|
307
508
|
medi = config.get('MediLink_Config', {})
|
|
308
509
|
local_storage_path = medi.get('local_storage_path', '.')
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
510
|
+
primary = os.path.join(local_storage_path, 'reports_queue')
|
|
511
|
+
fallback = os.path.join('.', 'reports_queue')
|
|
512
|
+
files = []
|
|
513
|
+
for q in (primary, fallback):
|
|
514
|
+
try:
|
|
515
|
+
if os.path.isdir(q):
|
|
516
|
+
files.extend([os.path.join(q, f) for f in os.listdir(q) if f.endswith('.zip')])
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
files = sorted(set(files))
|
|
314
520
|
return files
|
|
315
521
|
except Exception:
|
|
316
522
|
return []
|
|
@@ -354,6 +560,42 @@ def email_error_report_flow():
|
|
|
354
560
|
return 0 if sent else 1
|
|
355
561
|
except Exception as e:
|
|
356
562
|
mc_log("[ERROR] Exception during email report flow: {0}".format(e), level="ERROR")
|
|
563
|
+
try:
|
|
564
|
+
print("Unexpected error while sending error report: {0}".format(e))
|
|
565
|
+
except Exception:
|
|
566
|
+
pass
|
|
567
|
+
return 1
|
|
568
|
+
|
|
569
|
+
def email_test_error_report_flow():
|
|
570
|
+
"""
|
|
571
|
+
Create and send a TEST error report bundle, containing a placeholder
|
|
572
|
+
traceback and latest log tails. Intended for troubleshooting the
|
|
573
|
+
submission pipeline only.
|
|
574
|
+
"""
|
|
575
|
+
try:
|
|
576
|
+
zip_path = collect_test_support_bundle()
|
|
577
|
+
if not zip_path:
|
|
578
|
+
try:
|
|
579
|
+
print("Failed to create TEST support bundle. Ensure 'MediLink_Config.local_storage_path' is writable.")
|
|
580
|
+
except Exception:
|
|
581
|
+
pass
|
|
582
|
+
return 1
|
|
583
|
+
sent = submit_support_bundle_email(zip_path=zip_path, include_traceback=False)
|
|
584
|
+
if not sent:
|
|
585
|
+
try:
|
|
586
|
+
print("TEST error report was not sent. See messages above for the reason. The ZIP remains queued if creation succeeded.")
|
|
587
|
+
except Exception:
|
|
588
|
+
pass
|
|
589
|
+
return 0 if sent else 1
|
|
590
|
+
except Exception as e:
|
|
591
|
+
try:
|
|
592
|
+
mc_log("[ERROR] Exception during test email report flow: {0}".format(e), level="ERROR")
|
|
593
|
+
except Exception:
|
|
594
|
+
pass
|
|
595
|
+
try:
|
|
596
|
+
print("Unexpected error during TEST report flow: {0}".format(e))
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
357
599
|
return 1
|
|
358
600
|
|
|
359
601
|
if __name__ == "__main__":
|
MediLink/MediLink_Gmail.py
CHANGED
|
@@ -594,6 +594,71 @@ def clear_token_cache():
|
|
|
594
594
|
def check_invalid_grant_causes(auth_code):
|
|
595
595
|
log("FUTURE IMPLEMENTATION: Checking common causes for invalid_grant error with auth code: {}".format(auth_code))
|
|
596
596
|
|
|
597
|
+
|
|
598
|
+
def ensure_authenticated_for_gmail_send(max_wait_seconds=120):
|
|
599
|
+
"""Ensure a valid Gmail access token is available for sending.
|
|
600
|
+
|
|
601
|
+
- Reuses existing OAuth helpers in this module.
|
|
602
|
+
- Starts the local HTTPS server if needed, opens the browser for consent,
|
|
603
|
+
and polls for a token for up to max_wait_seconds.
|
|
604
|
+
- Returns True if a usable access token is available after the flow; otherwise False.
|
|
605
|
+
"""
|
|
606
|
+
try:
|
|
607
|
+
token = get_access_token()
|
|
608
|
+
except Exception:
|
|
609
|
+
token = None
|
|
610
|
+
if token:
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
# Prepare server and certificates
|
|
614
|
+
try:
|
|
615
|
+
generate_self_signed_cert(cert_file, key_file)
|
|
616
|
+
except Exception as e:
|
|
617
|
+
log("Warning: could not ensure self-signed certs: {}".format(e))
|
|
618
|
+
|
|
619
|
+
server_started_here = False
|
|
620
|
+
global httpd
|
|
621
|
+
try:
|
|
622
|
+
if httpd is None:
|
|
623
|
+
log("Starting local HTTPS server for OAuth redirect handling.")
|
|
624
|
+
server_thread = Thread(target=run_server)
|
|
625
|
+
server_thread.daemon = True
|
|
626
|
+
server_thread.start()
|
|
627
|
+
server_started_here = True
|
|
628
|
+
time.sleep(0.5)
|
|
629
|
+
except Exception as e:
|
|
630
|
+
log("Failed to start OAuth local server: {}".format(e))
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
auth_url = get_authorization_url()
|
|
634
|
+
print("Opening browser to authorize Gmail permission for sending...")
|
|
635
|
+
open_browser_with_executable(auth_url)
|
|
636
|
+
except Exception as e:
|
|
637
|
+
log("Failed to open authorization URL: {}".format(e))
|
|
638
|
+
|
|
639
|
+
# Poll for token availability within timeout
|
|
640
|
+
start_ts = time.time()
|
|
641
|
+
token = None
|
|
642
|
+
while time.time() - start_ts < max_wait_seconds:
|
|
643
|
+
try:
|
|
644
|
+
token = get_access_token()
|
|
645
|
+
except Exception:
|
|
646
|
+
token = None
|
|
647
|
+
if token:
|
|
648
|
+
break
|
|
649
|
+
time.sleep(3)
|
|
650
|
+
|
|
651
|
+
if server_started_here:
|
|
652
|
+
try:
|
|
653
|
+
stop_server()
|
|
654
|
+
except Exception:
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
if not token:
|
|
658
|
+
print("Gmail authorization not completed within timeout. Please finish consent and retry.")
|
|
659
|
+
|
|
660
|
+
return bool(token)
|
|
661
|
+
|
|
597
662
|
if __name__ == "__main__":
|
|
598
663
|
signal.signal(signal.SIGINT, signal_handler)
|
|
599
664
|
signal.signal(signal.SIGTERM, signal_handler)
|
MediLink/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
MediBot/MediBot.bat,sha256=
|
|
1
|
+
MediBot/MediBot.bat,sha256=mBoD0Dit4Om_iGgSbSoVZ0d0kScGWofA7qaYqhlJhWo,29161
|
|
2
2
|
MediBot/MediBot.py,sha256=ABSqWikb_c1VSuR4n8Vh5YfOqzDCi2jnnp3sg_xOYg0,51092
|
|
3
3
|
MediBot/MediBot_Charges.py,sha256=a28if_f_IoazIHiqlaFosFnfEgEoCwb9LQ6aOyk5-D0,10704
|
|
4
4
|
MediBot/MediBot_Crosswalk_Library.py,sha256=6LrpRx2UKVeH3TspS9LpR93iw5M7nTqN6IYpC-6PPGE,26060
|
|
@@ -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=
|
|
15
|
+
MediBot/__init__.py,sha256=W7JANBDt-VBMAzdtL-KXgJ3-ODNLji-8stnIn38LUIQ,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,14 +22,14 @@ 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=heTbZ0ItwlpxqbAb0oV2dbTFRSTMnZyhBT9fS8nI0YU,12735
|
|
25
|
-
MediCafe/__init__.py,sha256=
|
|
26
|
-
MediCafe/__main__.py,sha256=
|
|
25
|
+
MediCafe/__init__.py,sha256=56u8QPxfilu-bGcU5MF8kleMtvRMpsOVEQ1VKKlZai4,5721
|
|
26
|
+
MediCafe/__main__.py,sha256=NZMgsjuXYF39YS7mCWMjTO0vYf_0IAotQpNyUXfbahs,13233
|
|
27
27
|
MediCafe/api_core.py,sha256=wLAdRNZdmovKReXvzsmAgKrbYon4-wbJbGCyOm_C3AU,89896
|
|
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
31
|
MediCafe/deductible_utils.py,sha256=-ixDYwI3JNAyACrFjKqoX_hD3Awzownq441U0PSrwXw,64932
|
|
32
|
-
MediCafe/error_reporter.py,sha256=
|
|
32
|
+
MediCafe/error_reporter.py,sha256=421z3lYMlHD0p-OIJSDyvKWRYsImVJKvm7nG0s5drFc,23848
|
|
33
33
|
MediCafe/graphql_utils.py,sha256=jo4CboMb9i5_qD0jkfrLbL87_Q3aFiwOntZhjF9fMsI,51928
|
|
34
34
|
MediCafe/logging_config.py,sha256=auT65LN5oDEXVhkMeLke63kJHTWxYf2o8YihAfQFgzU,5493
|
|
35
35
|
MediCafe/logging_demo.py,sha256=TwUhzafna5pMdN3zSKGrpUWRqX96F1JGGsSUtr3dygs,1975
|
|
@@ -51,7 +51,7 @@ MediLink/MediLink_Deductible.py,sha256=opGa5YQ6tfowurlf8xDWRtAtQMmoNYout0gYe3R5f
|
|
|
51
51
|
MediLink/MediLink_Deductible_Validator.py,sha256=x6tHJOi88TblUpDPSH6QhIdXXRgr3rXI7kYPVGZYCgU,24998
|
|
52
52
|
MediLink/MediLink_Display_Utils.py,sha256=MonsX6VPbdvqwY_V8sHUYrXCS0fMKc4toJvG0oyr-V4,24872
|
|
53
53
|
MediLink/MediLink_Down.py,sha256=s4_z-RaqHYanjwbQCl-OSkg4XIpcIQ2Q6jXa8-q_QXw,28111
|
|
54
|
-
MediLink/MediLink_Gmail.py,sha256=
|
|
54
|
+
MediLink/MediLink_Gmail.py,sha256=Gnc-tI8ACeFaw0YVPp5JUBAXhwYfptY8QGxShJu4Pbg,31476
|
|
55
55
|
MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
56
|
MediLink/MediLink_Parser.py,sha256=eRVZ4ckZ5gDOrcvtCUZP3DOd3Djly66rCIk0aYXLz14,12567
|
|
57
57
|
MediLink/MediLink_PatientProcessor.py,sha256=9r2w4p45d30Tn0kbXL3j5574MYOehP83tDirNOw_Aek,19977
|
|
@@ -63,15 +63,15 @@ MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJY
|
|
|
63
63
|
MediLink/MediLink_main.py,sha256=1nmDtbJ9IfPwLnFhCZvDtBPSG_8gzj6LneuoV60l4IQ,26736
|
|
64
64
|
MediLink/MediLink_smart_import.py,sha256=ZUXvAkIA2Pk2uuyLZazKfKK8YGdkZt1VAeZo_ZSUyxk,9942
|
|
65
65
|
MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
|
|
66
|
-
MediLink/__init__.py,sha256=
|
|
66
|
+
MediLink/__init__.py,sha256=I6BiRTvpQ5QhK_7dLbb_4VFtyYWZf1nH2dLX1OYtgr0,3888
|
|
67
67
|
MediLink/gmail_http_utils.py,sha256=mYChIhkbA1oJaAJA-nY3XgHQY-H7zvZJUZPhUagomsI,4047
|
|
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=DwDYjVvluGJ7eDdvEogfKN4t24ZJRoIUuSBfCYCL-3w,21252
|
|
72
|
-
medicafe-0.251027.
|
|
73
|
-
medicafe-0.251027.
|
|
74
|
-
medicafe-0.251027.
|
|
75
|
-
medicafe-0.251027.
|
|
76
|
-
medicafe-0.251027.
|
|
77
|
-
medicafe-0.251027.
|
|
72
|
+
medicafe-0.251027.3.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
|
|
73
|
+
medicafe-0.251027.3.dist-info/METADATA,sha256=xsE3H4MHme5SYio00FfRVaSBO51wYxwQIC190GL8_7Y,3414
|
|
74
|
+
medicafe-0.251027.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
75
|
+
medicafe-0.251027.3.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
|
|
76
|
+
medicafe-0.251027.3.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
|
|
77
|
+
medicafe-0.251027.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|