Jacaranda commited on
Commit
1754605
Β·
verified Β·
1 Parent(s): 9e16b85

Upload gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +542 -0
gradio_app.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Gradio version for Hugging Face Spaces
2
+ import gradio as gr
3
+ import requests
4
+ import json
5
+ import time
6
+ from typing import Dict, Any, Tuple, List
7
+
8
+ # Configuration - Can switch between local and AWS
9
+ LOCAL_API = "http://localhost:8080"
10
+ AWS_API = "https://rj92jktas7.us-east-2.awsapprunner.com"
11
+
12
+ # Global variables to maintain state
13
+ current_session = {
14
+ 'session_id': None,
15
+ 'validation_started': False,
16
+ 'all_questions': [],
17
+ 'current_question_id': None,
18
+ 'validation_complete': False,
19
+ 'api_base_url': AWS_API
20
+ }
21
+
22
+ # Sample records for testing
23
+ SAMPLE_RECORDS = {
24
+ "Medical Record 1 - Multiple Issues": {
25
+ "UUID": "sample-001",
26
+ "patient_info": {
27
+ "name": "John Doe",
28
+ "age": 28
29
+ },
30
+ "clinical_risk": {
31
+ "thyroid_disease": "how are you?",
32
+ "anemia": None,
33
+ "preeclampsia_current": "not sure",
34
+ "geriatric_mother": "maybe",
35
+ "malaria_chronic_history": ".",
36
+ "preeclampsia_prior": "[insert explanation here]"
37
+ },
38
+ "rmc1": {
39
+ "rmc1_respect": True,
40
+ "rmc1_explanation": "[insert explanation here]",
41
+ "rmc1_facility": "Test Hospital"
42
+ },
43
+ "immunization_birth": {
44
+ "birth_bcg": False,
45
+ "birth_oral_polio": True,
46
+ "birth_no_vax_reason": "not sure"
47
+ }
48
+ },
49
+ "Medical Record 2 - Few Issues": {
50
+ "UUID": "sample-002",
51
+ "patient_info": {
52
+ "name": "Jane Smith",
53
+ "age": 35
54
+ },
55
+ "clinical_risk": {
56
+ "thyroid_disease": True,
57
+ "anemia": "maybe",
58
+ "preeclampsia_current": False,
59
+ "malaria_chronic_history": "[insert explanation here]"
60
+ },
61
+ "rmc2": {
62
+ "rmc2_respect": False,
63
+ "rmc2_explanation": ".",
64
+ "rmc2_facility": "Another Hospital"
65
+ }
66
+ }
67
+ }
68
+
69
+ def check_api_health():
70
+ """Check if the API is running and get feature info"""
71
+ try:
72
+ response = requests.get(f"{current_session['api_base_url']}/health", timeout=10)
73
+ if response.status_code == 200:
74
+ health_data = response.json()
75
+ return True, health_data
76
+ else:
77
+ return False, f"Status code: {response.status_code}"
78
+ except Exception as e:
79
+ return False, f"Error: {str(e)}"
80
+
81
+ def start_validation_session(record: Dict[str, Any]):
82
+ """Start a validation session with the API"""
83
+ try:
84
+ response = requests.post(f"{current_session['api_base_url']}/start-validation", json=record, timeout=30)
85
+ if response.status_code == 200:
86
+ return response.json()
87
+ else:
88
+ return None, f"Failed to start validation: {response.text}"
89
+ except Exception as e:
90
+ return None, f"Error connecting to API: {str(e)}"
91
+
92
+ def get_question_by_id(question_id: str):
93
+ """Get a specific question by its unique ID"""
94
+ try:
95
+ response = requests.get(f"{current_session['api_base_url']}/get-question/{question_id}", timeout=15)
96
+ if response.status_code == 200:
97
+ return response.json()
98
+ else:
99
+ return None
100
+ except Exception as e:
101
+ return None
102
+
103
+ def list_all_questions(session_id: str):
104
+ """Get list of all questions for a session"""
105
+ try:
106
+ response = requests.get(f"{current_session['api_base_url']}/list-questions/{session_id}", timeout=15)
107
+ if response.status_code == 200:
108
+ return response.json()
109
+ else:
110
+ return None
111
+ except Exception as e:
112
+ return None
113
+
114
+ def submit_response_to_question(question_id: str, user_response: str):
115
+ """Submit a response to a specific question"""
116
+ try:
117
+ response = requests.post(f"{current_session['api_base_url']}/submit-response/{question_id}",
118
+ json={"response": user_response}, timeout=30)
119
+ if response.status_code == 200:
120
+ return response.json()
121
+ else:
122
+ return None
123
+ except Exception as e:
124
+ return None
125
+
126
+ def get_final_results(session_id: str):
127
+ """Get the final validation results"""
128
+ try:
129
+ response = requests.get(f"{current_session['api_base_url']}/get-results/{session_id}", timeout=15)
130
+ if response.status_code == 200:
131
+ return response.json()
132
+ else:
133
+ return None
134
+ except Exception as e:
135
+ return None
136
+
137
+ # Gradio Interface Functions
138
+
139
+ def update_api_endpoint(api_choice, custom_url=""):
140
+ """Update the API endpoint based on user selection"""
141
+ if api_choice == "AWS Deployment":
142
+ current_session['api_base_url'] = AWS_API
143
+ status = f"βœ… Using AWS App Runner: {AWS_API}"
144
+ elif api_choice == "Local Development":
145
+ current_session['api_base_url'] = LOCAL_API
146
+ status = f"ℹ️ Using Local API: {LOCAL_API}"
147
+ else: # Custom URL
148
+ current_session['api_base_url'] = custom_url if custom_url else AWS_API
149
+ status = f"πŸ”§ Using Custom URL: {current_session['api_base_url']}"
150
+
151
+ # Check API health
152
+ is_healthy, health_info = check_api_health()
153
+ if is_healthy:
154
+ if isinstance(health_info, dict):
155
+ status += f"\n\nπŸ“Š API Status:\n"
156
+ status += f"β€’ Version: {health_info.get('version', 'N/A')}\n"
157
+ status += f"β€’ Active Sessions: {health_info.get('active_sessions', 0)}\n"
158
+ status += f"β€’ Total Questions: {health_info.get('total_questions', 0)}\n"
159
+
160
+ features = health_info.get('features', [])
161
+ if features:
162
+ status += f"β€’ Features: {', '.join(features)}"
163
+ else:
164
+ status += f"\n\n❌ API Error: {health_info}"
165
+
166
+ return status
167
+
168
+ def load_sample_record(sample_choice):
169
+ """Load a sample record based on user selection"""
170
+ if sample_choice and sample_choice in SAMPLE_RECORDS:
171
+ record_json = json.dumps(SAMPLE_RECORDS[sample_choice], indent=2)
172
+ return record_json, f"βœ… Loaded: {sample_choice}"
173
+ return "", "Please select a sample record"
174
+
175
+ def parse_json_record(json_text):
176
+ """Parse and validate JSON record"""
177
+ if not json_text.strip():
178
+ return "❌ Please provide a JSON record", ""
179
+
180
+ try:
181
+ record = json.loads(json_text)
182
+ preview = json.dumps(record, indent=2)
183
+ return f"βœ… Valid JSON record parsed successfully!", preview
184
+ except json.JSONDecodeError as e:
185
+ return f"❌ Invalid JSON: {str(e)}", ""
186
+
187
+ def start_validation(json_text):
188
+ """Start the validation process"""
189
+ if not json_text.strip():
190
+ return "❌ Please provide a JSON record first", "", gr.update(visible=False)
191
+
192
+ try:
193
+ record = json.loads(json_text)
194
+ except json.JSONDecodeError as e:
195
+ return f"❌ Invalid JSON: {str(e)}", "", gr.update(visible=False)
196
+
197
+ # Start validation session
198
+ result = start_validation_session(record)
199
+ if isinstance(result, tuple): # Error case
200
+ return f"❌ {result[1]}", "", gr.update(visible=False)
201
+
202
+ if result:
203
+ current_session['session_id'] = result["session_id"]
204
+ current_session['all_questions'] = result.get("questions", [])
205
+ current_session['validation_started'] = True
206
+
207
+ if result["total_questions"] == 0:
208
+ current_session['validation_complete'] = True
209
+ return "βœ… No missing fields found! Record is already complete.", "", gr.update(visible=False)
210
+ else:
211
+ status = f"βœ… Created {result['total_questions']} individual questions with unique tracking IDs\n"
212
+
213
+ # Show tracking info
214
+ tracking_info = result.get("tracking", {})
215
+ if tracking_info.get("question_tracking_enabled"):
216
+ status += "πŸ” Each question now has a unique ID for precise tracking"
217
+
218
+ # Get first question
219
+ question_status = list_all_questions(current_session['session_id'])
220
+ if question_status:
221
+ questions_list = question_status["questions"]
222
+ if questions_list:
223
+ first_question = questions_list[0]
224
+ current_session['current_question_id'] = first_question['question_id']
225
+
226
+ # Get question details
227
+ question_details = get_question_by_id(first_question['question_id'])
228
+ if question_details:
229
+ question_display = f"**Field:** {question_details['field_path']}\n"
230
+ question_display += f"**Question ID:** {question_details['question_id']}\n\n"
231
+ question_display += f"### {question_details['question']}"
232
+
233
+ progress_info = f"Progress: 0/{result['total_questions']} (0.0%)"
234
+
235
+ return status, f"{progress_info}\n\n{question_display}", gr.update(visible=True)
236
+
237
+ return status, "Loading questions...", gr.update(visible=True)
238
+ else:
239
+ return "❌ Failed to start validation session", "", gr.update(visible=False)
240
+
241
+ def submit_answer(answer_text):
242
+ """Submit an answer to the current question"""
243
+ if not answer_text.strip():
244
+ return "❌ Please provide an answer", "", gr.update(visible=True)
245
+
246
+ if not current_session.get('current_question_id'):
247
+ return "❌ No active question", "", gr.update(visible=True)
248
+
249
+ # Submit response
250
+ response = submit_response_to_question(current_session['current_question_id'], answer_text)
251
+
252
+ if not response:
253
+ return "❌ Failed to submit response", "", gr.update(visible=True)
254
+
255
+ # Process response
256
+ result_text = ""
257
+
258
+ if response.get("status") == "needs_clarification":
259
+ result_text = f"⚠️ Your answer needs clarification\n\n"
260
+ result_text += f"**Follow-up question:** {response['clarification_question']}\n\n"
261
+ result_text += "Please provide a clearer, more specific answer."
262
+
263
+ # Update current question to the clarification
264
+ current_session['current_question_id'] = response.get('clarification_question_id', current_session['current_question_id'])
265
+
266
+ return result_text, "", gr.update(visible=True)
267
+
268
+ elif response.get("status") in ["completed", "low_confidence"]:
269
+ validation = response["validation"]
270
+ confidence = validation["confidence"]
271
+
272
+ if response.get("status") == "completed":
273
+ result_text = f"βœ… Answer accepted! (Confidence: {confidence:.2f})\n\n"
274
+ else:
275
+ result_text = f"⚠️ Answer accepted with low confidence: {confidence:.2f}\n\n"
276
+
277
+ # Show tracking details
278
+ tracking_info = response.get("tracking", {})
279
+ if tracking_info:
280
+ result_text += f"πŸ” **Tracking Details:**\n"
281
+ result_text += f"β€’ Question ID: {tracking_info.get('question_unique_id', 'N/A')}\n"
282
+ result_text += f"β€’ Response Time: {tracking_info.get('response_timestamp', 'N/A')}\n"
283
+ result_text += f"β€’ Attempt Count: {tracking_info.get('attempt_count', 'N/A')}\n"
284
+ result_text += f"β€’ Validation Confidence: {tracking_info.get('validation_confidence', 0):.3f}\n\n"
285
+
286
+ # Check if all completed
287
+ progress = response.get("progress", {})
288
+ if progress.get("all_completed"):
289
+ current_session['validation_complete'] = True
290
+ result_text += "πŸŽ‰ **All questions completed!**"
291
+ return result_text, "All questions have been answered. You can now view the final results.", gr.update(visible=False)
292
+ else:
293
+ # Get next question
294
+ question_status = list_all_questions(current_session['session_id'])
295
+ if question_status:
296
+ questions_list = question_status["questions"]
297
+ completed_count = question_status["completed_questions"]
298
+ total_count = question_status["total_questions"]
299
+
300
+ # Find next pending question
301
+ next_question = None
302
+ for q in questions_list:
303
+ if q['status'] in ['pending', 'needs_clarification']:
304
+ next_question = q
305
+ break
306
+
307
+ if next_question:
308
+ current_session['current_question_id'] = next_question['question_id']
309
+
310
+ # Get question details
311
+ question_details = get_question_by_id(next_question['question_id'])
312
+ if question_details:
313
+ question_display = f"**Field:** {question_details['field_path']}\n"
314
+ question_display += f"**Question ID:** {question_details['question_id']}\n\n"
315
+ question_display += f"### {question_details['question']}"
316
+
317
+ progress_info = f"Progress: {completed_count}/{total_count} ({completed_count/total_count*100:.1f}%)"
318
+
319
+ return result_text, f"{progress_info}\n\n{question_display}", gr.update(visible=True)
320
+
321
+ return result_text, "Loading next question...", gr.update(visible=True)
322
+
323
+ return "❓ Unexpected response", "", gr.update(visible=True)
324
+
325
+ def get_results():
326
+ """Get the final validation results"""
327
+ if not current_session.get('session_id'):
328
+ return "❌ No active session"
329
+
330
+ results = get_final_results(current_session['session_id'])
331
+
332
+ if not results:
333
+ return "❌ Failed to get results"
334
+
335
+ # Format results
336
+ result_text = "# πŸŽ‰ Validation Complete!\n\n"
337
+
338
+ # Summary metrics
339
+ result_text += "## πŸ“Š Summary\n"
340
+ result_text += f"β€’ **Fields Processed:** {results['summary']['total_fields_processed']}\n"
341
+ result_text += f"β€’ **Success Rate:** {results['summary']['success_rate']:.1f}%\n"
342
+ result_text += f"β€’ **Average Confidence:** {results['summary']['average_confidence']:.2f}\n"
343
+ result_text += f"β€’ **Average Attempts per Question:** {results['summary']['average_attempts_per_question']:.1f}\n\n"
344
+
345
+ # Tracking metrics
346
+ result_text += "## πŸ” Question Tracking\n"
347
+ result_text += f"β€’ **Total Questions:** {results['summary']['total_questions']}\n"
348
+ result_text += f"β€’ **Questions with Clarification:** {results['summary']['questions_with_clarification']}\n"
349
+ result_text += f"β€’ **Completed Questions:** {results['summary']['completed_questions']}\n\n"
350
+
351
+ # Updated record
352
+ result_text += "## πŸ“‹ Updated Record\n"
353
+ result_text += "```json\n"
354
+ result_text += json.dumps(results["updated_record"], indent=2)
355
+ result_text += "\n```\n\n"
356
+
357
+ # Download files
358
+ updated_record_json = json.dumps(results["updated_record"], indent=2)
359
+ tracking_data = {
360
+ "session_info": results.get("session_info", {}),
361
+ "question_details": results.get("question_details", {}),
362
+ "summary": results.get("summary", {})
363
+ }
364
+ tracking_json = json.dumps(tracking_data, indent=2)
365
+ full_results_json = json.dumps(results, indent=2)
366
+
367
+ return result_text, updated_record_json, tracking_json, full_results_json
368
+
369
+ def reset_session():
370
+ """Reset the current session"""
371
+ global current_session
372
+ session_id = current_session.get('session_id', 'new')
373
+ current_session = {
374
+ 'session_id': None,
375
+ 'validation_started': False,
376
+ 'all_questions': [],
377
+ 'current_question_id': None,
378
+ 'validation_complete': False,
379
+ 'api_base_url': current_session['api_base_url'] # Keep the API setting
380
+ }
381
+ return f"πŸ”„ Session reset. Ready for new validation.", "", "", gr.update(visible=False)
382
+
383
+ # Create Gradio Interface
384
+ def create_interface():
385
+ with gr.Blocks(title="Medical Record Validation", theme=gr.themes.Soft()) as demo:
386
+ gr.Markdown("# πŸ₯ Medical Record Data Validation")
387
+ gr.Markdown("**Enhanced with Question-Level Tracking**")
388
+
389
+ with gr.Tab("πŸš€ Setup & Configuration"):
390
+ gr.Markdown("## API Configuration")
391
+
392
+ api_choice = gr.Radio(
393
+ choices=["AWS Deployment", "Local Development", "Custom URL"],
394
+ value="AWS Deployment",
395
+ label="Choose API endpoint"
396
+ )
397
+
398
+ custom_url = gr.Textbox(
399
+ label="Custom API URL",
400
+ placeholder="https://your-api-url.com",
401
+ visible=False
402
+ )
403
+
404
+ api_status = gr.Markdown("πŸ”„ Checking API status...")
405
+
406
+ # Update API status when choice changes
407
+ def update_custom_url_visibility(choice):
408
+ return gr.update(visible=(choice == "Custom URL"))
409
+
410
+ api_choice.change(
411
+ update_custom_url_visibility,
412
+ inputs=[api_choice],
413
+ outputs=[custom_url]
414
+ )
415
+
416
+ api_choice.change(
417
+ update_api_endpoint,
418
+ inputs=[api_choice, custom_url],
419
+ outputs=[api_status]
420
+ )
421
+
422
+ custom_url.change(
423
+ update_api_endpoint,
424
+ inputs=[api_choice, custom_url],
425
+ outputs=[api_status]
426
+ )
427
+
428
+ with gr.Tab("πŸ“‹ Record Input"):
429
+ gr.Markdown("## Step 1: Provide Medical Record")
430
+
431
+ with gr.Row():
432
+ with gr.Column():
433
+ sample_choice = gr.Dropdown(
434
+ choices=list(SAMPLE_RECORDS.keys()),
435
+ label="Choose from sample records",
436
+ value=None
437
+ )
438
+
439
+ load_sample_btn = gr.Button("Load Sample Record", variant="secondary")
440
+
441
+ with gr.Column():
442
+ upload_file = gr.File(
443
+ label="Upload JSON file",
444
+ file_types=[".json"]
445
+ )
446
+
447
+ json_input = gr.Textbox(
448
+ label="Medical Record JSON",
449
+ placeholder="Paste your JSON record here or use the options above...",
450
+ lines=10
451
+ )
452
+
453
+ parse_status = gr.Markdown("")
454
+ json_preview = gr.Code(label="Record Preview", language="json", visible=False)
455
+
456
+ validate_btn = gr.Button("πŸš€ Start Validation", variant="primary", size="lg")
457
+ validation_status = gr.Markdown("")
458
+
459
+ # Event handlers
460
+ load_sample_btn.click(
461
+ load_sample_record,
462
+ inputs=[sample_choice],
463
+ outputs=[json_input, parse_status]
464
+ )
465
+
466
+ json_input.change(
467
+ parse_json_record,
468
+ inputs=[json_input],
469
+ outputs=[parse_status, json_preview]
470
+ )
471
+
472
+ with gr.Tab("❓ Answer Questions"):
473
+ gr.Markdown("## Step 2: Answer Validation Questions")
474
+
475
+ question_display = gr.Markdown("Start validation to see questions here...")
476
+
477
+ with gr.Row():
478
+ answer_input = gr.Textbox(
479
+ label="Your Answer",
480
+ placeholder="Enter your response here...",
481
+ scale=3
482
+ )
483
+ submit_btn = gr.Button("Submit Answer", variant="primary", scale=1)
484
+
485
+ answer_result = gr.Markdown("")
486
+
487
+ question_section = gr.Group(visible=False)
488
+
489
+ with question_section:
490
+ submit_btn.click(
491
+ submit_answer,
492
+ inputs=[answer_input],
493
+ outputs=[answer_result, question_display, question_section]
494
+ )
495
+
496
+ with gr.Tab("πŸ“Š Results"):
497
+ gr.Markdown("## Step 3: Validation Results")
498
+
499
+ get_results_btn = gr.Button("πŸ“₯ Get Final Results", variant="primary")
500
+ results_display = gr.Markdown("Complete validation to see results here...")
501
+
502
+ with gr.Row():
503
+ download_record = gr.File(label="Updated Record", visible=False)
504
+ download_tracking = gr.File(label="Tracking Details", visible=False)
505
+ download_full = gr.File(label="Complete Results", visible=False)
506
+
507
+ reset_btn = gr.Button("πŸ”„ Start New Validation", variant="secondary")
508
+ reset_status = gr.Markdown("")
509
+
510
+ # Main event handlers
511
+ validate_btn.click(
512
+ start_validation,
513
+ inputs=[json_input],
514
+ outputs=[validation_status, question_display, question_section]
515
+ )
516
+
517
+ get_results_btn.click(
518
+ get_results,
519
+ outputs=[results_display, download_record, download_tracking, download_full]
520
+ )
521
+
522
+ reset_btn.click(
523
+ reset_session,
524
+ outputs=[reset_status, validation_status, question_display, question_section]
525
+ )
526
+
527
+ # Initialize API status
528
+ demo.load(
529
+ lambda: update_api_endpoint("AWS Deployment"),
530
+ outputs=[api_status]
531
+ )
532
+
533
+ return demo
534
+
535
+ # Launch the app
536
+ if __name__ == "__main__":
537
+ demo = create_interface()
538
+ demo.launch(
539
+ server_name="0.0.0.0",
540
+ server_port=7860,
541
+ share=False
542
+ )