File size: 16,561 Bytes
a3934b1
 
 
 
 
 
 
7bbd836
a3934b1
 
713cfc8
 
a3934b1
 
 
 
 
 
 
 
 
 
 
7bbd836
 
 
 
 
 
 
 
a3934b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
7bbd836
a3934b1
7bbd836
a3934b1
 
 
 
7bbd836
a3934b1
 
 
 
 
7bbd836
a3934b1
7bbd836
a3934b1
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
7bbd836
 
a3934b1
7bbd836
 
a3934b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
7bbd836
 
a3934b1
7bbd836
 
a3934b1
 
 
 
 
 
7bbd836
a3934b1
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
 
 
 
7bbd836
 
 
a3934b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bbd836
a3934b1
 
 
 
 
 
 
 
 
 
7bbd836
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# verification_ui.py
"""
Gradio UI components for Verification Mode.

Provides interface components for reviewing classified messages,
collecting verifier feedback, and displaying results.

Requirements: 1.1, 2.1, 2.2, 2.3, 2.4, 2.5, 3.1, 3.3, 3.4, 12.1, 12.2, 12.3, 12.4, 12.5
"""

from __future__ import annotations

import gradio as gr
from typing import List, Dict, Tuple, Optional, Any
from dataclasses import dataclass
from src.core.verification_models import (
    VerificationRecord,
    VerificationSession,
    TestMessage,
    TestDataset,
)
from src.core.test_datasets import TestDatasetManager
from src.core.verification_metrics import VerificationMetricsCalculator
from src.interface.ui_consistency_components import (
    StandardizedComponents,
    ClassificationDisplay,
    ProgressDisplay,
    ErrorDisplay,
    SessionDisplay,
    HelpDisplay
)


@dataclass
class UIState:
    """State container for verification UI."""
    current_session: Optional[VerificationSession] = None
    current_dataset: Optional[TestDataset] = None
    message_queue: List[TestMessage] = None
    current_message_index: int = 0
    
    def __post_init__(self):
        if self.message_queue is None:
            self.message_queue = []


