Add variable question distractor pools

This commit is contained in:
hz
2026-07-13 10:15:05 -05:00
parent f2c59388eb
commit 93154d5925
24 changed files with 1040 additions and 247 deletions
+31 -1
View File
@@ -30,6 +30,8 @@ func start_block(selected_system: String, block_size: int, selected_mode: String
mode = selected_mode
session_kind = "block"
active_questions = QuestionBankService.build_block(selected_system, block_size)
for index in range(active_questions.size()):
active_questions[index] = _prepare_question_choices(active_questions[index])
answers.clear()
answers.resize(active_questions.size())
current_index = 0
@@ -293,10 +295,38 @@ func _draw_core_question() -> Dictionary:
var candidate: Dictionary = core_question_pool[index]
if str(candidate.get("id", "")) != last_id or core_question_pool.size() == 1:
core_question_pool.remove_at(index)
return candidate.duplicate(true)
return _prepare_question_choices(candidate.duplicate(true))
return {}
func _prepare_question_choices(question: Dictionary) -> Dictionary:
var existing: Array = question.get("presented_choices", [])
if not existing.is_empty():
return question
var all_choices: Array = question.get("choices", []).duplicate(true)
var correct_id := str(question.get("correct_choice_id", ""))
var correct_choice: Dictionary = {}
var distractors: Array = []
for choice in all_choices:
if typeof(choice) != TYPE_DICTIONARY:
continue
if str(choice.get("id", "")) == correct_id:
correct_choice = choice
else:
distractors.append(choice)
distractors.shuffle()
var presented: Array = distractors.slice(0, min(4, distractors.size()))
if not correct_choice.is_empty():
presented.append(correct_choice)
elif not all_choices.is_empty():
presented = all_choices.slice(0, min(5, all_choices.size()))
presented.shuffle()
question["presented_choices"] = presented
return question
func _build_core_question_pool() -> Array:
var pool: Array = []
var profile := ProfileService.get_profile()
+9 -5
View File
@@ -103,7 +103,8 @@ func load_questions_from_api(reset_retry: bool = true) -> void:
"Authorization: Bearer %s" % AuthState.access_token
])
var payload := {
"limit": 80
"limit": 80,
"supportsChoicePools": true
}
var err := request.request(questions_api_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
if err != OK:
@@ -177,10 +178,10 @@ func clear_questions() -> void:
ratings_changed.emit()
func record_first_attempt(question_id: String, choice_id: String) -> void:
func record_first_attempt(question_id: String, choice_id: String, presented_choice_ids: Array = []) -> void:
if AuthState.access_token.is_empty() or question_id == "" or choice_id == "":
return
_enqueue_attempt("answer", question_id, choice_id)
_enqueue_attempt("answer", question_id, choice_id, presented_choice_ids)
func skip_question(question_id: String) -> void:
@@ -294,7 +295,7 @@ func _on_retry_timer_timeout() -> void:
load_questions_from_api(false)
func _enqueue_attempt(kind: String, question_id: String, choice_id: String) -> void:
func _enqueue_attempt(kind: String, question_id: String, choice_id: String, presented_choice_ids: Array = []) -> void:
var account := AuthState.username.strip_edges()
if account == "":
return
@@ -306,6 +307,7 @@ func _enqueue_attempt(kind: String, question_id: String, choice_id: String) -> v
"kind": kind,
"question_id": question_id,
"choice_id": choice_id,
"presented_choice_ids": presented_choice_ids.duplicate(),
"retry_count": 0
})
_save_attempt_queue()
@@ -335,7 +337,8 @@ func _process_attempt_queue() -> void:
var url := question_attempt_api_url
var body := JSON.stringify({
"questionExternalId": question_id,
"choiceId": str(queued.get("choice_id", ""))
"choiceId": str(queued.get("choice_id", "")),
"presentedChoiceIds": queued.get("presented_choice_ids", [])
})
if str(queued.get("kind", "answer")) == "skip":
url = "%s/%s/skip" % [question_api_base_url.rstrip("/"), question_id.uri_encode()]
@@ -457,6 +460,7 @@ func _load_attempt_queue() -> void:
"kind": kind,
"question_id": question_id,
"choice_id": str(item.get("choice_id", "")),
"presented_choice_ids": item.get("presented_choice_ids", []).duplicate(),
"retry_count": max(0, int(item.get("retry_count", 0)))
})
+20 -3
View File
@@ -442,8 +442,7 @@ func _load_question() -> void:
lead_label.text = question.get("lead_in", "")
_refresh_labs_button()
presented_choices = question.get("choices", []).duplicate(true)
presented_choices.shuffle()
presented_choices = question.get("presented_choices", question.get("choices", [])).duplicate(true)
var choices := presented_choices
for index in range(choice_buttons.size()):
var button := choice_buttons[index]
@@ -613,7 +612,11 @@ func _submit_or_next() -> void:
_collapse_resolved_followups()
var attempted_choice := selected_choice
var attempted_question_id := str(BlockSessionService.get_current_question().get("id", ""))
QuestionBankService.record_first_attempt(attempted_question_id, attempted_choice)
QuestionBankService.record_first_attempt(
attempted_question_id,
attempted_choice,
_presented_choice_ids()
)
var result := BlockSessionService.submit_core_attempt(selected_choice, eliminated, flagged)
if result.is_empty():
return
@@ -632,6 +635,12 @@ func _submit_or_next() -> void:
_trigger_wrong_feedback()
_apply_saved_choice_state()
return
var attempted_question_id := str(BlockSessionService.get_current_question().get("id", ""))
QuestionBankService.record_first_attempt(
attempted_question_id,
selected_choice,
_presented_choice_ids()
)
current_answer = BlockSessionService.answer_current(selected_choice, eliminated, flagged)
if BlockSessionService.mode == "Tutor":
_show_explanation()
@@ -1871,6 +1880,14 @@ func _get_presented_choices() -> Array:
return BlockSessionService.get_current_question().get("choices", [])
func _presented_choice_ids() -> Array:
var ids: Array = []
for choice in _get_presented_choices():
if typeof(choice) == TYPE_DICTIONARY:
ids.append(str(choice.get("id", "")))
return ids
func _display_label_for_index(index: int) -> String:
return String.chr(65 + index)