Constrain CCS patient dialogue
This commit is contained in:
@@ -103,6 +103,7 @@ antialiased = true
|
|||||||
|
|
||||||
[node name="DialogueBubble" type="Control" parent="."]
|
[node name="DialogueBubble" type="Control" parent="."]
|
||||||
z_index = 30
|
z_index = 30
|
||||||
|
clip_contents = true
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 0
|
anchors_preset = 0
|
||||||
anchor_left = 0.53
|
anchor_left = 0.53
|
||||||
@@ -127,7 +128,7 @@ texture = ExtResource("6_chat_bubble")
|
|||||||
expand_mode = 1
|
expand_mode = 1
|
||||||
stretch_mode = 0
|
stretch_mode = 0
|
||||||
|
|
||||||
[node name="Text" type="Label" parent="DialogueBubble"]
|
[node name="Text" type="RichTextLabel" parent="DialogueBubble"]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -141,6 +142,8 @@ grow_vertical = 2
|
|||||||
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
|
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
|
||||||
theme_override_font_sizes/font_size = 20
|
theme_override_font_sizes/font_size = 20
|
||||||
text = "Hello, doctor."
|
text = "Hello, doctor."
|
||||||
|
fit_content = false
|
||||||
|
scroll_active = false
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const DIALOGUE_PAGE_MAX_CHARS := 82
|
|||||||
@onready var second_hand: Node2D = $ClockMount/SecondHand
|
@onready var second_hand: Node2D = $ClockMount/SecondHand
|
||||||
@onready var patient: TextureRect = $Patient
|
@onready var patient: TextureRect = $Patient
|
||||||
@onready var dialogue_bubble: Control = $DialogueBubble
|
@onready var dialogue_bubble: Control = $DialogueBubble
|
||||||
@onready var dialogue_text: Label = $DialogueBubble/Text
|
@onready var dialogue_text: RichTextLabel = $DialogueBubble/Text
|
||||||
@onready var dialogue_continue_hint: Label = $DialogueBubble/ContinueHint
|
@onready var dialogue_continue_hint: Label = $DialogueBubble/ContinueHint
|
||||||
@onready var choice_area: Control = $DialogueChoiceArea
|
@onready var choice_area: Control = $DialogueChoiceArea
|
||||||
@onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices
|
@onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ const Brand := preload("res://scripts/ui/medmind_style.gd")
|
|||||||
const Style := preload("res://scripts/ui/ui_style.gd")
|
const Style := preload("res://scripts/ui/ui_style.gd")
|
||||||
const DOCTOR_SCENE := preload("res://scenes/ccs_cases_2/doctor_office.tscn")
|
const DOCTOR_SCENE := preload("res://scenes/ccs_cases_2/doctor_office.tscn")
|
||||||
const DESIGN_SIZE := Vector2(1500.0, 800.0)
|
const DESIGN_SIZE := Vector2(1500.0, 800.0)
|
||||||
|
const FOCUSED_INTERVIEW_CASE_ID := "ccs-gastro-duodenal-ulcer-office-001"
|
||||||
|
const RETIRED_INTERVIEW_TOPIC_IDS := [
|
||||||
|
"abdominal_symptoms", "bleeding_alarm_features", "medications",
|
||||||
|
"medical_history", "social_history", "additional_review"
|
||||||
|
]
|
||||||
|
|
||||||
var main
|
var main
|
||||||
var stage_root: Control
|
var stage_root: Control
|
||||||
@@ -16,6 +21,7 @@ var current_view := ""
|
|||||||
var cases_loaded := false
|
var cases_loaded := false
|
||||||
var active_loaded := false
|
var active_loaded := false
|
||||||
var pending_action := ""
|
var pending_action := ""
|
||||||
|
var restart_case_external_id := ""
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -39,10 +45,13 @@ func _connect_service() -> void:
|
|||||||
CCSService.attempt_changed.connect(_on_attempt_changed)
|
CCSService.attempt_changed.connect(_on_attempt_changed)
|
||||||
if not CCSService.request_failed.is_connected(_on_request_failed):
|
if not CCSService.request_failed.is_connected(_on_request_failed):
|
||||||
CCSService.request_failed.connect(_on_request_failed)
|
CCSService.request_failed.connect(_on_request_failed)
|
||||||
|
if not CCSService.attempt_abandoned.is_connected(_on_attempt_abandoned):
|
||||||
|
CCSService.attempt_abandoned.connect(_on_attempt_abandoned)
|
||||||
|
|
||||||
|
|
||||||
func on_show(_payload: Variant = null) -> void:
|
func on_show(_payload: Variant = null) -> void:
|
||||||
CCSService.launch_origin = "ccs_cases_2"
|
CCSService.launch_origin = "ccs_cases_2"
|
||||||
|
restart_case_external_id = ""
|
||||||
cases_loaded = false
|
cases_loaded = false
|
||||||
active_loaded = false
|
active_loaded = false
|
||||||
pending_action = "loading"
|
pending_action = "loading"
|
||||||
@@ -52,6 +61,7 @@ func on_show(_payload: Variant = null) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func on_hide() -> void:
|
func on_hide() -> void:
|
||||||
|
restart_case_external_id = ""
|
||||||
_clear_active_scene()
|
_clear_active_scene()
|
||||||
|
|
||||||
|
|
||||||
@@ -102,6 +112,9 @@ func _render_attempt(attempt: Dictionary) -> void:
|
|||||||
if node.is_empty():
|
if node.is_empty():
|
||||||
_show_unsupported_attempt("The graphical interview could not load its current dialogue.")
|
_show_unsupported_attempt("The graphical interview could not load its current dialogue.")
|
||||||
return
|
return
|
||||||
|
if _is_retired_interview_revision(attempt, node):
|
||||||
|
_restart_with_current_interview(attempt)
|
||||||
|
return
|
||||||
if str(attempt.get("experienceMode", "management")) != "graphical_interview" \
|
if str(attempt.get("experienceMode", "management")) != "graphical_interview" \
|
||||||
and bool(attempt.get("openingCompleted", false)) and not bool(node.get("isTerminal", false)):
|
and bool(attempt.get("openingCompleted", false)) and not bool(node.get("isTerminal", false)):
|
||||||
_show_unsupported_attempt("This case has progressed beyond the graphical interview.")
|
_show_unsupported_attempt("This case has progressed beyond the graphical interview.")
|
||||||
@@ -263,6 +276,37 @@ func _repeat_interview() -> void:
|
|||||||
CCSService.create_attempt(case_external_id, "timed")
|
CCSService.create_attempt(case_external_id, "timed")
|
||||||
|
|
||||||
|
|
||||||
|
func _is_retired_interview_revision(attempt: Dictionary, node: Dictionary) -> bool:
|
||||||
|
if str(attempt.get("caseExternalId", "")) != FOCUSED_INTERVIEW_CASE_ID:
|
||||||
|
return false
|
||||||
|
if str(node.get("id", "")) in RETIRED_INTERVIEW_TOPIC_IDS:
|
||||||
|
return true
|
||||||
|
for raw_choice in node.get("choices", []):
|
||||||
|
if str(raw_choice.get("id", "")) in RETIRED_INTERVIEW_TOPIC_IDS:
|
||||||
|
return true
|
||||||
|
var patient_text := str(node.get("patientText", "")).to_lower()
|
||||||
|
return patient_text.contains("stomach bothers me") or patient_text.contains("gnawing ache")
|
||||||
|
|
||||||
|
|
||||||
|
func _restart_with_current_interview(attempt: Dictionary) -> void:
|
||||||
|
if pending_action == "refreshing_interview":
|
||||||
|
return
|
||||||
|
restart_case_external_id = str(attempt.get("caseExternalId", ""))
|
||||||
|
pending_action = "refreshing_interview"
|
||||||
|
_show_selection("Updating this interview to its latest dizziness and fatigue dialogue...")
|
||||||
|
CCSService.abandon()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_attempt_abandoned() -> void:
|
||||||
|
if restart_case_external_id.is_empty():
|
||||||
|
return
|
||||||
|
var case_external_id := restart_case_external_id
|
||||||
|
restart_case_external_id = ""
|
||||||
|
pending_action = "creating"
|
||||||
|
status_label.text = "Starting the updated interview..."
|
||||||
|
CCSService.create_attempt(case_external_id, "timed")
|
||||||
|
|
||||||
|
|
||||||
func _on_request_failed(message: String) -> void:
|
func _on_request_failed(message: String) -> void:
|
||||||
if not visible:
|
if not visible:
|
||||||
return
|
return
|
||||||
@@ -272,6 +316,11 @@ func _on_request_failed(message: String) -> void:
|
|||||||
status_label.text = "An active case was found. Loading it now..."
|
status_label.text = "An active case was found. Loading it now..."
|
||||||
CCSService.load_active()
|
CCSService.load_active()
|
||||||
return
|
return
|
||||||
|
if failed_action == "refreshing_interview":
|
||||||
|
restart_case_external_id = ""
|
||||||
|
status_label.text = "The updated interview could not be started. %s" % message
|
||||||
|
retry_button.visible = true
|
||||||
|
return
|
||||||
if active_scene != null:
|
if active_scene != null:
|
||||||
active_scene.set_choices_pending(false)
|
active_scene.set_choices_pending(false)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -54,6 +54,20 @@ func _run() -> void:
|
|||||||
await process_frame
|
await process_frame
|
||||||
_assert(screen.current_view == "case_selection", "CCS Cases 2 should begin with compatible case selection")
|
_assert(screen.current_view == "case_selection", "CCS Cases 2 should begin with compatible case selection")
|
||||||
_assert(screen.case_list.get_child_count() == 1, "legacy cases should be excluded from graphical selection")
|
_assert(screen.case_list.get_child_count() == 1, "legacy cases should be excluded from graphical selection")
|
||||||
|
var retired_attempt := {
|
||||||
|
"caseExternalId": "ccs-gastro-duodenal-ulcer-office-001"
|
||||||
|
}
|
||||||
|
_assert(screen._is_retired_interview_revision(retired_attempt, {
|
||||||
|
"id": "fatigue_course", "patientText": "My stomach bothers me.", "choices": []
|
||||||
|
}), "the retired stomach response should trigger a current-revision restart")
|
||||||
|
_assert(screen._is_retired_interview_revision(retired_attempt, {
|
||||||
|
"id": "chief_concern", "patientText": "Dizzy.",
|
||||||
|
"choices": [{"id": "abdominal_symptoms", "text": "Ask about stomach pain"}]
|
||||||
|
}), "retired stomach-topic choices should trigger a current-revision restart")
|
||||||
|
_assert(not screen._is_retired_interview_revision(retired_attempt, {
|
||||||
|
"id": "chief_concern", "patientText": "Dizzy and tired.",
|
||||||
|
"choices": [{"id": "dizziness_character", "text": "Ask about dizziness"}]
|
||||||
|
}), "the focused dizziness and fatigue revision should remain active")
|
||||||
|
|
||||||
service.attempt = _sample_attempt(false)
|
service.attempt = _sample_attempt(false)
|
||||||
screen._render_state()
|
screen._render_state()
|
||||||
@@ -219,11 +233,16 @@ func _choice_labels_fit(scene: Control) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _label_content_fits(label: Label) -> bool:
|
func _label_content_fits(label: Control) -> bool:
|
||||||
var font := label.get_theme_font("font")
|
if label is RichTextLabel:
|
||||||
var font_size := label.get_theme_font_size("font_size")
|
return label.get_content_height() <= label.size.y + 0.5
|
||||||
var content_height := float(label.get_line_count()) * font.get_height(font_size)
|
var plain_label := label as Label
|
||||||
return content_height <= label.size.y + 0.5
|
if plain_label == null:
|
||||||
|
return false
|
||||||
|
var font := plain_label.get_theme_font("font")
|
||||||
|
var font_size := plain_label.get_theme_font_size("font_size")
|
||||||
|
var content_height := float(plain_label.get_line_count()) * font.get_height(font_size)
|
||||||
|
return content_height <= plain_label.size.y + 0.5
|
||||||
|
|
||||||
|
|
||||||
func _assert(condition: bool, message: String) -> void:
|
func _assert(condition: bool, message: String) -> void:
|
||||||
|
|||||||
Reference in New Issue
Block a user