{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ch03-preview-title",
   "metadata": {},
   "source": [
    "# Preview\n",
    "\n",
    "- Use these questions before class to check your familiarity with the chapter glossary and core terms.\n",
    "- Sign in from the account menu before you start answering the questions.\n",
    "- Multiple submissions are allowed. The highest score is kept.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ch03-preview-quiz",
   "metadata": {},
   "source": [
    "<style>\n",
    ".ch03-preview-quiz { max-width: 900px; }\n",
    ".ch03-preview-question { border: 1px solid #d8dee4; border-radius: 6px; padding: 1rem; margin: 1rem 0; background: #fff; }\n",
    ".ch03-preview-question p { margin-top: 0; font-weight: 600; }\n",
    ".ch03-preview-question label { display: block; margin: 0.35rem 0; cursor: pointer; }\n",
    ".ch03-preview-question input { margin-right: 0.4rem; }\n",
    ".ch03-preview-actions { display: flex; gap: 0.75rem; align-items: center; margin: 1rem 0; flex-wrap: wrap; }\n",
    ".ch03-preview-actions button { border: 1px solid #176de8; border-radius: 6px; background: #176de8; color: white; padding: 0.55rem 0.9rem; cursor: pointer; font-weight: 600; }\n",
    ".ch03-preview-actions button.secondary { background: white; color: #176de8; }\n",
    ".ch03-preview-actions.is-locked { opacity: 0.55; }\n",
    ".ch03-preview-actions button:disabled { cursor: not-allowed; opacity: 0.65; }\n",
    ".ch03-preview-result { font-weight: 600; }\n",
    ".ch03-preview-feedback { margin-top: 0.65rem; font-weight: 600; }\n",
    ".ch03-preview-feedback.correct { color: #1a7f37; }\n",
    ".ch03-preview-feedback.incorrect { color: #cf222e; }\n",
    "</style>\n",
    "\n",
    "<div class='ch03-preview-quiz' id='ch03-preview-quiz'>\n",
    "  <div id='ch03-preview-questions'></div>\n",
    "  <div class='ch03-preview-actions'>\n",
    "    <button type='button' id='ch03-preview-submit' disabled>Submit</button>\n",
    "    <button type='button' class='secondary' id='ch03-preview-reset'>Reset</button>\n",
    "    <span class='ch03-preview-result' id='ch03-preview-result' aria-live='polite'></span>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(function () {\n",
    "  var quizId = 'ch03-preview';\n",
    "  var questions = [\n",
    "    {\n",
    "        \"id\": \"q1\",\n",
    "        \"prompt\": \"Which term matches this explanation? A Python library for fast numerical computing with arrays.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"Dimension\",\n",
    "            \"B\": \"Array\",\n",
    "            \"C\": \"ndarray\",\n",
    "            \"D\": \"NumPy\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q2\",\n",
    "        \"prompt\": \"Which term matches this explanation? A numbered direction along which NumPy organizes or reduces array values.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"NumPy\",\n",
    "            \"B\": \"Axis\",\n",
    "            \"C\": \"Array\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q3\",\n",
    "        \"prompt\": \"Which term matches this explanation? An array whose elements all share one data type.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"Homogeneous array\",\n",
    "            \"B\": \"Array\",\n",
    "            \"C\": \"NumPy\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q4\",\n",
    "        \"prompt\": \"Which term matches this explanation? A two-dimensional array organized by rows and columns.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"NumPy\",\n",
    "            \"B\": \"Matrix\",\n",
    "            \"C\": \"ndarray\",\n",
    "            \"D\": \"Array\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q5\",\n",
    "        \"prompt\": \"Which term matches this explanation? An array of True and False values used to filter or modify another array.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"Array\",\n",
    "            \"B\": \"NumPy\",\n",
    "            \"C\": \"Boolean mask\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q6\",\n",
    "        \"prompt\": \"Which term matches this explanation? NumPy's rule system for combining arrays with compatible but different shapes.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"NumPy\",\n",
    "            \"B\": \"Array\",\n",
    "            \"C\": \"Broadcasting\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q7\",\n",
    "        \"prompt\": \"Which term matches this explanation? A calculation that summarizes many values into fewer values, often one value.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"NumPy\",\n",
    "            \"B\": \"Array\",\n",
    "            \"C\": \"ndarray\",\n",
    "            \"D\": \"Aggregation\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q8\",\n",
    "        \"prompt\": \"Which term matches this explanation? An array object that refers to data owned by another array.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"View\",\n",
    "            \"B\": \"Array\",\n",
    "            \"C\": \"ndarray\",\n",
    "            \"D\": \"NumPy\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q9\",\n",
    "        \"prompt\": \"Which term matches this explanation? NumPy's recommended function for creating a modern random-number generator.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"default_rng()\",\n",
    "            \"B\": \"NumPy\",\n",
    "            \"C\": \"Array\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"id\": \"q10\",\n",
    "        \"prompt\": \"Which term matches this explanation? Repeatedly using random values to model uncertainty or possible outcomes.\",\n",
    "        \"choices\": {\n",
    "            \"A\": \"NumPy\",\n",
    "            \"B\": \"Simulation\",\n",
    "            \"C\": \"Array\",\n",
    "            \"D\": \"ndarray\"\n",
    "        }\n",
    "    }\n",
    "];\n",
    "\n",
    "  function text(value) {\n",
    "    return document.createTextNode(value);\n",
    "  }\n",
    "\n",
    "  function getParams() {\n",
    "    var params = new URLSearchParams(window.location.search);\n",
    "    return {\n",
    "      canvas_course_id: params.get('canvas_course_id') || params.get('course_id') || '',\n",
    "      canvas_assignment_id: params.get('canvas_assignment_id') || params.get('assignment_id') || '',\n",
    "      canvas_user_id: params.get('canvas_user_id') || params.get('user_id') || '',\n",
    "      student_identifier: params.get('student_identifier') || params.get('student_id') || params.get('email') || ''\n",
    "    };\n",
    "  }\n",
    "\n",
    "  function loadSessionIdentity(identity, actions, submit, result) {\n",
    "    submit.disabled = true;\n",
    "    actions.classList.add('is-locked');\n",
    "    result.textContent = 'Sign in from the account menu to submit this assignment.';\n",
    "\n",
    "    return fetch('/api/v1/session.php', { credentials: 'same-origin' })\n",
    "      .then(function (response) { return response.ok ? response.json() : null; })\n",
    "      .then(function (payload) {\n",
    "        if (!payload || !payload.authenticated || !payload.identity) return identity;\n",
    "        identity.canvas_user_id = payload.identity.canvas_user_id || identity.canvas_user_id || '';\n",
    "        identity.student_identifier = payload.identity.student_identifier || identity.student_identifier || '';\n",
    "        submit.disabled = false;\n",
    "        actions.classList.remove('is-locked');\n",
    "        result.textContent = payload.identity.display_name ? 'Signed in as ' + payload.identity.display_name + '.' : 'Signed in.';\n",
    "        return identity;\n",
    "      })\n",
    "      .catch(function () { return identity; });\n",
    "  }\n",
    "\n",
    "  function renderQuestions(container) {\n",
    "    questions.forEach(function (question, index) {\n",
    "      var card = document.createElement('div');\n",
    "      card.className = 'ch03-preview-question';\n",
    "      card.dataset.question = question.id;\n",
    "\n",
    "      var prompt = document.createElement('p');\n",
    "      prompt.appendChild(text((index + 1) + '. ' + question.prompt));\n",
    "      card.appendChild(prompt);\n",
    "\n",
    "      Object.keys(question.choices).forEach(function (letter) {\n",
    "        var label = document.createElement('label');\n",
    "        var input = document.createElement('input');\n",
    "        input.type = 'radio';\n",
    "        input.name = question.id;\n",
    "        input.value = letter;\n",
    "        label.appendChild(input);\n",
    "        label.appendChild(text(letter + '. ' + question.choices[letter]));\n",
    "        card.appendChild(label);\n",
    "      });\n",
    "\n",
    "      var feedback = document.createElement('div');\n",
    "      feedback.className = 'ch03-preview-feedback';\n",
    "      feedback.setAttribute('aria-live', 'polite');\n",
    "      card.appendChild(feedback);\n",
    "      container.appendChild(card);\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function collectAnswers(quiz) {\n",
    "    var answers = {};\n",
    "    questions.forEach(function (question) {\n",
    "      var selected = quiz.querySelector('input[name=' + question.id + ']:checked');\n",
    "      answers[question.id] = selected ? selected.value : '';\n",
    "    });\n",
    "    return answers;\n",
    "  }\n",
    "\n",
    "  function applyFeedback(quiz, feedback) {\n",
    "    questions.forEach(function (question) {\n",
    "      var card = quiz.querySelector('[data-question=' + question.id + ']');\n",
    "      var message = card.querySelector('.ch03-preview-feedback');\n",
    "      var item = feedback[question.id];\n",
    "      message.className = 'ch03-preview-feedback';\n",
    "      if (!item || !item.submitted) {\n",
    "        message.textContent = item && item.message ? item.message : 'No answer submitted.';\n",
    "        message.classList.add('incorrect');\n",
    "      } else if (item.correct) {\n",
    "        message.textContent = item.message || 'Correct.';\n",
    "        message.classList.add('correct');\n",
    "      } else {\n",
    "        message.textContent = item.message || 'Try again.';\n",
    "        message.classList.add('incorrect');\n",
    "      }\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function initPreviewQuiz() {\n",
    "    var quiz = document.getElementById('ch03-preview-quiz');\n",
    "    if (!quiz || quiz.dataset.ready === 'true') return;\n",
    "    quiz.dataset.ready = 'true';\n",
    "\n",
    "    var identity = getParams();\n",
    "    var questionsContainer = document.getElementById('ch03-preview-questions');\n",
    "    var result = document.getElementById('ch03-preview-result');\n",
    "    var submit = document.getElementById('ch03-preview-submit');\n",
    "    var actions = quiz.querySelector('.ch03-preview-actions');\n",
    "    var reset = document.getElementById('ch03-preview-reset');\n",
    "\n",
    "    renderQuestions(questionsContainer);\n",
    "    loadSessionIdentity(identity, actions, submit, result);\n",
    "\n",
    "    submit.addEventListener('click', function () {\n",
    "      var answers = collectAnswers(quiz);\n",
    "      submit.disabled = true;\n",
    "      result.textContent = 'Submitting...';\n",
    "\n",
    "      fetch('/api/v1/quiz-attempts.php', {\n",
    "        method: 'POST',\n",
    "        headers: { 'Content-Type': 'application/json' },\n",
    "        credentials: 'same-origin',\n",
    "        body: JSON.stringify({\n",
    "          quiz_id: quizId,\n",
    "          answers: answers,\n",
    "          student_identifier: identity.student_identifier || identity.canvas_user_id || '',\n",
    "          canvas_course_id: identity.canvas_course_id,\n",
    "          canvas_assignment_id: identity.canvas_assignment_id,\n",
    "          canvas_user_id: identity.canvas_user_id\n",
    "        })\n",
    "      })\n",
    "        .then(function (response) {\n",
    "          return response.text().then(function (text) {\n",
    "            var payload = null;\n",
    "            try {\n",
    "              payload = text ? JSON.parse(text) : null;\n",
    "            } catch (error) {\n",
    "              throw new Error('Server returned a non-JSON response (HTTP ' + response.status + '): ' + text.slice(0, 160));\n",
    "            }\n",
    "            if (!payload) {\n",
    "              throw new Error('Server returned an empty response (HTTP ' + response.status + '). Check that PHP is enabled for /api/v1/quiz-attempts.php.');\n",
    "            }\n",
    "            if (!response.ok || !payload.ok) {\n",
    "              throw new Error(payload.message || payload.error || 'Submission failed.');\n",
    "            }\n",
    "            return payload;\n",
    "          });\n",
    "        })\n",
    "        .then(function (payload) {\n",
    "          var best = (payload.best_score === null || typeof payload.best_score === 'undefined') ? payload.score : payload.best_score;\n",
    "          var attempt = (payload.attempt_count === null || typeof payload.attempt_count === 'undefined') ? '' : ' Attempt ' + payload.attempt_count + '.';\n",
    "          applyFeedback(quiz, payload.feedback || {});\n",
    "          result.textContent = 'Submission saved.' + attempt + ' Score: ' + payload.score + ' / ' + payload.max_score + '. Best score: ' + best + ' / ' + payload.max_score + '. Canvas sync: ' + payload.canvas_sync_status + '.';\n",
    "        })\n",
    "        .catch(function (error) {\n",
    "          result.textContent = 'Could not save this attempt: ' + error.message;\n",
    "        })\n",
    "        .finally(function () {\n",
    "          submit.disabled = false;\n",
    "        });\n",
    "    });\n",
    "\n",
    "    reset.addEventListener('click', function () {\n",
    "      quiz.querySelectorAll('input[type=radio]').forEach(function (input) {\n",
    "        input.checked = false;\n",
    "      });\n",
    "      quiz.querySelectorAll('.ch03-preview-feedback').forEach(function (feedback) {\n",
    "        feedback.className = 'ch03-preview-feedback';\n",
    "        feedback.textContent = '';\n",
    "      });\n",
    "      result.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "  if (document.readyState === 'loading') {\n",
    "    document.addEventListener('DOMContentLoaded', initPreviewQuiz);\n",
    "  } else {\n",
    "    initPreviewQuiz();\n",
    "  }\n",
    "})();\n",
    "</script>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
