Add data-driven dynamic question mode
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
extends Control
|
||||
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
|
||||
var main
|
||||
var status_label: Label
|
||||
var start_button: Button
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(center)
|
||||
var card := VBoxContainer.new()
|
||||
card.custom_minimum_size = Vector2(620, 0)
|
||||
card.add_theme_constant_override("separation", 18)
|
||||
center.add_child(card)
|
||||
var title := Label.new()
|
||||
title.text = "Dynamic Questions"
|
||||
title.add_theme_font_override("font", Brand.font)
|
||||
title.add_theme_font_size_override("font_size", 34)
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
card.add_child(title)
|
||||
var description := Label.new()
|
||||
description.text = "Endless, data-driven Step 3 practice. Clinical values and distractors vary while each tested objective stays medically invariant."
|
||||
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
description.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
card.add_child(description)
|
||||
status_label = Label.new()
|
||||
status_label.text = "Checking available objectives…"
|
||||
status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
card.add_child(status_label)
|
||||
start_button = Button.new()
|
||||
start_button.text = "Start Dynamic Questions"
|
||||
start_button.custom_minimum_size = Vector2(0, 52)
|
||||
start_button.disabled = true
|
||||
start_button.pressed.connect(_start)
|
||||
card.add_child(start_button)
|
||||
DynamicQuestionService.status_loaded.connect(_on_status)
|
||||
DynamicQuestionService.question_loaded.connect(_on_question)
|
||||
DynamicQuestionService.request_failed.connect(_on_failed)
|
||||
|
||||
|
||||
func on_show(_payload: Variant = null) -> void:
|
||||
start_button.disabled = true
|
||||
status_label.text = "Checking available objectives…"
|
||||
DynamicQuestionService.load_status()
|
||||
|
||||
|
||||
func _on_status(status: Dictionary) -> void:
|
||||
var available := bool(status.get("available", false))
|
||||
var count := int(status.get("publishedObjectives", 0))
|
||||
var attempts := int(status.get("totalAttempts", 0))
|
||||
status_label.text = "%d objectives • %d attempts • mastery %d" % [
|
||||
count, attempts, int(status.get("averageObjectiveElo", 1000))]
|
||||
start_button.disabled = not available
|
||||
if not available:
|
||||
status_label.text = "Dynamic question content has not been published yet."
|
||||
|
||||
|
||||
func _start() -> void:
|
||||
start_button.disabled = true
|
||||
start_button.text = "Generating…"
|
||||
DynamicQuestionService.reset_run()
|
||||
DynamicQuestionService.load_next()
|
||||
|
||||
|
||||
func _on_question(question: Dictionary) -> void:
|
||||
start_button.text = "Start Dynamic Questions"
|
||||
start_button.disabled = false
|
||||
if BlockSessionService.start_dynamic_question(question):
|
||||
main.show_screen("player")
|
||||
|
||||
|
||||
func _on_failed(message: String) -> void:
|
||||
start_button.text = "Try Again"
|
||||
start_button.disabled = false
|
||||
status_label.text = message
|
||||
@@ -0,0 +1 @@
|
||||
uid://53fdgx3f3am1
|
||||
@@ -248,6 +248,12 @@ func _build_ui() -> void:
|
||||
screens_container.add_child(selector)
|
||||
screens["selection"] = selector
|
||||
|
||||
var dynamic_questions := preload("res://scripts/ui/dynamic_questions_screen.gd").new()
|
||||
dynamic_questions.name = "DynamicQuestionsScreen"
|
||||
dynamic_questions.main = self
|
||||
screens_container.add_child(dynamic_questions)
|
||||
screens["dynamic_questions"] = dynamic_questions
|
||||
|
||||
var map := preload("res://scripts/ui/study_map.gd").new()
|
||||
map.name = "StudyMap"
|
||||
map.main = self
|
||||
@@ -410,6 +416,7 @@ func _populate_navigation(container: VBoxContainer, include_brand: bool) -> void
|
||||
|
||||
_add_nav_button(container, "home", "Dashboard", "chart-no-axes-column-increasing", true)
|
||||
_add_nav_button(container, "question_bank", "Question Bank", "clipboard-list", true)
|
||||
_add_nav_button(container, "dynamic_questions", "Dynamic Questions", "shuffle", true)
|
||||
_add_nav_button(container, "ccs", "CCS Cases", "stethoscope", true)
|
||||
_add_nav_button(container, "exams", "Exam Simulations", "clipboard-list", true)
|
||||
_add_nav_button(container, "flashcards", "Flashcards", "clipboard-plus", true)
|
||||
@@ -454,6 +461,7 @@ func _on_navigation_pressed(key: String) -> void:
|
||||
match key:
|
||||
"home": show_screen("dashboard")
|
||||
"question_bank": show_screen("selection", "question_bank")
|
||||
"dynamic_questions": show_screen("dynamic_questions")
|
||||
"exams": show_screen("simulation_history")
|
||||
"ccs": show_screen("ccs_hub")
|
||||
"flashcards": show_screen("flashcards_study")
|
||||
@@ -472,6 +480,7 @@ func _update_header_title(screen_name: String) -> void:
|
||||
var titles := {
|
||||
"dashboard": "Dashboard",
|
||||
"selection": "Question Bank",
|
||||
"dynamic_questions": "Dynamic Questions",
|
||||
"simulation_history": "Exam Simulations",
|
||||
"ccs_hub": "CCS Cases",
|
||||
"flashcards_hub": "Flashcards",
|
||||
|
||||
@@ -10,7 +10,9 @@ static func key_for_screen(screen_name: String, payload: Variant = null) -> Stri
|
||||
elif screen_name in ["map", "setup", "review"]:
|
||||
key = "study_plan" if screen_name == "map" else "question_bank"
|
||||
elif screen_name == "player":
|
||||
key = "question_bank"
|
||||
key = "dynamic_questions" if BlockSessionService.session_kind == "dynamic" else "question_bank"
|
||||
elif screen_name == "dynamic_questions":
|
||||
key = "dynamic_questions"
|
||||
elif screen_name.begins_with("simulation_"):
|
||||
key = "exams"
|
||||
elif screen_name.begins_with("ccs_"):
|
||||
|
||||
@@ -164,6 +164,14 @@ func _ready() -> void:
|
||||
QuestionBankService.question_report_submitted.connect(_on_question_report_submitted)
|
||||
if not QuestionBankService.question_report_failed.is_connected(_on_question_report_failed):
|
||||
QuestionBankService.question_report_failed.connect(_on_question_report_failed)
|
||||
if not DynamicQuestionService.question_loaded.is_connected(_on_dynamic_question_loaded):
|
||||
DynamicQuestionService.question_loaded.connect(_on_dynamic_question_loaded)
|
||||
if not DynamicQuestionService.request_failed.is_connected(_on_dynamic_question_failed):
|
||||
DynamicQuestionService.request_failed.connect(_on_dynamic_question_failed)
|
||||
if not DynamicQuestionService.report_submitted.is_connected(_on_question_report_submitted):
|
||||
DynamicQuestionService.report_submitted.connect(_on_question_report_submitted)
|
||||
if not DynamicQuestionService.report_failed.is_connected(_on_question_report_failed):
|
||||
DynamicQuestionService.report_failed.connect(_on_question_report_failed)
|
||||
if not QuestionBankService.questions_changed.is_connected(_on_random_question_changed):
|
||||
QuestionBankService.questions_changed.connect(_on_random_question_changed)
|
||||
if not QuestionBankService.questions_load_failed.is_connected(_on_random_question_failed):
|
||||
@@ -1115,7 +1123,7 @@ func _load_question() -> void:
|
||||
next_button.text = "Submit"
|
||||
next_button.disabled = true
|
||||
skip_button.disabled = false
|
||||
skip_button.visible = BlockSessionService.session_kind != "review"
|
||||
skip_button.visible = BlockSessionService.session_kind not in ["review", "dynamic"]
|
||||
skip_button.text = "Skip"
|
||||
report_button.visible = BlockSessionService.session_kind != "simulation"
|
||||
|
||||
@@ -1123,13 +1131,16 @@ func _load_question() -> void:
|
||||
if question.is_empty():
|
||||
if BlockSessionService.session_kind == "review":
|
||||
main.show_screen("dashboard")
|
||||
elif BlockSessionService.session_kind == "dynamic":
|
||||
main.show_screen("dynamic_questions")
|
||||
else:
|
||||
main.show_screen("bookmarks" if BlockSessionService.session_kind == "bookmark" else "selection", "exam" if BlockSessionService.session_kind == "core" else "step3")
|
||||
return
|
||||
BookmarkService.observe_question(question)
|
||||
if BlockSessionService.session_kind != "dynamic":
|
||||
BookmarkService.observe_question(question)
|
||||
progress_button.text = _question_progress_text()
|
||||
progress_button.disabled = BlockSessionService.session_kind in ["core", "bookmark", "review"]
|
||||
progress_button.tooltip_text = "" if BlockSessionService.session_kind in ["core", "bookmark", "review"] else "Open question navigator"
|
||||
progress_button.disabled = BlockSessionService.session_kind in ["core", "bookmark", "review", "dynamic"]
|
||||
progress_button.tooltip_text = "" if BlockSessionService.session_kind in ["core", "bookmark", "review", "dynamic"] else "Open question navigator"
|
||||
var show_timer := _is_session_timer_visible()
|
||||
timer_label.visible = show_timer
|
||||
timer_bar.visible = show_timer
|
||||
@@ -1373,6 +1384,12 @@ func _submit_or_next() -> void:
|
||||
if selected_choice == "":
|
||||
return
|
||||
var question_id_for_attempt := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
if BlockSessionService.session_kind == "dynamic":
|
||||
var elapsed := int(floor(BlockSessionService.get_current_elapsed_seconds()))
|
||||
DynamicQuestionService.submit_attempt(selected_choice, elapsed)
|
||||
current_answer = BlockSessionService.answer_current(selected_choice, eliminated, false)
|
||||
_show_answer_review()
|
||||
return
|
||||
if BlockSessionService.session_kind in ["core", "bookmark", "review"]:
|
||||
var attempted_choice := selected_choice
|
||||
if BlockSessionService.session_kind == "core":
|
||||
@@ -1422,7 +1439,12 @@ func _submit_or_next() -> void:
|
||||
current_answer = BlockSessionService.answer_current(selected_choice, eliminated, flagged)
|
||||
_show_answer_review()
|
||||
else:
|
||||
if BlockSessionService.session_kind == "core":
|
||||
if BlockSessionService.session_kind == "dynamic":
|
||||
awaiting_next_question = true
|
||||
next_button.text = "Generating…"
|
||||
next_button.disabled = true
|
||||
DynamicQuestionService.load_next()
|
||||
elif BlockSessionService.session_kind == "core":
|
||||
_request_next_core_question()
|
||||
elif BlockSessionService.session_kind == "bookmark":
|
||||
if BookmarkService.has_next_practice():
|
||||
@@ -1441,6 +1463,26 @@ func _submit_or_next() -> void:
|
||||
_advance_or_finish()
|
||||
|
||||
|
||||
func _on_dynamic_question_loaded(question: Dictionary) -> void:
|
||||
if not awaiting_next_question or BlockSessionService.session_kind != "dynamic":
|
||||
return
|
||||
if not BlockSessionService.replace_dynamic_question(question):
|
||||
_on_dynamic_question_failed("The next dynamic question could not be prepared.")
|
||||
return
|
||||
awaiting_next_question = false
|
||||
_load_question()
|
||||
UISoundService.play_sound("question")
|
||||
|
||||
|
||||
func _on_dynamic_question_failed(message: String) -> void:
|
||||
if not awaiting_next_question or BlockSessionService.session_kind != "dynamic":
|
||||
return
|
||||
awaiting_next_question = false
|
||||
next_button.text = "Retry"
|
||||
next_button.disabled = false
|
||||
next_button.tooltip_text = message
|
||||
|
||||
|
||||
func _request_next_core_question() -> void:
|
||||
awaiting_next_question = true
|
||||
next_button.text = "Loading..."
|
||||
@@ -2053,12 +2095,12 @@ func _refresh_toolbar_state() -> void:
|
||||
var question_id := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
report_button.tooltip_text = "Report submitted; report another issue" if reported_question_ids.has(question_id) else "Report question"
|
||||
if notes_button != null:
|
||||
notes_button.visible = BlockSessionService.session_kind != "simulation"
|
||||
notes_button.visible = BlockSessionService.session_kind not in ["simulation", "dynamic"]
|
||||
if flashcard_button != null:
|
||||
flashcard_button.visible = BlockSessionService.session_kind != "simulation"
|
||||
flashcard_button.visible = BlockSessionService.session_kind not in ["simulation", "dynamic"]
|
||||
flashcard_button.disabled = current_answer.is_empty()
|
||||
if bookmark_button != null:
|
||||
bookmark_button.visible = BlockSessionService.session_kind != "simulation"
|
||||
bookmark_button.visible = BlockSessionService.session_kind not in ["simulation", "dynamic"]
|
||||
var bookmark_question_id := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
var bookmark_value := BookmarkService.is_bookmarked(bookmark_question_id)
|
||||
var bookmark_pending := BookmarkService.is_pending(bookmark_question_id)
|
||||
@@ -2797,7 +2839,10 @@ func _submit_question_report() -> void:
|
||||
return
|
||||
report_error_label.text = ""
|
||||
_set_report_submitting(true)
|
||||
QuestionBankService.submit_question_report(report_question_id, category, details)
|
||||
if BlockSessionService.session_kind == "dynamic":
|
||||
DynamicQuestionService.submit_report(report_question_id, category, details)
|
||||
else:
|
||||
QuestionBankService.submit_question_report(report_question_id, category, details)
|
||||
|
||||
|
||||
func _on_question_report_submitted(question_id: String, _report_id: String) -> void:
|
||||
|
||||
@@ -155,7 +155,7 @@ func _build_mode_view() -> void:
|
||||
mode_view.add_child(title)
|
||||
|
||||
var subtitle := Label.new()
|
||||
subtitle.text = "Build clinical judgment through focused questions. Additional modes are in development."
|
||||
subtitle.text = "Build clinical judgment through focused authored questions."
|
||||
subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
subtitle.add_theme_font_override("font", Brand.font)
|
||||
subtitle.add_theme_font_size_override("font_size", 17)
|
||||
@@ -187,16 +187,6 @@ func _build_mode_view() -> void:
|
||||
retry_button = question_card.retry
|
||||
retry_button.pressed.connect(_retry_question_bank)
|
||||
|
||||
_add_mode_card(
|
||||
"Dynamic Questions",
|
||||
"AI-generated practice",
|
||||
"Coming Soon",
|
||||
"Coming Soon",
|
||||
"Endless practice with AI-generated questions\ntailored to your performance.",
|
||||
"activity",
|
||||
false,
|
||||
Brand.PURPLE)
|
||||
|
||||
var ccs_card := _add_mode_card(
|
||||
"CCS Cases",
|
||||
"Interactive case simulations",
|
||||
|
||||
Reference in New Issue
Block a user