Track real first attempt question stats

This commit is contained in:
hz
2026-07-11 01:42:18 -05:00
parent e991dccbb2
commit 8a86734a0b
13 changed files with 297 additions and 6 deletions
@@ -198,6 +198,15 @@ func get_answer_for_index(index: int) -> Variant:
return answers[index]
func update_question_choice_percentages(question_id: String, percentages: Dictionary) -> void:
for index in range(active_questions.size()):
var question: Dictionary = active_questions[index]
if str(question.get("id", "")) == question_id:
question["choice_selection_percentages"] = percentages
active_questions[index] = question
break
func advance_core_question() -> bool:
var question := _draw_core_question()
if question.is_empty():
+10
View File
@@ -123,6 +123,16 @@ func reset_profile() -> void:
profile_changed.emit()
func reset_qbank_memory() -> void:
_ensure_tracking_fields()
profile["completed_question_ids"] = []
profile["correct_question_ids"] = []
profile["question_wrong_counts"] = {}
profile["missed_question_ids"] = []
save_profile()
profile_changed.emit()
func apply_question_results(results: Dictionary) -> void:
_ensure_daily_current()
profile["xp"] = int(profile["xp"]) + int(results.get("xp_earned", 0))
+56
View File
@@ -1,15 +1,19 @@
extends Node
const DEFAULT_QUESTIONS_API_URL := "https://s3q.ranaze.com/api/questions/search"
const DEFAULT_QUESTION_ATTEMPT_API_URL := "https://s3q.ranaze.com/api/questions/attempts/first"
const RETRY_DELAYS := [2, 5, 10, 20, 30]
signal questions_changed
signal questions_load_failed(message: String)
signal choice_percentages_updated(question_id: String, percentages: Dictionary)
var questions: Array = []
var request: HTTPRequest
var attempt_request: HTTPRequest
var retry_timer: Timer
var questions_api_url := DEFAULT_QUESTIONS_API_URL
var question_attempt_api_url := DEFAULT_QUESTION_ATTEMPT_API_URL
var is_loading := false
var last_error := ""
var load_state := "idle"
@@ -18,10 +22,15 @@ var next_retry_seconds := 0
func _ready() -> void:
questions_api_url = str(ProjectSettings.get_setting("step3/questions_api_url", DEFAULT_QUESTIONS_API_URL))
question_attempt_api_url = str(ProjectSettings.get_setting("step3/question_attempt_api_url", DEFAULT_QUESTION_ATTEMPT_API_URL))
request = HTTPRequest.new()
request.request_completed.connect(_on_api_questions_loaded)
add_child(request)
attempt_request = HTTPRequest.new()
attempt_request.request_completed.connect(_on_first_attempt_recorded)
add_child(attempt_request)
retry_timer = Timer.new()
retry_timer.one_shot = true
retry_timer.timeout.connect(_on_retry_timer_timeout)
@@ -96,6 +105,8 @@ func retry_now() -> void:
func clear_questions() -> void:
if request != null:
request.cancel_request()
if attempt_request != null:
attempt_request.cancel_request()
if retry_timer != null:
retry_timer.stop()
questions.clear()
@@ -107,6 +118,23 @@ func clear_questions() -> void:
questions_changed.emit()
func record_first_attempt(question_id: String, choice_id: String) -> void:
if AuthState.access_token.is_empty() or question_id == "" or choice_id == "":
return
var headers := PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer %s" % AuthState.access_token
])
var payload := {
"questionExternalId": question_id,
"choiceId": choice_id
}
var err := attempt_request.request(question_attempt_api_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
if err != OK:
push_warning("Failed to record first question attempt.")
func get_all_questions() -> Array:
return questions.duplicate(true)
@@ -170,6 +198,34 @@ func _on_retry_timer_timeout() -> void:
load_questions_from_api(false)
func _on_first_attempt_recorded(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
push_warning("Question attempt API request failed with status %d." % response_code)
return
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
if typeof(parsed) != TYPE_DICTIONARY:
push_warning("Question attempt API response was not an object.")
return
var question_id := str(parsed.get("questionExternalId", parsed.get("question_external_id", "")))
var percentages: Dictionary = parsed.get("choiceSelectionPercentages", parsed.get("choice_selection_percentages", {}))
if question_id == "":
return
_update_question_percentages(question_id, percentages)
choice_percentages_updated.emit(question_id, percentages)
func _update_question_percentages(question_id: String, percentages: Dictionary) -> void:
for index in range(questions.size()):
var question: Dictionary = questions[index]
if str(question.get("id", "")) == question_id:
question["choice_selection_percentages"] = percentages
questions[index] = question
return
func _is_valid_question(item: Variant) -> bool:
if typeof(item) != TYPE_DICTIONARY:
return false