class VerificationUIComponents:
    """Manages Gradio UI components for verification mode."""
    
    # Color mappings for classification badges
    BADGE_COLORS = {
        "green": "🟒",
        "yellow": "🟑",
        "red": "πŸ”΄",
    }
    
    BADGE_LABELS = {
        "green": "GREEN - No Distress",
        "yellow": "YELLOW - Potential Distress",
        "red": "RED - Severe Distress",
    }
    
    @staticmethod
    def format_confidence_percentage(confidence: float) -> str:
        """
        Format confidence score as percentage using standardized components.
        
        Args:
            confidence: Confidence score (0.0-1.0)
            
        Returns:
            Formatted percentage string with consistent styling
        """
        return ClassificationDisplay.format_confidence_display(confidence)
    
    @staticmethod
    def format_indicators_as_bullets(indicators: List[str]) -> str:
        """
        Format indicators using standardized components.
        
        Args:
            indicators: List of indicator strings
            
        Returns:
            Formatted indicators string with consistent styling
        """
        return ClassificationDisplay.format_indicators_display(indicators)
    
    @staticmethod
    def get_classifier_decision_badge(decision: str) -> str:
        """
        Get classifier decision with colored badge using standardized components.
        
        Args:
            decision: Classification decision ("green", "yellow", "red")
            
        Returns:
            Formatted badge string with emoji and label
        """
        return ClassificationDisplay.format_classification_badge(decision)
    
    @staticmethod
    def create_dataset_selector_component() -> gr.Component:
        """
        Create dataset selector component.
        
        Returns:
            Gradio component for dataset selection
        """
        datasets = TestDatasetManager.get_dataset_list()
        
        # Create dataset options with descriptions
        dataset_options = [
            f"{d['name']} ({d['message_count']} messages)"
            for d in datasets
        ]
        
        return gr.Dropdown(
            choices=dataset_options,
            label="πŸ“Š Select Dataset to Verify",
            info="Choose which test dataset to review",
            interactive=True,
        )
    
    @staticmethod
    def create_dataset_metadata_display() -> gr.Component:
        """
        Create dataset metadata display component.
        
        Returns:
            Gradio component for displaying dataset metadata
        """
        return gr.Markdown(
            value="Select a dataset to view details",
            label="πŸ“‹ Dataset Details",
        )
    
    @staticmethod
    def render_dataset_metadata(dataset: TestDataset) -> str:
        """
        Render dataset metadata for display.
        
        Args:
            dataset: Test dataset to display metadata for
            
        Returns:
            Formatted markdown string with dataset metadata
        """
        if dataset is None:
            return "No dataset selected"
        
        metadata = f"""### {dataset.name}

**Description:** {dataset.description}

**Message Count:** {dataset.message_count} messages

**Dataset ID:** `{dataset.dataset_id}`
"""
        return metadata
    
    @staticmethod
    def render_dataset_selection_confirmation(dataset: TestDataset) -> str:
        """
        Render dataset selection confirmation message.
        
        Args:
            dataset: Selected test dataset
            
        Returns:
            Formatted confirmation message
        """
        if dataset is None:
            return "No dataset selected"
        
        confirmation = f"""βœ“ **Dataset Selected**

You have selected: **{dataset.name}**

This dataset contains **{dataset.message_count} messages** to verify.

Click "Start Verification" to begin reviewing messages.
"""
        return confirmation
    
    @staticmethod
    def create_session_resumption_component() -> Tuple[gr.Component, gr.Component]:
        """
        Create session resumption components using standardized components.
        
        Returns:
            Tuple of (resume_button, new_session_button) components
        """
        resume_btn = StandardizedComponents.create_primary_button("Resume Previous Session", "▢️", "lg")
        resume_btn.scale = 1
        
        new_session_btn = StandardizedComponents.create_secondary_button("Start New Session", "✨", "lg")
        new_session_btn.scale = 1
        
        return resume_btn, new_session_btn
    
    @staticmethod
    def create_message_review_component() -> Tuple[gr.Component, gr.Component, gr.Component, gr.Component]:
        """
        Create message review component with all required elements.
        
        Returns:
            Tuple of (message_text, decision_badge, confidence, indicators) components
        """
        message_text = gr.Textbox(
            label="πŸ“ Patient Message",
            interactive=False,
            lines=4,
            max_lines=6,
        )
        
        decision_badge = gr.Markdown(
            value="πŸ”„ Loading...",
            label="🎯 Classifier Decision",
        )
        
        confidence = gr.Markdown(
            value="Loading...",
            label="πŸ“Š Confidence Level",
        )
        
        indicators = gr.Markdown(
            value="Loading...",
            label="πŸ” Detected Indicators",
        )
        
        return message_text, decision_badge, confidence, indicators
    
    @staticmethod
    def create_feedback_buttons() -> Tuple[gr.Component, gr.Component]:
        """
        Create feedback buttons for correct/incorrect using standardized components.
        
        Returns:
            Tuple of (correct_button, incorrect_button) components
        """
        correct_btn = StandardizedComponents.create_primary_button("Correct", "βœ“", "lg")
        correct_btn.scale = 1
        
        incorrect_btn = StandardizedComponents.create_stop_button("Incorrect", "βœ—", "lg")
        incorrect_btn.scale = 1
        
        return correct_btn, incorrect_btn
    
    @staticmethod
    def create_correction_selector() -> Tuple[gr.Component, gr.Component]:
        """
        Create correction selector for incorrect classifications using standardized components.
        
        Returns:
            Tuple of (correction_selector, notes_field) components
        """
        correction_selector = ClassificationDisplay.create_classification_radio()
        
        notes_field = gr.Textbox(
            label="πŸ“ Optional Notes (Why is this incorrect?)",
            placeholder="e.g., 'Missed anxiety indicators', 'False positive'",
            lines=2,
            interactive=True,
        )
        
        return correction_selector, notes_field
    
    @staticmethod
    def create_progress_display() -> gr.Component:
        """
        Create progress display component.
        
        Returns:
            Gradio component for progress display
        """
        return gr.Markdown(
            value="πŸ“Š Progress: 0 of 0 messages reviewed",
            label="Progress",
        )
    
    @staticmethod
    def create_statistics_panel() -> Tuple[gr.Component, gr.Component, gr.Component]:
        """
        Create statistics display panel.
        
        Returns:
            Tuple of (correct_count, incorrect_count, accuracy) components
        """
        correct_count = gr.Markdown(
            value="βœ“ Correct: 0",
            label="Correct Classifications",
        )
        
        incorrect_count = gr.Markdown(
            value="βœ— Incorrect: 0",
            label="Incorrect Classifications",
        )
        
        accuracy = gr.Markdown(
            value="πŸ“Š Accuracy: 0%",
            label="Overall Accuracy",
        )
        
        return correct_count, incorrect_count, accuracy
    
    @staticmethod
    def render_message_review(
        message: TestMessage,
        classifier_decision: str,
        classifier_confidence: float,
        classifier_indicators: List[str],
    ) -> Tuple[str, str, str, str]:
        """
        Render message review with all components.
        
        Args:
            message: Test message to display
            classifier_decision: Classifier's decision
            classifier_confidence: Classifier's confidence
            classifier_indicators: List of detected indicators
            
        Returns:
            Tuple of (message_text, decision_badge, confidence, indicators)
        """
        message_text = message.text
        
        decision_badge = VerificationUIComponents.get_classifier_decision_badge(
            classifier_decision
        )
        
        confidence_str = VerificationUIComponents.format_confidence_percentage(
            classifier_confidence
        )
        
        indicators_str = VerificationUIComponents.format_indicators_as_bullets(
            classifier_indicators
        )
        
        return message_text, decision_badge, confidence_str, indicators_str
    
    @staticmethod
    def update_progress_display(
        current_index: int,
        total_messages: int,
    ) -> str:
        """
        Update progress display using standardized components.
        
        Args:
            current_index: Current message index (0-based)
            total_messages: Total messages in dataset
            
        Returns:
            Formatted progress string
        """
        message_number = current_index + 1
        return ProgressDisplay.format_progress_display(message_number, total_messages)
    
    @staticmethod
    def update_statistics_display(
        correct_count: int,
        incorrect_count: int,
    ) -> Tuple[str, str, str]:
        """
        Update statistics display using standardized components.
        
        Args:
            correct_count: Number of correct classifications
            incorrect_count: Number of incorrect classifications
            
        Returns:
            Tuple of (correct_str, incorrect_str, accuracy_str)
        """
        total = correct_count + incorrect_count
        
        correct_str = f"βœ“ **Correct:** {correct_count}"
        incorrect_str = f"βœ— **Incorrect:** {incorrect_count}"
        accuracy_str = ProgressDisplay.format_accuracy_display(correct_count, total)
        
        return correct_str, incorrect_str, accuracy_str
    
    @staticmethod
    def create_breakdown_by_type_component() -> gr.Component:
        """
        Create breakdown by classification type component.
        
        Returns:
            Gradio component for displaying breakdown by type
        """
        return gr.Markdown(
            value="🟒 GREEN: 0 correct | 🟑 YELLOW: 0 correct | πŸ”΄ RED: 0 correct",
            label="Breakdown by Classification Type",
        )
    
    @staticmethod
    def update_breakdown_by_type(
        records: List[VerificationRecord],
    ) -> str:
        """
        Update breakdown by classification type.
        
        Args:
            records: List of verification records
            
        Returns:
            Formatted breakdown string
        """
        breakdown = {}
        
        for classification_type in ["green", "yellow", "red"]:
            type_records = [
                r for r in records
                if r.classifier_decision == classification_type
            ]
            correct_count = sum(1 for r in type_records if r.is_correct)
            breakdown[classification_type] = correct_count
        
        return (
            f"🟒 GREEN: {breakdown['green']} correct | "
            f"🟑 YELLOW: {breakdown['yellow']} correct | "
            f"πŸ”΄ RED: {breakdown['red']} correct"
        )
    
    @staticmethod
    def create_summary_card_component() -> gr.Component:
        """
        Create summary card component for session completion.
        
        Returns:
            Gradio component for displaying summary card
        """
        return gr.Markdown(
            value="## Session Summary\n\nNo session data yet.",
            label="Session Summary",
        )
    
    @staticmethod
    def render_summary_card(
        session: VerificationSession,
        records: List[VerificationRecord],
    ) -> str:
        """
        Render summary card for session completion.
        
        Args:
            session: Verification session
            records: List of verification records
            
        Returns:
            Formatted summary card markdown
        """
        if not records:
            return "## Session Summary\n\nNo messages verified yet."
        
        total = len(records)
        correct_count = sum(1 for r in records if r.is_correct)
        incorrect_count = total - correct_count
        accuracy = (correct_count / total) * 100 if total > 0 else 0
        
        # Get breakdown by type
        breakdown = {}
        for classification_type in ["green", "yellow", "red"]:
            type_records = [
                r for r in records
                if r.classifier_decision == classification_type
            ]
            correct_count_type = sum(1 for r in type_records if r.is_correct)
            breakdown[classification_type] = correct_count_type
        
        summary = f"""## Session Summary

**Dataset:** {session.dataset_name}

**Overall Results:**
- Total Messages Reviewed: {total}
- Correct Classifications: {correct_count}
- Incorrect Classifications: {incorrect_count}
- Overall Accuracy: {accuracy:.1f}%

**Breakdown by Classification Type:**
- 🟒 GREEN: {breakdown['green']} correct
- 🟑 YELLOW: {breakdown['yellow']} correct
- πŸ”΄ RED: {breakdown['red']} correct

**Session Status:** {'βœ“ Complete' if session.is_complete else '⏳ In Progress'}
"""
        return summary
    
    @staticmethod
    def create_session_info_display() -> gr.Component:
        """
        Create session info display component.
        
        Returns:
            Gradio component for displaying session information
        """
        return gr.Markdown(
            value="No active session",
            label="Session Info",
        )
    
    @staticmethod
    def render_session_info(session: VerificationSession) -> str:
        """
        Render session information display using standardized components.
        
        Args:
            session: Verification session
            
        Returns:
            Formatted session info markdown
        """
        if session is None:
            return "No active session"
        
        session_data = {
            'verifier_name': session.verifier_name,
            'mode_type': getattr(session, 'mode_type', 'standard'),
            'dataset_name': session.dataset_name,
            'verified_count': session.verified_count,
            'total_messages': session.total_messages,
            'is_complete': session.is_complete,
            'accuracy': (session.correct_count / session.verified_count * 100) if session.verified_count > 0 else 0,
            'created_at': session.created_at
        }
        
        return SessionDisplay.format_session_info(session_data)