extends Node const QUESTION_FILE := "res://data/questions/sample_questions.json" const DEFAULT_QUESTIONS_API_URL := "http://localhost:5008/api/questions/search" signal questions_changed var questions: Array = [] var request: HTTPRequest var questions_api_url := DEFAULT_QUESTIONS_API_URL func _ready() -> void: questions_api_url = str(ProjectSettings.get_setting("step3/questions_api_url", DEFAULT_QUESTIONS_API_URL)) request = HTTPRequest.new() request.request_completed.connect(_on_api_questions_loaded) add_child(request) load_questions() func load_questions() -> void: questions.clear() var file := FileAccess.open(QUESTION_FILE, FileAccess.READ) if file == null: push_error("Unable to open question file: %s" % QUESTION_FILE) return var parsed = JSON.parse_string(file.get_as_text()) if typeof(parsed) != TYPE_ARRAY: push_error("Question file must contain an array.") return for item in parsed: if _is_valid_question(item): questions.append(item) else: push_warning("Skipping invalid question: %s" % str(item.get("id", "unknown"))) questions_changed.emit() func load_questions_from_api() -> void: if AuthState.access_token.is_empty(): return var headers := PackedStringArray([ "Content-Type: application/json", "Authorization: Bearer %s" % AuthState.access_token ]) var payload := { "limit": 80 } var err := request.request(questions_api_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload)) if err != OK: push_warning("Failed to request questions API. Using local question bank.") func _on_api_questions_loaded(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("Questions API request failed. Using local question bank.") return var parsed: Variant = JSON.parse_string(body.get_string_from_utf8()) if typeof(parsed) != TYPE_ARRAY: push_warning("Questions API response was not an array. Using local question bank.") return var api_questions: Array = [] for item in parsed: var normalized := _normalize_api_question(item) if _is_valid_question(normalized): api_questions.append(normalized) if api_questions.is_empty(): push_warning("Questions API returned no usable questions. Using local question bank.") return questions = api_questions questions_changed.emit() func get_all_questions() -> Array: return questions.duplicate(true) func get_systems() -> Array[String]: var system_names: Array[String] = [] for question in questions: var system_name := str(question.get("system", "General")) if not system_names.has(system_name): system_names.append(system_name) system_names.sort() return system_names func get_questions_for_system(system_name: String) -> Array: if system_name == "All Systems": return get_all_questions() var filtered: Array = [] for question in questions: if str(question.get("system", "")) == system_name: filtered.append(question.duplicate(true)) return filtered func build_block(system_name: String, block_size: int) -> Array: var pool := get_questions_for_system(system_name) pool.shuffle() return pool.slice(0, min(block_size, pool.size())) func _is_valid_question(item: Variant) -> bool: if typeof(item) != TYPE_DICTIONARY: return false var required := ["id", "stem", "lead_in", "choices", "correct_choice_id", "explanation", "system", "discipline"] for key in required: if not item.has(key): return false return typeof(item["choices"]) == TYPE_ARRAY and item["choices"].size() >= 2 func _normalize_api_question(item: Variant) -> Dictionary: if typeof(item) != TYPE_DICTIONARY: return {} var normalized := { "id": str(item.get("externalId", item.get("id", ""))), "api_id": str(item.get("id", "")), "stem": str(item.get("stem", "")), "lead_in": str(item.get("leadIn", item.get("lead_in", ""))), "choices": item.get("choices", []), "correct_choice_id": str(item.get("correctChoiceId", item.get("correct_choice_id", ""))), "explanation": str(item.get("explanation", "")), "choice_explanations": item.get("choiceExplanations", item.get("choice_explanations", {})), "discipline": str(item.get("discipline", "")), "system": str(item.get("system", "")), "difficulty": int(item.get("difficulty", 1)), "labs": item.get("labs", []), "media": item.get("media", []), "tags": item.get("tags", []) } return normalized