DocUA Claude Opus 4.7 (1M context) commited on
Commit
251ab3e
·
1 Parent(s): 543fc05

fix: Or feedback — Provider Summary flag error, stale verification on clear, latency timing

Browse files

#1 Conversation Verification "Error" badge on every component when saving the
final Provider Summary with text only: the handlers returned "" for the
correct-classification Radio, which Gradio 6 rejects (value not in choices),
failing the whole event response. Replace "" with None across all conv
verification handlers (via new _classification_update helper) and hide the flag
on the PROVIDER_SUMMARY final step, since the flag was already chosen earlier.
Also fixes a latent arity mismatch in the empty-records early returns.

#2 "Clear Chat" left the Conversation Verification tab showing the previous
scenario. handle_clear_simplified now also resets conv_verify_state/records/index
and the displayed components; clear_btn wiring updated (mirrored in gradio_app /
chat_handlers for local parity).

#3 Add lightweight per-stage [PERF] timing in the message path (PERF_TIMING env,
default on) to diagnose >10s responses. No model changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

src/interface/chat_handlers.py CHANGED
@@ -172,7 +172,15 @@ def handle_clear(session: SimplifiedSessionData):
172
  gr.update(visible=False), # Hide provider_summary_content group
173
  "No provider summary available", # Clear provider_summary_status
174
  "", # Clear provider_summary_display HTML
175
- "" # Clear coherent_summary_display - direct value
 
 
 
 
 
 
 
 
176
  )
177
 
178
  def send_example(example_text: str, history, session: SimplifiedSessionData):
 
172
  gr.update(visible=False), # Hide provider_summary_content group
173
  "No provider summary available", # Clear provider_summary_status
174
  "", # Clear provider_summary_display HTML
175
+ "", # Clear coherent_summary_display - direct value
176
+ # Also reset the Conversation Verification tab (avoid stale scenario)
177
+ None, # conv_verify_state
178
+ [], # conv_verify_records
179
+ 0, # conv_verify_index
180
+ "", # conv_verify_status
181
+ "", # conv_verify_exchange
182
+ "", # conv_position
183
+ "", # conv_stats
184
  )
185
 
186
  def send_example(example_text: str, history, session: SimplifiedSessionData):
