Spaces:
Sleeping
fix: Or_6 feedback — consent questions, unrecorded verdicts, feedback types
Browse filesFindings from Alex's first reviewed test case (review/Or_6.txt).
Fixed:
- A question about the spiritual care team was scored as a refusal.
"I don't know, who are they" contains "don't"/"not", so _check_consent's
negative keyword scan matched first, _process_consent_declined closed the
topic and moved the session back to GREEN while the patient was still in an
unresolved RED. _is_consent_question now runs before _check_consent, explains
what the chaplains do, re-asks for consent and stays in AWAITING_CONSENT.
- Saving from the always-visible verification panel never set is_correct, so a
fully reviewed session exported as "0 reviewed". Picking a classification is
now the verdict itself (PROVIDER_SUMMARY step excluded, its radio is hidden).
The panel also stays open after Save so a note left on an exchange whose
classification was correct doesn't disappear.
Added:
- "Type of feedback" multi-select (wrong classification / tone / missing
information / conversation flow) in both interfaces, exported as a
feedback_types column with stable codes. An exchange can carry more than one,
which splits reviewer notes into classifier-prompt vs response-prompt fixes
without re-reading free text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@@ -422,20 +422,27 @@ class SimplifiedMedicalApp:
|
|
| 422 |
def _handle_consent_response(self, message: str) -> str:
|
| 423 |
"""
|
| 424 |
Handle patient's response to consent request.
|
| 425 |
-
|
|
|
|
| 426 |
If patient agrees → generate referral
|
| 427 |
If patient declines → return to medical dialog without referral
|
| 428 |
-
|
| 429 |
Requirement: Or_review.md section 4.A.3
|
| 430 |
"""
|
| 431 |
logger.info(f"Handling consent response: {message[:50]}...")
|
| 432 |
-
|
| 433 |
# Detect language
|
| 434 |
patient_language = self._detect_language(message)
|
| 435 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
# Check if patient consents
|
| 437 |
consent_given = self._check_consent(message)
|
| 438 |
-
|
| 439 |
if consent_given:
|
| 440 |
logger.info("Patient consented to spiritual care referral")
|
| 441 |
return self._process_consent_accepted(patient_language)
|
|
@@ -443,10 +450,57 @@ class SimplifiedMedicalApp:
|
|
| 443 |
logger.info("Patient declined spiritual care referral")
|
| 444 |
return self._process_consent_declined(patient_language)
|
| 445 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
def _check_consent(self, message: str) -> bool:
|
| 447 |
"""
|
| 448 |
Check if patient's response indicates consent.
|
| 449 |
-
|
| 450 |
Returns True if patient agrees, False if declines or unclear.
|
| 451 |
"""
|
| 452 |
message_lower = message.lower()
|
|
|
|
| 422 |
def _handle_consent_response(self, message: str) -> str:
|
| 423 |
"""
|
| 424 |
Handle patient's response to consent request.
|
| 425 |
+
|
| 426 |
+
If patient asks about the service → explain it and keep awaiting consent
|
| 427 |
If patient agrees → generate referral
|
| 428 |
If patient declines → return to medical dialog without referral
|
| 429 |
+
|
| 430 |
Requirement: Or_review.md section 4.A.3
|
| 431 |
"""
|
| 432 |
logger.info(f"Handling consent response: {message[:50]}...")
|
| 433 |
+
|
| 434 |
# Detect language
|
| 435 |
patient_language = self._detect_language(message)
|
| 436 |
+
|
| 437 |
+
# A question about the spiritual care team is not a refusal - answer it and
|
| 438 |
+
# stay in AWAITING_CONSENT so the patient can still decide (Or_6.txt).
|
| 439 |
+
if self._is_consent_question(message):
|
| 440 |
+
logger.info("Patient asked about the spiritual care team - explaining service")
|
| 441 |
+
return self._explain_spiritual_care_service(patient_language)
|
| 442 |
+
|
| 443 |
# Check if patient consents
|
| 444 |
consent_given = self._check_consent(message)
|
| 445 |
+
|
| 446 |
if consent_given:
|
| 447 |
logger.info("Patient consented to spiritual care referral")
|
| 448 |
return self._process_consent_accepted(patient_language)
|
|
|
|
| 450 |
logger.info("Patient declined spiritual care referral")
|
| 451 |
return self._process_consent_declined(patient_language)
|
| 452 |
|
| 453 |
+
def _is_consent_question(self, message: str) -> bool:
|
| 454 |
+
"""
|
| 455 |
+
Check whether the patient is asking about the spiritual care team rather
|
| 456 |
+
than accepting or refusing the referral.
|
| 457 |
+
|
| 458 |
+
This must be checked before _check_consent: phrases like "I don't know,
|
| 459 |
+
who are they" contain negative keywords ("don't", "not") and would
|
| 460 |
+
otherwise be scored as a refusal.
|
| 461 |
+
"""
|
| 462 |
+
message_lower = message.lower()
|
| 463 |
+
|
| 464 |
+
question_en = [
|
| 465 |
+
"who are they", "who is", "who are", "what is", "what are",
|
| 466 |
+
"what do they", "what would", "what does", "what's that",
|
| 467 |
+
"how does", "how do they", "how would", "tell me more",
|
| 468 |
+
"more information", "more about", "what kind of",
|
| 469 |
+
"i don't know", "i dont know", "not sure", "like what"
|
| 470 |
+
]
|
| 471 |
+
|
| 472 |
+
question_uk = [
|
| 473 |
+
"хто це", "хто вони", "хто такі", "що це", "що вони",
|
| 474 |
+
"що саме", "як це", "як вони", "розкажіть більше",
|
| 475 |
+
"більше інформації", "не знаю", "не впевнен", "що це означає"
|
| 476 |
+
]
|
| 477 |
+
|
| 478 |
+
return any(q in message_lower for q in question_en + question_uk)
|
| 479 |
+
|
| 480 |
+
def _explain_spiritual_care_service(self, language: str) -> str:
|
| 481 |
+
"""
|
| 482 |
+
Explain what the spiritual care team does and re-ask for consent.
|
| 483 |
+
|
| 484 |
+
The session stays in AWAITING_CONSENT, so the patient's next message is
|
| 485 |
+
routed back through _handle_consent_response.
|
| 486 |
+
"""
|
| 487 |
+
if language == "Ukrainian":
|
| 488 |
+
return """Звісно, поясню. Команда духовної підтримки — це капелани нашої лікарні. Вони не є медичним персоналом і не змінюють ваше лікування.
|
| 489 |
+
|
| 490 |
+
Вони просто вислухають вас і побудуть поруч — можна говорити про те, що вас турбує, про сенс, страх чи втрату. Це конфіденційно, безкоштовно, не залежить від вашої релігії чи її відсутності, і ви можете припинити будь-якої миті.
|
| 491 |
+
|
| 492 |
+
Якщо ви погодитесь, я передам їм ваше ім'я та контакт, і хтось із них зв'яжеться з вами. Чи хотіли б ви, щоб я це зробив?"""
|
| 493 |
+
else:
|
| 494 |
+
return """Of course, let me explain. The spiritual care team are the chaplains here at the hospital. They are not medical staff, and they don't change anything about your treatment.
|
| 495 |
+
|
| 496 |
+
They're simply there to listen and sit with you — you can talk about whatever is weighing on you, about meaning, fear, or loss. It's confidential, there's no cost, it doesn't depend on your religion or having one, and you can stop at any point.
|
| 497 |
+
|
| 498 |
+
If you'd like, I can share your name and contact details so someone from the team can reach out to you. Would you like me to do that?"""
|
| 499 |
+
|
| 500 |
def _check_consent(self, message: str) -> bool:
|
| 501 |
"""
|
| 502 |
Check if patient's response indicates consent.
|
| 503 |
+
|
| 504 |
Returns True if patient agrees, False if declines or unclear.
|
| 505 |
"""
|
| 506 |
message_lower = message.lower()
|
|
@@ -260,6 +260,11 @@ def create_simplified_interface():
|
|
| 260 |
label="Correct Classification",
|
| 261 |
interactive=True
|
| 262 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
conv_incorrect_comment = gr.Textbox(
|
| 264 |
label="Comment (why incorrect / what to fix)",
|
| 265 |
placeholder="Add a short note for this exchange...",
|
|
@@ -665,6 +670,7 @@ def create_simplified_interface():
|
|
| 665 |
conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
|
| 666 |
# Show the classification/comment panel for the first exchange too
|
| 667 |
conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification,
|
|
|
|
| 668 |
]
|
| 669 |
)
|
| 670 |
|
|
@@ -693,6 +699,7 @@ def create_simplified_interface():
|
|
| 693 |
conv_incorrect_comment_row,
|
| 694 |
conv_incorrect_comment,
|
| 695 |
conv_correct_classification,
|
|
|
|
| 696 |
]
|
| 697 |
)
|
| 698 |
|
|
@@ -709,12 +716,13 @@ def create_simplified_interface():
|
|
| 709 |
conv_incorrect_comment_row,
|
| 710 |
conv_incorrect_comment,
|
| 711 |
conv_correct_classification,
|
|
|
|
| 712 |
]
|
| 713 |
)
|
| 714 |
|
| 715 |
conv_save_comment_btn.click(
|
| 716 |
verification_handlers._save_incorrect_comment,
|
| 717 |
-
inputs=[conv_verify_records, conv_verify_index, conv_incorrect_comment, conv_correct_classification],
|
| 718 |
outputs=[
|
| 719 |
conv_verify_records,
|
| 720 |
conv_verify_index,
|
|
@@ -725,19 +733,20 @@ def create_simplified_interface():
|
|
| 725 |
conv_incorrect_comment_row,
|
| 726 |
conv_incorrect_comment,
|
| 727 |
conv_correct_classification,
|
|
|
|
| 728 |
]
|
| 729 |
)
|
| 730 |
|
| 731 |
conv_prev_btn.click(
|
| 732 |
lambda records, idx: verification_handlers._nav_conv(records, idx, -1),
|
| 733 |
inputs=[conv_verify_records, conv_verify_index],
|
| 734 |
-
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification]
|
| 735 |
)
|
| 736 |
|
| 737 |
conv_next_btn.click(
|
| 738 |
lambda records, idx: verification_handlers._nav_conv(records, idx, 1),
|
| 739 |
inputs=[conv_verify_records, conv_verify_index],
|
| 740 |
-
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification]
|
| 741 |
)
|
| 742 |
|
| 743 |
|
|
|
|
| 260 |
label="Correct Classification",
|
| 261 |
interactive=True
|
| 262 |
)
|
| 263 |
+
conv_feedback_type = gr.CheckboxGroup(
|
| 264 |
+
choices=verification_handlers._FEEDBACK_TYPE_CHOICES,
|
| 265 |
+
label="Type of feedback (select all that apply)",
|
| 266 |
+
interactive=True,
|
| 267 |
+
)
|
| 268 |
conv_incorrect_comment = gr.Textbox(
|
| 269 |
label="Comment (why incorrect / what to fix)",
|
| 270 |
placeholder="Add a short note for this exchange...",
|
|
|
|
| 670 |
conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
|
| 671 |
# Show the classification/comment panel for the first exchange too
|
| 672 |
conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification,
|
| 673 |
+
conv_feedback_type,
|
| 674 |
]
|
| 675 |
)
|
| 676 |
|
|
|
|
| 699 |
conv_incorrect_comment_row,
|
| 700 |
conv_incorrect_comment,
|
| 701 |
conv_correct_classification,
|
| 702 |
+
conv_feedback_type,
|
| 703 |
]
|
| 704 |
)
|
| 705 |
|
|
|
|
| 716 |
conv_incorrect_comment_row,
|
| 717 |
conv_incorrect_comment,
|
| 718 |
conv_correct_classification,
|
| 719 |
+
conv_feedback_type,
|
| 720 |
]
|
| 721 |
)
|
| 722 |
|
| 723 |
conv_save_comment_btn.click(
|
| 724 |
verification_handlers._save_incorrect_comment,
|
| 725 |
+
inputs=[conv_verify_records, conv_verify_index, conv_incorrect_comment, conv_correct_classification, conv_feedback_type],
|
| 726 |
outputs=[
|
| 727 |
conv_verify_records,
|
| 728 |
conv_verify_index,
|
|
|
|
| 733 |
conv_incorrect_comment_row,
|
| 734 |
conv_incorrect_comment,
|
| 735 |
conv_correct_classification,
|
| 736 |
+
conv_feedback_type,
|
| 737 |
]
|
| 738 |
)
|
| 739 |
|
| 740 |
conv_prev_btn.click(
|
| 741 |
lambda records, idx: verification_handlers._nav_conv(records, idx, -1),
|
| 742 |
inputs=[conv_verify_records, conv_verify_index],
|
| 743 |
+
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification, conv_feedback_type]
|
| 744 |
)
|
| 745 |
|
| 746 |
conv_next_btn.click(
|
| 747 |
lambda records, idx: verification_handlers._nav_conv(records, idx, 1),
|
| 748 |
inputs=[conv_verify_records, conv_verify_index],
|
| 749 |
+
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification, conv_feedback_type]
|
| 750 |
)
|
| 751 |
|
| 752 |
|
|
@@ -177,6 +177,11 @@ def create_simplified_interface():
|
|
| 177 |
label="Select Correct Classification",
|
| 178 |
interactive=True
|
| 179 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
conv_incorrect_comment = gr.Textbox(
|
| 181 |
label="Comment (why incorrect / what to fix)",
|
| 182 |
placeholder="Add your feedback here...",
|
|
@@ -540,6 +545,7 @@ def create_simplified_interface():
|
|
| 540 |
conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
|
| 541 |
# Show the classification/comment panel for the first exchange too
|
| 542 |
conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification,
|
|
|
|
| 543 |
]
|
| 544 |
)
|
| 545 |
|
|
@@ -562,6 +568,7 @@ def create_simplified_interface():
|
|
| 562 |
conv_incorrect_comment_row,
|
| 563 |
conv_incorrect_comment,
|
| 564 |
conv_correct_classification,
|
|
|
|
| 565 |
]
|
| 566 |
)
|
| 567 |
|
|
@@ -578,12 +585,13 @@ def create_simplified_interface():
|
|
| 578 |
conv_incorrect_comment_row,
|
| 579 |
conv_incorrect_comment,
|
| 580 |
conv_correct_classification,
|
|
|
|
| 581 |
]
|
| 582 |
)
|
| 583 |
|
| 584 |
conv_save_comment_btn.click(
|
| 585 |
verification_handlers._save_incorrect_comment,
|
| 586 |
-
inputs=[conv_verify_records, conv_verify_index, conv_incorrect_comment, conv_correct_classification],
|
| 587 |
outputs=[
|
| 588 |
conv_verify_records,
|
| 589 |
conv_verify_index,
|
|
@@ -594,19 +602,20 @@ def create_simplified_interface():
|
|
| 594 |
conv_incorrect_comment_row,
|
| 595 |
conv_incorrect_comment,
|
| 596 |
conv_correct_classification,
|
|
|
|
| 597 |
]
|
| 598 |
)
|
| 599 |
|
| 600 |
conv_prev_btn.click(
|
| 601 |
lambda records, idx: verification_handlers._nav_conv(records, idx, -1),
|
| 602 |
inputs=[conv_verify_records, conv_verify_index],
|
| 603 |
-
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification]
|
| 604 |
)
|
| 605 |
|
| 606 |
conv_next_btn.click(
|
| 607 |
lambda records, idx: verification_handlers._nav_conv(records, idx, 1),
|
| 608 |
inputs=[conv_verify_records, conv_verify_index],
|
| 609 |
-
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification]
|
| 610 |
)
|
| 611 |
|
| 612 |
# Prompt editing events
|
|
|
|
| 177 |
label="Select Correct Classification",
|
| 178 |
interactive=True
|
| 179 |
)
|
| 180 |
+
conv_feedback_type = gr.CheckboxGroup(
|
| 181 |
+
choices=verification_handlers._FEEDBACK_TYPE_CHOICES,
|
| 182 |
+
label="Type of feedback (select all that apply)",
|
| 183 |
+
interactive=True,
|
| 184 |
+
)
|
| 185 |
conv_incorrect_comment = gr.Textbox(
|
| 186 |
label="Comment (why incorrect / what to fix)",
|
| 187 |
placeholder="Add your feedback here...",
|
|
|
|
| 545 |
conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
|
| 546 |
# Show the classification/comment panel for the first exchange too
|
| 547 |
conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification,
|
| 548 |
+
conv_feedback_type,
|
| 549 |
]
|
| 550 |
)
|
| 551 |
|
|
|
|
| 568 |
conv_incorrect_comment_row,
|
| 569 |
conv_incorrect_comment,
|
| 570 |
conv_correct_classification,
|
| 571 |
+
conv_feedback_type,
|
| 572 |
]
|
| 573 |
)
|
| 574 |
|
|
|
|
| 585 |
conv_incorrect_comment_row,
|
| 586 |
conv_incorrect_comment,
|
| 587 |
conv_correct_classification,
|
| 588 |
+
conv_feedback_type,
|
| 589 |
]
|
| 590 |
)
|
| 591 |
|
| 592 |
conv_save_comment_btn.click(
|
| 593 |
verification_handlers._save_incorrect_comment,
|
| 594 |
+
inputs=[conv_verify_records, conv_verify_index, conv_incorrect_comment, conv_correct_classification, conv_feedback_type],
|
| 595 |
outputs=[
|
| 596 |
conv_verify_records,
|
| 597 |
conv_verify_index,
|
|
|
|
| 602 |
conv_incorrect_comment_row,
|
| 603 |
conv_incorrect_comment,
|
| 604 |
conv_correct_classification,
|
| 605 |
+
conv_feedback_type,
|
| 606 |
]
|
| 607 |
)
|
| 608 |
|
| 609 |
conv_prev_btn.click(
|
| 610 |
lambda records, idx: verification_handlers._nav_conv(records, idx, -1),
|
| 611 |
inputs=[conv_verify_records, conv_verify_index],
|
| 612 |
+
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification, conv_feedback_type]
|
| 613 |
)
|
| 614 |
|
| 615 |
conv_next_btn.click(
|
| 616 |
lambda records, idx: verification_handlers._nav_conv(records, idx, 1),
|
| 617 |
inputs=[conv_verify_records, conv_verify_index],
|
| 618 |
+
outputs=[conv_verify_index, conv_verify_exchange, conv_position, conv_stats, conv_incorrect_comment_row, conv_incorrect_comment, conv_correct_classification, conv_feedback_type]
|
| 619 |
)
|
| 620 |
|
| 621 |
# Prompt editing events
|
|
@@ -924,6 +924,7 @@ def _export_conv_records_to_csv(meta: dict, records: list):
|
|
| 924 |
"original_confidence",
|
| 925 |
"is_correct",
|
| 926 |
"correct_classification",
|
|
|
|
| 927 |
"verifier_notes",
|
| 928 |
"user_message",
|
| 929 |
"assistant_response",
|
|
@@ -951,6 +952,7 @@ def _export_conv_records_to_csv(meta: dict, records: list):
|
|
| 951 |
"original_confidence": r.get("original_confidence"),
|
| 952 |
"is_correct": r.get("is_correct"),
|
| 953 |
"correct_classification": r.get("correct_classification") or "",
|
|
|
|
| 954 |
"verifier_notes": r.get("verifier_notes") or "",
|
| 955 |
"user_message": r.get("user_message"),
|
| 956 |
"assistant_response": r.get("assistant_response"),
|
|
@@ -961,9 +963,9 @@ def _export_conv_records_to_csv(meta: dict, records: list):
|
|
| 961 |
|
| 962 |
def _generate_conv_verification(session: SimplifiedSessionData):
|
| 963 |
if session is None or not hasattr(session.app_instance, "conversation_logger"):
|
| 964 |
-
return None, [], 0, "❌ No session/conversation found", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 965 |
if not session.app_instance.conversation_logger.entries:
|
| 966 |
-
return None, [], 0, "⚠️ No exchanges to verify yet", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 967 |
|
| 968 |
manager = ConversationVerificationManager()
|
| 969 |
vs = manager.create_verification_session(session.app_instance.conversation_logger, "Medical Professional")
|
|
@@ -1004,6 +1006,7 @@ def _generate_conv_verification(session: SimplifiedSessionData):
|
|
| 1004 |
"correct_classification": r.correct_classification,
|
| 1005 |
"correction_reason": r.correction_reason,
|
| 1006 |
"verifier_notes": r.verifier_notes,
|
|
|
|
| 1007 |
"provider_summary": provider_summary_text if r.original_classification.upper() == "RED" else "",
|
| 1008 |
}
|
| 1009 |
for r in vs.verification_records
|
|
@@ -1011,9 +1014,10 @@ def _generate_conv_verification(session: SimplifiedSessionData):
|
|
| 1011 |
html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
|
| 1012 |
row_upd, note_val = _comment_ui_state(records_as_dicts, 0)
|
| 1013 |
radio_upd = _classification_update(records_as_dicts, 0)
|
|
|
|
| 1014 |
return (
|
| 1015 |
meta, records_as_dicts, 0, f"✅ Generated session `{vs.session_id}`",
|
| 1016 |
-
html, pos, stats, row_upd, note_val, radio_upd,
|
| 1017 |
)
|
| 1018 |
|
| 1019 |
# Display text for each classification flag (shared by the verification handlers).
|
|
@@ -1023,6 +1027,31 @@ _CLASSIFICATION_DISPLAY = {
|
|
| 1023 |
"RED": "🔴 Should be RED - Spiritual distress",
|
| 1024 |
}
|
| 1025 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
def _classification_update(records: list, idx: int):
|
| 1027 |
"""Build the gr.update for the 'Correct Classification' Radio.
|
| 1028 |
|
|
@@ -1041,38 +1070,42 @@ def _classification_update(records: list, idx: int):
|
|
| 1041 |
|
| 1042 |
def _mark_conv_correct(records: list, idx: int):
|
| 1043 |
if not records:
|
| 1044 |
-
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1045 |
idx = max(0, min(idx, len(records) - 1))
|
| 1046 |
if isinstance(records[idx], dict):
|
| 1047 |
records[idx]["is_correct"] = True
|
| 1048 |
# clear comment and correct_classification when marked correct (avoid stale data)
|
| 1049 |
records[idx]["verifier_notes"] = ""
|
| 1050 |
records[idx]["correct_classification"] = None
|
|
|
|
| 1051 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1052 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1053 |
-
return records, idx, "✅ Marked correct", html, pos, stats, row_upd, note_val, _classification_update(records, idx)
|
| 1054 |
|
| 1055 |
def _mark_conv_incorrect(records: list, idx: int):
|
| 1056 |
if not records:
|
| 1057 |
-
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1058 |
idx = max(0, min(idx, len(records) - 1))
|
| 1059 |
if isinstance(records[idx], dict):
|
| 1060 |
records[idx]["is_correct"] = False
|
| 1061 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1062 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1063 |
-
return records, idx, "❌ Marked incorrect", html, pos, stats, row_upd, note_val, _classification_update(records, idx)
|
| 1064 |
|
| 1065 |
def _show_incorrect_comment_ui(records: list, idx: int):
|
| 1066 |
"""Mark incorrect and open the comment row, pre-filling any existing note."""
|
| 1067 |
-
records, idx, status, html, pos, stats, _row, note, existing_classification = _mark_conv_incorrect(records, idx)
|
| 1068 |
-
return records, idx, status, html, pos, stats, gr.update(visible=True), note, existing_classification
|
| 1069 |
|
| 1070 |
-
def _save_incorrect_comment(records: list, idx: int, note: str, correct_classification: str):
|
| 1071 |
if not records:
|
| 1072 |
-
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1073 |
idx = max(0, min(idx, len(records) - 1))
|
| 1074 |
if isinstance(records[idx], dict):
|
| 1075 |
records[idx]["verifier_notes"] = (note or "").strip()
|
|
|
|
|
|
|
|
|
|
| 1076 |
# Map display text to classification code
|
| 1077 |
classification_map = {
|
| 1078 |
"🟢 Should be GREEN - No distress": "GREEN",
|
|
@@ -1080,11 +1113,19 @@ def _save_incorrect_comment(records: list, idx: int, note: str, correct_classifi
|
|
| 1080 |
"🔴 Should be RED - Spiritual distress": "RED"
|
| 1081 |
}
|
| 1082 |
if correct_classification and correct_classification in classification_map:
|
| 1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1085 |
-
|
| 1086 |
-
#
|
| 1087 |
-
|
|
|
|
| 1088 |
|
| 1089 |
def _download_reviewed_json(meta: dict, records: list):
|
| 1090 |
return _export_conv_records_to_json(meta, records)
|
|
@@ -1094,11 +1135,11 @@ def _download_reviewed_csv(meta: dict, records: list):
|
|
| 1094 |
|
| 1095 |
def _nav_conv(records: list, idx: int, delta: int):
|
| 1096 |
if not records:
|
| 1097 |
-
return idx, "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1098 |
idx = max(0, min(idx + delta, len(records) - 1))
|
| 1099 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1100 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1101 |
-
return idx, html, pos, stats, row_upd, note_val, _classification_update(records, idx)
|
| 1102 |
|
| 1103 |
|
| 1104 |
# ============================================================================
|
|
@@ -1113,9 +1154,9 @@ def _generate_conv_verification_with_summary(session: SimplifiedSessionData):
|
|
| 1113 |
"Provider Summary to be the final exchange presented in that tab"
|
| 1114 |
"""
|
| 1115 |
if session is None or not hasattr(session.app_instance, "conversation_logger"):
|
| 1116 |
-
return None, [], 0, "❌ No session/conversation found", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1117 |
if not session.app_instance.conversation_logger.entries:
|
| 1118 |
-
return None, [], 0, "⚠️ No exchanges to verify yet", "", "", "", gr.update(visible=False), "", gr.update(value=None)
|
| 1119 |
|
| 1120 |
manager = ConversationVerificationManager()
|
| 1121 |
vs = manager.create_verification_session(session.app_instance.conversation_logger, "Medical Professional")
|
|
@@ -1182,6 +1223,7 @@ def _generate_conv_verification_with_summary(session: SimplifiedSessionData):
|
|
| 1182 |
"correct_classification": r.correct_classification,
|
| 1183 |
"correction_reason": r.correction_reason,
|
| 1184 |
"verifier_notes": r.verifier_notes,
|
|
|
|
| 1185 |
"provider_summary": "", # Not shown in regular exchanges
|
| 1186 |
}
|
| 1187 |
for r in vs.verification_records
|
|
@@ -1204,6 +1246,7 @@ def _generate_conv_verification_with_summary(session: SimplifiedSessionData):
|
|
| 1204 |
"correct_classification": None,
|
| 1205 |
"correction_reason": "",
|
| 1206 |
"verifier_notes": "",
|
|
|
|
| 1207 |
"provider_summary": provider_summary_text,
|
| 1208 |
"provider_summary_html": provider_summary_html,
|
| 1209 |
}
|
|
@@ -1212,10 +1255,11 @@ def _generate_conv_verification_with_summary(session: SimplifiedSessionData):
|
|
| 1212 |
html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
|
| 1213 |
row_upd, note_val = _comment_ui_state(records_as_dicts, 0)
|
| 1214 |
radio_upd = _classification_update(records_as_dicts, 0)
|
|
|
|
| 1215 |
return (
|
| 1216 |
meta, records_as_dicts, 0,
|
| 1217 |
f"✅ Generated session with {len(records_as_dicts)} exchanges (Provider Summary as final step)",
|
| 1218 |
-
html, pos, stats, row_upd, note_val, radio_upd,
|
| 1219 |
)
|
| 1220 |
|
| 1221 |
|
|
|
|
| 924 |
"original_confidence",
|
| 925 |
"is_correct",
|
| 926 |
"correct_classification",
|
| 927 |
+
"feedback_types",
|
| 928 |
"verifier_notes",
|
| 929 |
"user_message",
|
| 930 |
"assistant_response",
|
|
|
|
| 952 |
"original_confidence": r.get("original_confidence"),
|
| 953 |
"is_correct": r.get("is_correct"),
|
| 954 |
"correct_classification": r.get("correct_classification") or "",
|
| 955 |
+
"feedback_types": "; ".join(r.get("feedback_types") or []),
|
| 956 |
"verifier_notes": r.get("verifier_notes") or "",
|
| 957 |
"user_message": r.get("user_message"),
|
| 958 |
"assistant_response": r.get("assistant_response"),
|
|
|
|
| 963 |
|
| 964 |
def _generate_conv_verification(session: SimplifiedSessionData):
|
| 965 |
if session is None or not hasattr(session.app_instance, "conversation_logger"):
|
| 966 |
+
return None, [], 0, "❌ No session/conversation found", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 967 |
if not session.app_instance.conversation_logger.entries:
|
| 968 |
+
return None, [], 0, "⚠️ No exchanges to verify yet", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 969 |
|
| 970 |
manager = ConversationVerificationManager()
|
| 971 |
vs = manager.create_verification_session(session.app_instance.conversation_logger, "Medical Professional")
|
|
|
|
| 1006 |
"correct_classification": r.correct_classification,
|
| 1007 |
"correction_reason": r.correction_reason,
|
| 1008 |
"verifier_notes": r.verifier_notes,
|
| 1009 |
+
"feedback_types": [],
|
| 1010 |
"provider_summary": provider_summary_text if r.original_classification.upper() == "RED" else "",
|
| 1011 |
}
|
| 1012 |
for r in vs.verification_records
|
|
|
|
| 1014 |
html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
|
| 1015 |
row_upd, note_val = _comment_ui_state(records_as_dicts, 0)
|
| 1016 |
radio_upd = _classification_update(records_as_dicts, 0)
|
| 1017 |
+
types_upd = _feedback_type_update(records_as_dicts, 0)
|
| 1018 |
return (
|
| 1019 |
meta, records_as_dicts, 0, f"✅ Generated session `{vs.session_id}`",
|
| 1020 |
+
html, pos, stats, row_upd, note_val, radio_upd, types_upd,
|
| 1021 |
)
|
| 1022 |
|
| 1023 |
# Display text for each classification flag (shared by the verification handlers).
|
|
|
|
| 1027 |
"RED": "🔴 Should be RED - Spiritual distress",
|
| 1028 |
}
|
| 1029 |
|
| 1030 |
+
# What kind of problem the reviewer is reporting. A single exchange can have more
|
| 1031 |
+
# than one (e.g. wrong flag AND a missing explanation), hence a multi-select.
|
| 1032 |
+
# Stored as stable codes so the export can be split into "fix the classifier
|
| 1033 |
+
# prompt" vs "fix the response prompt" without re-reading free text (Or_6.txt).
|
| 1034 |
+
_FEEDBACK_TYPE_DISPLAY = {
|
| 1035 |
+
"CLASSIFICATION": "🏷 Wrong classification",
|
| 1036 |
+
"TONE": "💬 Tone / wording",
|
| 1037 |
+
"MISSING_CONTENT": "📋 Missing information",
|
| 1038 |
+
"FLOW": "🔀 Conversation flow",
|
| 1039 |
+
}
|
| 1040 |
+
_FEEDBACK_TYPE_CHOICES = list(_FEEDBACK_TYPE_DISPLAY.values())
|
| 1041 |
+
_FEEDBACK_TYPE_CODES = {v: k for k, v in _FEEDBACK_TYPE_DISPLAY.items()}
|
| 1042 |
+
|
| 1043 |
+
|
| 1044 |
+
def _feedback_type_update(records: list, idx: int):
|
| 1045 |
+
"""Build the gr.update for the 'Type of feedback' CheckboxGroup."""
|
| 1046 |
+
value = []
|
| 1047 |
+
if records and 0 <= idx < len(records) and isinstance(records[idx], dict):
|
| 1048 |
+
value = [
|
| 1049 |
+
_FEEDBACK_TYPE_DISPLAY[c]
|
| 1050 |
+
for c in (records[idx].get("feedback_types") or [])
|
| 1051 |
+
if c in _FEEDBACK_TYPE_DISPLAY
|
| 1052 |
+
]
|
| 1053 |
+
return gr.update(value=value)
|
| 1054 |
+
|
| 1055 |
def _classification_update(records: list, idx: int):
|
| 1056 |
"""Build the gr.update for the 'Correct Classification' Radio.
|
| 1057 |
|
|
|
|
| 1070 |
|
| 1071 |
def _mark_conv_correct(records: list, idx: int):
|
| 1072 |
if not records:
|
| 1073 |
+
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1074 |
idx = max(0, min(idx, len(records) - 1))
|
| 1075 |
if isinstance(records[idx], dict):
|
| 1076 |
records[idx]["is_correct"] = True
|
| 1077 |
# clear comment and correct_classification when marked correct (avoid stale data)
|
| 1078 |
records[idx]["verifier_notes"] = ""
|
| 1079 |
records[idx]["correct_classification"] = None
|
| 1080 |
+
records[idx]["feedback_types"] = []
|
| 1081 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1082 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1083 |
+
return records, idx, "✅ Marked correct", html, pos, stats, row_upd, note_val, _classification_update(records, idx), _feedback_type_update(records, idx)
|
| 1084 |
|
| 1085 |
def _mark_conv_incorrect(records: list, idx: int):
|
| 1086 |
if not records:
|
| 1087 |
+
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1088 |
idx = max(0, min(idx, len(records) - 1))
|
| 1089 |
if isinstance(records[idx], dict):
|
| 1090 |
records[idx]["is_correct"] = False
|
| 1091 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1092 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1093 |
+
return records, idx, "❌ Marked incorrect", html, pos, stats, row_upd, note_val, _classification_update(records, idx), _feedback_type_update(records, idx)
|
| 1094 |
|
| 1095 |
def _show_incorrect_comment_ui(records: list, idx: int):
|
| 1096 |
"""Mark incorrect and open the comment row, pre-filling any existing note."""
|
| 1097 |
+
records, idx, status, html, pos, stats, _row, note, existing_classification, existing_types = _mark_conv_incorrect(records, idx)
|
| 1098 |
+
return records, idx, status, html, pos, stats, gr.update(visible=True), note, existing_classification, existing_types
|
| 1099 |
|
| 1100 |
+
def _save_incorrect_comment(records: list, idx: int, note: str, correct_classification: str, feedback_types: list = None):
|
| 1101 |
if not records:
|
| 1102 |
+
return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1103 |
idx = max(0, min(idx, len(records) - 1))
|
| 1104 |
if isinstance(records[idx], dict):
|
| 1105 |
records[idx]["verifier_notes"] = (note or "").strip()
|
| 1106 |
+
records[idx]["feedback_types"] = [
|
| 1107 |
+
_FEEDBACK_TYPE_CODES[t] for t in (feedback_types or []) if t in _FEEDBACK_TYPE_CODES
|
| 1108 |
+
]
|
| 1109 |
# Map display text to classification code
|
| 1110 |
classification_map = {
|
| 1111 |
"🟢 Should be GREEN - No distress": "GREEN",
|
|
|
|
| 1113 |
"🔴 Should be RED - Spiritual distress": "RED"
|
| 1114 |
}
|
| 1115 |
if correct_classification and correct_classification in classification_map:
|
| 1116 |
+
code = classification_map[correct_classification]
|
| 1117 |
+
records[idx]["correct_classification"] = code
|
| 1118 |
+
# Picking a classification IS the review verdict. Without this, a
|
| 1119 |
+
# reviewer who uses the always-visible panel (pick + comment + Save)
|
| 1120 |
+
# never sets is_correct, and the exchange exports as "not reviewed"
|
| 1121 |
+
# even though it was reviewed (Or_6.txt).
|
| 1122 |
+
if records[idx].get("original_classification") != "PROVIDER_SUMMARY":
|
| 1123 |
+
records[idx]["is_correct"] = (code == records[idx].get("original_classification"))
|
| 1124 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1125 |
+
saved_note = records[idx].get("verifier_notes", "") if isinstance(records[idx], dict) else ""
|
| 1126 |
+
# Keep the panel open with the saved note visible, even when the reviewer
|
| 1127 |
+
# agreed with the classification and only commented on the wording.
|
| 1128 |
+
return records, idx, "💾 Comment saved", html, pos, stats, gr.update(visible=True), str(saved_note), _classification_update(records, idx), _feedback_type_update(records, idx)
|
| 1129 |
|
| 1130 |
def _download_reviewed_json(meta: dict, records: list):
|
| 1131 |
return _export_conv_records_to_json(meta, records)
|
|
|
|
| 1135 |
|
| 1136 |
def _nav_conv(records: list, idx: int, delta: int):
|
| 1137 |
if not records:
|
| 1138 |
+
return idx, "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1139 |
idx = max(0, min(idx + delta, len(records) - 1))
|
| 1140 |
html, pos, stats = _render_conv_exchange(records, idx)
|
| 1141 |
row_upd, note_val = _comment_ui_state(records, idx)
|
| 1142 |
+
return idx, html, pos, stats, row_upd, note_val, _classification_update(records, idx), _feedback_type_update(records, idx)
|
| 1143 |
|
| 1144 |
|
| 1145 |
# ============================================================================
|
|
|
|
| 1154 |
"Provider Summary to be the final exchange presented in that tab"
|
| 1155 |
"""
|
| 1156 |
if session is None or not hasattr(session.app_instance, "conversation_logger"):
|
| 1157 |
+
return None, [], 0, "❌ No session/conversation found", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1158 |
if not session.app_instance.conversation_logger.entries:
|
| 1159 |
+
return None, [], 0, "⚠️ No exchanges to verify yet", "", "", "", gr.update(visible=False), "", gr.update(value=None), gr.update(value=[])
|
| 1160 |
|
| 1161 |
manager = ConversationVerificationManager()
|
| 1162 |
vs = manager.create_verification_session(session.app_instance.conversation_logger, "Medical Professional")
|
|
|
|
| 1223 |
"correct_classification": r.correct_classification,
|
| 1224 |
"correction_reason": r.correction_reason,
|
| 1225 |
"verifier_notes": r.verifier_notes,
|
| 1226 |
+
"feedback_types": [],
|
| 1227 |
"provider_summary": "", # Not shown in regular exchanges
|
| 1228 |
}
|
| 1229 |
for r in vs.verification_records
|
|
|
|
| 1246 |
"correct_classification": None,
|
| 1247 |
"correction_reason": "",
|
| 1248 |
"verifier_notes": "",
|
| 1249 |
+
"feedback_types": [],
|
| 1250 |
"provider_summary": provider_summary_text,
|
| 1251 |
"provider_summary_html": provider_summary_html,
|
| 1252 |
}
|
|
|
|
| 1255 |
html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
|
| 1256 |
row_upd, note_val = _comment_ui_state(records_as_dicts, 0)
|
| 1257 |
radio_upd = _classification_update(records_as_dicts, 0)
|
| 1258 |
+
types_upd = _feedback_type_update(records_as_dicts, 0)
|
| 1259 |
return (
|
| 1260 |
meta, records_as_dicts, 0,
|
| 1261 |
f"✅ Generated session with {len(records_as_dicts)} exchanges (Provider Summary as final step)",
|
| 1262 |
+
html, pos, stats, row_upd, note_val, radio_upd, types_upd,
|
| 1263 |
)
|
| 1264 |
|
| 1265 |
|