Spaces:
Sleeping
Sleeping
File size: 17,911 Bytes
ab93d81 713cfc8 ab93d81 |
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 |
# chaplain_feedback_ui.py
"""
Gradio UI components for Chaplain Feedback & Tagging System.
Provides interface components for displaying classification flows,
collecting chaplain feedback, and displaying error patterns.
Requirements: 1.5, 2.3, 3.3, 4.1, 5.1, 5.3, 6.1, 6.3, 8.1, 8.2, 8.3, 10.1, 10.2, 10.3
"""
from __future__ import annotations
import gradio as gr
from typing import List, Dict, Tuple, Optional, Any
from dataclasses import dataclass
from src.core.chaplain_models import (
ClassificationFlowResult,
DistressIndicator,
FollowUpQuestion,
TaggingRecord,
CLASSIFICATION_SUBCATEGORIES,
QUESTION_ISSUE_TYPES,
REFERRAL_ISSUE_TYPES,
)
class ChaplainFeedbackUIComponents:
"""Manages Gradio UI components for chaplain feedback system."""
# Color mappings for classification badges
BADGE_COLORS = {
"red": "π΄",
"yellow": "π‘",
"green": "π’",
}
BADGE_LABELS = {
"red": "RED - Severe Distress",
"yellow": "YELLOW - Potential Distress",
"green": "GREEN - No Distress",
}
# Severity color codes for indicators
SEVERITY_COLORS = {
"red": "#ea9999", # Red from definitions document
"yellow": "#ffe599", # Yellow from definitions document
}
@staticmethod
def create_classification_flow_display() -> Tuple[gr.Component, gr.Component, gr.Component, gr.Component]:
"""
Create ClassificationFlowDisplay component.
Displays RED/YELLOW/GREEN flow results with all generated content.
Returns:
Tuple of (classification_badge, explanation, content_section, indicators_section) components
Requirements: 1.5, 2.3, 3.3
"""
classification_badge = gr.Markdown(
value="π Loading classification...",
label="Classification Result",
)
explanation = gr.Markdown(
value="",
label="Explanation",
)
content_section = gr.Markdown(
value="",
label="Generated Content",
)
indicators_section = gr.Markdown(
value="",
label="Detected Indicators",
)
return classification_badge, explanation, content_section, indicators_section
@staticmethod
def render_classification_flow(
flow_result: ClassificationFlowResult,
) -> Tuple[str, str, str, str]:
"""
Render complete classification flow result.
Args:
flow_result: ClassificationFlowResult with all flow data
Returns:
Tuple of (badge, explanation, content, indicators) markdown strings
"""
# Classification badge
badge_emoji = ChaplainFeedbackUIComponents.BADGE_COLORS.get(flow_result.classification, "β")
badge_label = ChaplainFeedbackUIComponents.BADGE_LABELS.get(flow_result.classification, "UNKNOWN")
confidence_pct = int(round(flow_result.confidence * 100))
badge = f"## {badge_emoji} {badge_label}\n\n**Confidence:** {confidence_pct}%"
# Explanation
explanation = f"### Explanation\n\n{flow_result.explanation}"
# Generated content based on classification
content = ""
if flow_result.classification == "red":
content = ChaplainFeedbackUIComponents._render_red_flow_content(flow_result)
elif flow_result.classification == "yellow":
content = ChaplainFeedbackUIComponents._render_yellow_flow_content(flow_result)
elif flow_result.classification == "green":
content = ChaplainFeedbackUIComponents._render_green_flow_content(flow_result)
# Indicators
indicators = ChaplainFeedbackUIComponents._render_indicators(flow_result.indicators)
return badge, explanation, content, indicators
@staticmethod
def _render_red_flow_content(flow_result: ClassificationFlowResult) -> str:
"""Render RED flow content (permission check + referral message)."""
content = "### π΄ RED FLAG - Severe Distress Detected\n\n"
if flow_result.permission_check_message:
content += "#### Patient Permission Check\n\n"
content += f"{flow_result.permission_check_message}\n\n"
if flow_result.consent_status:
content += f"**Consent Status:** {flow_result.consent_status}\n\n"
if flow_result.referral_message and flow_result.consent_status == "granted":
content += "#### Referral Message for Spiritual Care Team\n\n"
content += f"{flow_result.referral_message}\n\n"
elif flow_result.consent_status == "declined":
content += "**Status:** No further action - patient declined spiritual support referral\n\n"
return content
@staticmethod
def _render_yellow_flow_content(flow_result: ClassificationFlowResult) -> str:
"""Render YELLOW flow content (follow-up questions + re-evaluation)."""
content = "### π‘ YELLOW FLAG - Potential Distress\n\n"
if flow_result.follow_up_questions:
content += "#### Follow-Up Questions\n\n"
for i, question in enumerate(flow_result.follow_up_questions, 1):
content += f"**Question {i}:** {question.question_text}\n\n"
content += f"*Purpose:* {question.purpose}\n\n"
if flow_result.patient_responses:
content += "#### Patient Responses\n\n"
for i, response in enumerate(flow_result.patient_responses, 1):
content += f"**Response {i}:** {response}\n\n"
if flow_result.re_evaluation_result:
content += f"#### Re-Evaluation Result\n\n"
if flow_result.re_evaluation_result == "red":
content += "π΄ **Escalated to RED** - Severe distress detected in responses\n\n"
elif flow_result.re_evaluation_result == "green":
content += "π’ **Downgraded to GREEN** - No distress indicators in responses\n\n"
return content
@staticmethod
def _render_green_flow_content(flow_result: ClassificationFlowResult) -> str:
"""Render GREEN flow content (no distress)."""
content = "### π’ GREEN FLAG - No Distress Detected\n\n"
content += "**Status:** No further steps required\n\n"
content += "No spiritual distress indicators were detected in this message.\n\n"
return content
@staticmethod
def _render_indicators(indicators: List[DistressIndicator]) -> str:
"""Render detected indicators with categories and severity."""
if not indicators:
return "### Detected Indicators\n\nNo indicators detected"
content = "### Detected Indicators\n\n"
# Group by severity
red_indicators = [i for i in indicators if i.severity == "red"]
yellow_indicators = [i for i in indicators if i.severity == "yellow"]
if red_indicators:
content += "#### π΄ RED Indicators (Severe)\n\n"
for indicator in red_indicators:
confidence_pct = int(round(indicator.confidence * 100))
content += f"β’ **{indicator.subcategory}** ({confidence_pct}% confidence)\n"
content += f" - Category: {indicator.category}\n"
content += f" - Reference: {indicator.definition_reference}\n\n"
if yellow_indicators:
content += "#### π‘ YELLOW Indicators (Potential)\n\n"
for indicator in yellow_indicators:
confidence_pct = int(round(indicator.confidence * 100))
content += f"β’ **{indicator.subcategory}** ({confidence_pct}% confidence)\n"
content += f" - Category: {indicator.category}\n"
content += f" - Reference: {indicator.definition_reference}\n\n"
return content
@staticmethod
def create_tagging_interface() -> Tuple[gr.Component, gr.Component, gr.Component, gr.Component, gr.Component, gr.Component, gr.Component, gr.Component, gr.Component, gr.Component]:
"""
Create TaggingInterface component.
Provides classification subcategory selector, multi-select for issues,
and free-text comment fields.
Returns:
Tuple of individual tagging components for use in event handlers
Requirements: 4.1, 5.1, 5.3, 6.1, 6.3
"""
# Classification tagging components
is_correct = gr.Radio(
choices=[("β Correct", True), ("β Incorrect", False)],
label="Is the classification correct?",
interactive=True,
visible=False,
)
subcategory = gr.Dropdown(
choices=CLASSIFICATION_SUBCATEGORIES,
label="What type of error? (if incorrect)",
interactive=True,
visible=False,
)
correct_classification = gr.Radio(
choices=[
("π’ GREEN - No Distress", "green"),
("π‘ YELLOW - Potential Distress", "yellow"),
("π΄ RED - Severe Distress", "red"),
],
label="What should the correct classification be?",
interactive=True,
visible=False,
)
# Follow-up question issues components
question_issues = gr.CheckboxGroup(
choices=QUESTION_ISSUE_TYPES,
label="Issues with follow-up questions (select all that apply)",
interactive=True,
visible=False,
)
question_comments = gr.Textbox(
label="Comments on questions",
placeholder="e.g., 'Too clinical', 'Not spiritually relevant'",
lines=2,
interactive=True,
visible=False,
)
# Referral message issues components
referral_issues = gr.CheckboxGroup(
choices=REFERRAL_ISSUE_TYPES,
label="Issues with referral message (select all that apply)",
interactive=True,
visible=False,
)
referral_comments = gr.Textbox(
label="Comments on referral message",
placeholder="e.g., 'Incomplete summary', 'Tone inappropriate'",
lines=2,
interactive=True,
visible=False,
)
# Indicator issues components
indicator_issues = gr.Textbox(
label="Incorrectly identified indicators",
placeholder="List indicator IDs or names that were incorrectly identified",
lines=2,
interactive=True,
visible=False,
)
indicator_comments = gr.Textbox(
label="Comments on indicators",
placeholder="e.g., 'Missed anxiety indicators', 'False positive on grief'",
lines=2,
interactive=True,
visible=False,
)
# General notes component
notes_section = gr.Textbox(
label="General Notes",
placeholder="Any additional feedback or observations",
lines=3,
interactive=True,
visible=False,
)
return is_correct, subcategory, correct_classification, question_issues, question_comments, referral_issues, referral_comments, indicator_issues, indicator_comments, notes_section
@staticmethod
def create_indicator_display() -> Tuple[gr.Component, gr.Component]:
"""
Create IndicatorDisplay component.
Shows indicators with categories and allows tagging incorrect indicators.
Returns:
Tuple of (indicators_display, indicator_tagging) components
Requirements: 8.1, 8.2, 8.3
"""
indicators_display = gr.Markdown(
value="No indicators to display",
label="Detected Indicators",
)
indicator_tagging = gr.Group(visible=False)
with indicator_tagging:
incorrect_indicators = gr.CheckboxGroup(
choices=[],
label="Select indicators that are incorrectly identified",
interactive=True,
)
indicator_notes = gr.Textbox(
label="Why are these indicators incorrect?",
placeholder="Explain why these indicators don't apply",
lines=2,
interactive=True,
)
return indicators_display, indicator_tagging
@staticmethod
def create_error_pattern_summary() -> Tuple[gr.Component, gr.Component, gr.Component]:
"""
Create ErrorPatternSummary component.
Displays error patterns grouped by type with frequent subcategories highlighted.
Returns:
Tuple of (error_patterns, subcategory_breakdown, recommendations) components
Requirements: 10.1, 10.2, 10.3
"""
error_patterns = gr.Markdown(
value="No error patterns yet",
label="Error Patterns",
)
subcategory_breakdown = gr.Markdown(
value="No data",
label="Subcategory Breakdown",
)
recommendations = gr.Markdown(
value="No recommendations yet",
label="Recommendations for Improvement",
)
return error_patterns, subcategory_breakdown, recommendations
@staticmethod
def render_error_patterns(
classification_errors: Dict[str, int],
question_errors: Dict[str, int],
referral_errors: Dict[str, int],
) -> Tuple[str, str, str]:
"""
Render error patterns summary.
Args:
classification_errors: Dict of classification error subcategories with counts
question_errors: Dict of question issue types with counts
referral_errors: Dict of referral issue types with counts
Returns:
Tuple of (patterns, breakdown, recommendations) markdown strings
"""
# Error patterns grouped by type
patterns = "### Error Patterns\n\n"
total_classification_errors = sum(classification_errors.values())
total_question_errors = sum(question_errors.values())
total_referral_errors = sum(referral_errors.values())
if total_classification_errors > 0:
patterns += f"#### Classification Errors: {total_classification_errors} total\n\n"
for subcategory, count in sorted(classification_errors.items(), key=lambda x: x[1], reverse=True):
patterns += f"β’ {subcategory}: {count}\n"
patterns += "\n"
if total_question_errors > 0:
patterns += f"#### Follow-Up Question Issues: {total_question_errors} total\n\n"
for issue_type, count in sorted(question_errors.items(), key=lambda x: x[1], reverse=True):
patterns += f"β’ {issue_type}: {count}\n"
patterns += "\n"
if total_referral_errors > 0:
patterns += f"#### Referral Message Issues: {total_referral_errors} total\n\n"
for issue_type, count in sorted(referral_errors.items(), key=lambda x: x[1], reverse=True):
patterns += f"β’ {issue_type}: {count}\n"
patterns += "\n"
# Subcategory breakdown
breakdown = "### Subcategory Breakdown\n\n"
if classification_errors:
breakdown += "**Classification Errors:**\n"
for subcategory, count in sorted(classification_errors.items(), key=lambda x: x[1], reverse=True):
breakdown += f"- {subcategory}: {count}\n"
breakdown += "\n"
if question_errors:
breakdown += "**Question Issues:**\n"
for issue_type, count in sorted(question_errors.items(), key=lambda x: x[1], reverse=True):
breakdown += f"- {issue_type}: {count}\n"
breakdown += "\n"
if referral_errors:
breakdown += "**Referral Issues:**\n"
for issue_type, count in sorted(referral_errors.items(), key=lambda x: x[1], reverse=True):
breakdown += f"- {issue_type}: {count}\n"
breakdown += "\n"
# Recommendations
recommendations = "### Recommendations for Improvement\n\n"
# Find most common errors
all_errors = {}
for subcategory, count in classification_errors.items():
all_errors[f"Classification: {subcategory}"] = count
for issue_type, count in question_errors.items():
all_errors[f"Questions: {issue_type}"] = count
for issue_type, count in referral_errors.items():
all_errors[f"Referral: {issue_type}"] = count
if all_errors:
sorted_errors = sorted(all_errors.items(), key=lambda x: x[1], reverse=True)
top_3 = sorted_errors[:3]
recommendations += "**Top areas for improvement:**\n\n"
for error_type, count in top_3:
recommendations += f"1. **{error_type}** ({count} occurrences)\n"
recommendations += f" - Review prompts and logic for this error type\n"
recommendations += f" - Consider additional training data\n\n"
else:
recommendations += "No errors detected yet. Great job!\n\n"
return patterns, breakdown, recommendations
|