src/interface/gradio_app.py CHANGED
@@ -582,7 +582,13 @@ def create_simplified_interface():
582
  clear_btn.click(
583
  chat_handlers.handle_clear,
584
  inputs=[session_data],
585
- outputs=[chatbot, status_box, session_data, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
 
 
 
 
 
 
586
  )
587
 
588
  # Refresh status
 
582
  clear_btn.click(
583
  chat_handlers.handle_clear,
584
  inputs=[session_data],
585
+ outputs=[
586
+ chatbot, status_box, session_data,
587
+ provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display,
588
+ # Also reset the Conversation Verification tab (avoid stale scenario)
589
+ conv_verify_state, conv_verify_records, conv_verify_index,
590
+ conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
591
+ ]
592
  )
593
 
594
  # Refresh status
src/interface/simplified_chat_handlers.py CHANGED
@@ -9,9 +9,21 @@ show the Provider Summary message for the spiritual care team."
9
 
10
  import gradio as gr
11
  import html
 
 
12
  from src.interface.session_manager import SimplifiedSessionData
13
  from src.interface.stats_handlers import get_conversation_stats
14
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  def handle_message_simplified(message: str, history, session: SimplifiedSessionData):
17
  """
@@ -25,7 +37,8 @@ def handle_message_simplified(message: str, history, session: SimplifiedSessionD
25
  """
26
  if session is None:
27
  session = SimplifiedSessionData()
28
-
 
29
  session.update_activity()
30
 
31
  # Apply per-session model overrides (if configured)
@@ -45,9 +58,11 @@ def handle_message_simplified(message: str, history, session: SimplifiedSessionD
45
  # Store previous spiritual state to detect transitions
46
  previous_state = session.app_instance.spiritual_state.spiritual_state
47
 
48
- # Process message
 
49
  new_history, status = session.app_instance.process_message(message, history)
50
-
 
51
  # Check if we just transitioned to RED state (consent was given)
52
  current_state = session.app_instance.spiritual_state.spiritual_state
53
  consent_just_given = (previous_state.value == "awaiting_consent" and
@@ -61,8 +76,10 @@ def handle_message_simplified(message: str, history, session: SimplifiedSessionD
61
  # Get the COHERENT NARRATIVE summary (LLM-generated from spiritual_care_message.txt)
62
  # This is the Medical Brain compatible format, not the structured format
63
  try:
 
64
  provider_summary_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
65
-
 
66
  if not provider_summary_text:
67
  # Fallback to structured format if coherent generation fails
68
  provider_summary_text = session.app_instance.provider_summary_generator.format_for_export(last_summary)
@@ -154,11 +171,13 @@ def handle_message_simplified(message: str, history, session: SimplifiedSessionD
154
 
155
  # Get updated conversation stats
156
  stats = get_conversation_stats(session)
157
-
 
 
158
  return (
159
- new_history,
160
- status,
161
- session,
162
  "", # Clear input
163
  stats
164
  )
@@ -167,22 +186,31 @@ def handle_message_simplified(message: str, history, session: SimplifiedSessionD
167
  def handle_clear_simplified(session: SimplifiedSessionData):
168
  """
169
  Handle clear chat button for simplified interface.
170
-
171
  Resets entire session including:
172
  - Chat history
173
  - Spiritual monitoring state
174
  - Conversation statistics
 
 
175
  """
176
  if session is None:
177
  session = SimplifiedSessionData()
178
-
179
  session.update_activity()
180
  new_history, status = session.app_instance.reset_session()
181
-
182
  return (
183
  new_history, # Clear chat history
184
  status, # Reset status
185
- session # Updated session
 
 
 
 
 
 
 
186
  )
187
 
188
 
 
9
 
10
  import gradio as gr
11
  import html
12
+ import os
13
+ import time
14
  from src.interface.session_manager import SimplifiedSessionData
15
  from src.interface.stats_handlers import get_conversation_stats
16
 
17
+ # Lightweight latency diagnosis. Prints per-stage timing to the server log (visible
18
+ # in Hugging Face Spaces logs) so we can see where a >10s response is spent.
19
+ # Enabled by default; set PERF_TIMING=false to silence. Overhead is negligible.
20
+ _PERF_TIMING = os.getenv("PERF_TIMING", "true").lower() == "true"
21
+
22
+
23
+ def _perf(label: str, start: float):
24
+ if _PERF_TIMING:
25
+ print(f"[PERF] {label}: {(time.perf_counter() - start) * 1000:.0f} ms")
26
+
27
 
28
  def handle_message_simplified(message: str, history, session: SimplifiedSessionData):
29
  """
 
37
  """
38
  if session is None:
39
  session = SimplifiedSessionData()
40
+
41
+ _t_handler = time.perf_counter()
42
  session.update_activity()
43
 
44
  # Apply per-session model overrides (if configured)
 
58
  # Store previous spiritual state to detect transitions
59
  previous_state = session.app_instance.spiritual_state.spiritual_state
60
 
61
+ # Process message (classification + assistant response — usually the bulk of latency)
62
+ _t_pm = time.perf_counter()
63
  new_history, status = session.app_instance.process_message(message, history)
64
+ _perf("process_message", _t_pm)
65
+
66
  # Check if we just transitioned to RED state (consent was given)
67
  current_state = session.app_instance.spiritual_state.spiritual_state
68
  consent_just_given = (previous_state.value == "awaiting_consent" and
 
76
  # Get the COHERENT NARRATIVE summary (LLM-generated from spiritual_care_message.txt)
77
  # This is the Medical Brain compatible format, not the structured format
78
  try:
79
+ _t_cs = time.perf_counter()
80
  provider_summary_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
81
+ _perf("format_coherent_paragraph (extra LLM call, one-time on RED+consent)", _t_cs)
82
+
83
  if not provider_summary_text:
84
  # Fallback to structured format if coherent generation fails
85
  provider_summary_text = session.app_instance.provider_summary_generator.format_for_export(last_summary)
 
171
 
172
  # Get updated conversation stats
173
  stats = get_conversation_stats(session)
174
+
175
+ _perf("handle_message_simplified TOTAL", _t_handler)
176
+
177
  return (
178
+ new_history,
179
+ status,
180
+ session,
181
  "", # Clear input
182
  stats
183
  )
 
186
  def handle_clear_simplified(session: SimplifiedSessionData):
187
  """
188
  Handle clear chat button for simplified interface.
189
+
190
  Resets entire session including:
191
  - Chat history
192
  - Spiritual monitoring state
193
  - Conversation statistics
194
+ - Conversation Verification tab (state + displayed components), so a new
195
+ scenario doesn't show the previous scenario's verification.
196
  """
197
  if session is None:
198
  session = SimplifiedSessionData()
199
+
200
  session.update_activity()
201
  new_history, status = session.app_instance.reset_session()
202
+
203
  return (
204
  new_history, # Clear chat history
205
  status, # Reset status
206
+ session, # Updated session
207
+ None, # conv_verify_state
208
+ [], # conv_verify_records
209
+ 0, # conv_verify_index
210
+ "", # conv_verify_status
211
+ "", # conv_verify_exchange
212
+ "", # conv_position
213
+ "", # conv_stats
214
  )
215
 
216
 
src/interface/simplified_gradio_app.py CHANGED
@@ -167,14 +167,14 @@ def create_simplified_interface():
167
  # Shown only when marking Incorrect
168
  with gr.Row(visible=False) as conv_incorrect_comment_row:
169
  with gr.Column(scale=3):
170
- gr.Markdown("### Select Correct Classification:")
171
  conv_correct_classification = gr.Radio(
172
  choices=[
173
  "🟢 Should be GREEN - No distress",
174
- "🟡 Should be YELLOW - Needs clarification",
175
  "🔴 Should be RED - Spiritual distress"
176
  ],
177
- label="Correct Classification",
178
  interactive=True
179
  )
180
  conv_incorrect_comment = gr.Textbox(
@@ -497,7 +497,12 @@ def create_simplified_interface():
497
  clear_btn.click(
498
  simplified_chat_handlers.handle_clear_simplified,
499
  inputs=[session_data],
500
- outputs=[chatbot, status_box, session_data]
 
 
 
 
 
501
  )
502
 
503
  # Refresh status
 
167
  # Shown only when marking Incorrect
168
  with gr.Row(visible=False) as conv_incorrect_comment_row:
169
  with gr.Column(scale=3):
170
+ # Hidden on the Provider Summary final step (flag already chosen earlier).
171
  conv_correct_classification = gr.Radio(
172
  choices=[
173
  "🟢 Should be GREEN - No distress",
174
+ "🟡 Should be YELLOW - Needs clarification",
175
  "🔴 Should be RED - Spiritual distress"
176
  ],
177
+ label="Select Correct Classification",
178
  interactive=True
179
  )
180
  conv_incorrect_comment = gr.Textbox(
 
497
  clear_btn.click(
498
  simplified_chat_handlers.handle_clear_simplified,
499
  inputs=[session_data],
500
+ outputs=[
501
+ chatbot, status_box, session_data,
502
+ # Also reset the Conversation Verification tab (avoid stale scenario)
503
+ conv_verify_state, conv_verify_records, conv_verify_index,
504
+ conv_verify_status, conv_verify_exchange, conv_position, conv_stats,
505
+ ]
506
  )
507
 
508
  # Refresh status
src/interface/verification_handlers.py CHANGED
@@ -1005,9 +1005,32 @@ def _generate_conv_verification(session: SimplifiedSessionData):
1005
  html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
1006
  return meta, records_as_dicts, 0, f"✅ Generated session `{vs.session_id}`", html, pos, stats
1007
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1008
  def _mark_conv_correct(records: list, idx: int):
1009
  if not records:
1010
- return records, idx, "", "", "", gr.update(visible=False), "", ""
1011
  idx = max(0, min(idx, len(records) - 1))
1012
  if isinstance(records[idx], dict):
1013
  records[idx]["is_correct"] = True
@@ -1016,29 +1039,17 @@ def _mark_conv_correct(records: list, idx: int):
1016
  records[idx]["correct_classification"] = None
1017
  html, pos, stats = _render_conv_exchange(records, idx)
1018
  row_upd, note_val = _comment_ui_state(records, idx)
1019
- return records, idx, "✅ Marked correct", html, pos, stats, row_upd, note_val, ""
1020
 
1021
  def _mark_conv_incorrect(records: list, idx: int):
1022
  if not records:
1023
- return records, idx, "", "", "", gr.update(visible=False), "", ""
1024
  idx = max(0, min(idx, len(records) - 1))
1025
  if isinstance(records[idx], dict):
1026
  records[idx]["is_correct"] = False
1027
  html, pos, stats = _render_conv_exchange(records, idx)
1028
  row_upd, note_val = _comment_ui_state(records, idx)
1029
- # Get existing correct_classification if any
1030
- existing_classification = ""
1031
- if isinstance(records[idx], dict):
1032
- correct_class = records[idx].get("correct_classification")
1033
- if correct_class:
1034
- # Map back to display text
1035
- reverse_map = {
1036
- "GREEN": "🟢 Should be GREEN - No distress",
1037
- "YELLOW": "🟡 Should be YELLOW - Needs clarification",
1038
- "RED": "🔴 Should be RED - Spiritual distress"
1039
- }
1040
- existing_classification = reverse_map.get(correct_class, "")
1041
- return records, idx, "❌ Marked incorrect", html, pos, stats, row_upd, note_val, existing_classification
1042
 
1043
  def _show_incorrect_comment_ui(records: list, idx: int):
1044
  """Mark incorrect and open the comment row, pre-filling any existing note."""
@@ -1047,14 +1058,14 @@ def _show_incorrect_comment_ui(records: list, idx: int):
1047
 
1048
  def _save_incorrect_comment(records: list, idx: int, note: str, correct_classification: str):
1049
  if not records:
1050
- return records, idx, "", "", "", "", gr.update(visible=False), "", ""
1051
  idx = max(0, min(idx, len(records) - 1))
1052
  if isinstance(records[idx], dict):
1053
  records[idx]["verifier_notes"] = (note or "").strip()
1054
  # Map display text to classification code
1055
  classification_map = {
1056
  "🟢 Should be GREEN - No distress": "GREEN",
1057
- "🟡 Should be YELLOW - Needs clarification": "YELLOW",
1058
  "🔴 Should be RED - Spiritual distress": "RED"
1059
  }
1060
  if correct_classification and correct_classification in classification_map:
@@ -1062,7 +1073,7 @@ def _save_incorrect_comment(records: list, idx: int, note: str, correct_classifi
1062
  html, pos, stats = _render_conv_exchange(records, idx)
1063
  row_upd, note_val = _comment_ui_state(records, idx)
1064
  # keep row visible after save (since still incorrect)
1065
- return records, idx, "💾 Comment saved", html, pos, stats, row_upd, note_val, ""
1066
 
1067
  def _download_reviewed_json(meta: dict, records: list):
1068
  return _export_conv_records_to_json(meta, records)
@@ -1072,22 +1083,11 @@ def _download_reviewed_csv(meta: dict, records: list):
1072
 
1073
  def _nav_conv(records: list, idx: int, delta: int):
1074
  if not records:
1075
- return idx, "", "", "", gr.update(visible=False), "", ""
1076
  idx = max(0, min(idx + delta, len(records) - 1))
1077
  html, pos, stats = _render_conv_exchange(records, idx)
1078
  row_upd, note_val = _comment_ui_state(records, idx)
1079
- # Get existing correct_classification if any
1080
- existing_classification = ""
1081
- if isinstance(records[idx], dict):
1082
- correct_class = records[idx].get("correct_classification")
1083
- if correct_class:
1084
- reverse_map = {
1085
- "GREEN": "🟢 Should be GREEN - No distress",
1086
- "YELLOW": "🟡 Should be YELLOW - Needs clarification",
1087
- "RED": "🔴 Should be RED - Spiritual distress"
1088
- }
1089
- existing_classification = reverse_map.get(correct_class, "")
1090
- return idx, html, pos, stats, row_upd, note_val, existing_classification
1091
 
1092
 
1093
  # ============================================================================
 
1005
  html, pos, stats = _render_conv_exchange(records_as_dicts, 0)
1006
  return meta, records_as_dicts, 0, f"✅ Generated session `{vs.session_id}`", html, pos, stats
1007
 
1008
+ # Display text for each classification flag (shared by the verification handlers).
1009
+ _CLASSIFICATION_DISPLAY = {
1010
+ "GREEN": "🟢 Should be GREEN - No distress",
1011
+ "YELLOW": "🟡 Should be YELLOW - Needs clarification",
1012
+ "RED": "🔴 Should be RED - Spiritual distress",
1013
+ }
1014
+
1015
+ def _classification_update(records: list, idx: int):
1016
+ """Build the gr.update for the 'Correct Classification' Radio.
1017
+
1018
+ - Hides the Radio on the PROVIDER_SUMMARY final step (the flag was already
1019
+ chosen in earlier phases, so it must not be required there).
1020
+ - Always uses None (never "") as the value: Gradio rejects a Radio value that
1021
+ is not one of its choices and fails the whole response with an "Error" badge.
1022
+ """
1023
+ value = None
1024
+ is_provider_summary = False
1025
+ if records and 0 <= idx < len(records) and isinstance(records[idx], dict):
1026
+ r = records[idx]
1027
+ is_provider_summary = r.get("original_classification") == "PROVIDER_SUMMARY"
1028
+ value = _CLASSIFICATION_DISPLAY.get(r.get("correct_classification"))
1029
+ return gr.update(visible=not is_provider_summary, value=value)
1030
+
1031
  def _mark_conv_correct(records: list, idx: int):
1032
  if not records:
1033
+ return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
1034
  idx = max(0, min(idx, len(records) - 1))
1035
  if isinstance(records[idx], dict):
1036
  records[idx]["is_correct"] = True
 
1039
  records[idx]["correct_classification"] = None
1040
  html, pos, stats = _render_conv_exchange(records, idx)
1041
  row_upd, note_val = _comment_ui_state(records, idx)
1042
+ return records, idx, "✅ Marked correct", html, pos, stats, row_upd, note_val, _classification_update(records, idx)
1043
 
1044
  def _mark_conv_incorrect(records: list, idx: int):
1045
  if not records:
1046
+ return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
1047
  idx = max(0, min(idx, len(records) - 1))
1048
  if isinstance(records[idx], dict):
1049
  records[idx]["is_correct"] = False
1050
  html, pos, stats = _render_conv_exchange(records, idx)
1051
  row_upd, note_val = _comment_ui_state(records, idx)
1052
+ return records, idx, "❌ Marked incorrect", html, pos, stats, row_upd, note_val, _classification_update(records, idx)
 
 
 
 
 
 
 
 
 
 
 
 
1053
 
1054
  def _show_incorrect_comment_ui(records: list, idx: int):
1055
  """Mark incorrect and open the comment row, pre-filling any existing note."""
 
1058
 
1059
  def _save_incorrect_comment(records: list, idx: int, note: str, correct_classification: str):
1060
  if not records:
1061
+ return records, idx, "", "", "", "", gr.update(visible=False), "", gr.update(value=None)
1062
  idx = max(0, min(idx, len(records) - 1))
1063
  if isinstance(records[idx], dict):
1064
  records[idx]["verifier_notes"] = (note or "").strip()
1065
  # Map display text to classification code
1066
  classification_map = {
1067
  "🟢 Should be GREEN - No distress": "GREEN",
1068
+ "🟡 Should be YELLOW - Needs clarification": "YELLOW",
1069
  "🔴 Should be RED - Spiritual distress": "RED"
1070
  }
1071
  if correct_classification and correct_classification in classification_map:
 
1073
  html, pos, stats = _render_conv_exchange(records, idx)
1074
  row_upd, note_val = _comment_ui_state(records, idx)
1075
  # keep row visible after save (since still incorrect)
1076
+ return records, idx, "💾 Comment saved", html, pos, stats, row_upd, note_val, _classification_update(records, idx)
1077
 
1078
  def _download_reviewed_json(meta: dict, records: list):
1079
  return _export_conv_records_to_json(meta, records)
 
1083
 
1084
  def _nav_conv(records: list, idx: int, delta: int):
1085
  if not records:
1086
+ return idx, "", "", "", gr.update(visible=False), "", gr.update(value=None)
1087
  idx = max(0, min(idx + delta, len(records) - 1))
1088
  html, pos, stats = _render_conv_exchange(records, idx)
1089
  row_upd, note_val = _comment_ui_state(records, idx)
1090
+ return idx, html, pos, stats, row_upd, note_val, _classification_update(records, idx)
 
 
 
 
 
 
 
 
 
 
 
1091
 
1092
 
1093
  # ============================================================================