{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ch08-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",
    "\n",
    "Review the [Chapter 12 glossary](../1200-regression.ipynb) before class. These ten questions mix multiple-choice and true/false: seven recognize terms and three connect related ideas. No calculations or code tracing are required.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ch08-preview-quiz",
   "metadata": {},
   "source": [
    "<style>\n",
    ".ch12-preview-quiz { max-width: 900px; }\n",
    ".ch12-preview-question { border: 1px solid #d8dee4; border-radius: 6px; padding: 1rem; margin: 1rem 0; background: #fff; }\n",
    ".ch12-preview-question p { margin-top: 0; font-weight: 600; }\n",
    ".ch12-preview-question label { display: block; margin: 0.35rem 0; cursor: pointer; }\n",
    ".ch12-preview-question input { margin-right: 0.4rem; }\n",
    ".ch12-preview-actions { display: flex; gap: 0.75rem; align-items: center; margin: 1rem 0; flex-wrap: wrap; }\n",
    ".ch12-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",
    ".ch12-preview-actions button.secondary { background: white; color: #176de8; }\n",
    ".ch12-preview-actions.is-locked { opacity: 0.55; }\n",
    ".ch12-preview-actions button:disabled { cursor: not-allowed; opacity: 0.65; }\n",
    ".ch12-preview-result { font-weight: 600; }\n",
    ".ch12-preview-feedback { margin-top: 0.65rem; font-weight: 600; }\n",
    ".ch12-preview-feedback.correct { color: #1a7f37; }\n",
    ".ch12-preview-feedback.incorrect { color: #cf222e; }\n",
    "</style>\n",
    "\n",
    "<div class='ch12-preview-quiz' id='ch12-preview-quiz' data-question-set-version='dsm-ch12-preview-2026-10-19-v2'>\n",
    "  <p>Earlier submissions and best scores are retained. If you already completed this Preview, you do not need to repeat it; you may retry the revised questions.</p>\n",
    "  <div id='ch12-preview-questions'></div>\n",
    "  <div class='ch12-preview-actions'>\n",
    "    <button type='button' id='ch12-preview-submit' disabled>Submit</button>\n",
    "    <button type='button' class='secondary' id='ch12-preview-reset'>Reset</button>\n",
    "    <span class='ch12-preview-result' id='ch12-preview-result' aria-live='polite'></span>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(function () {\n",
    "  var quizId = 'ch12-preview';\n",
    "  var questionSetVersion = 'dsm-ch12-preview-2026-10-19-v2';\n",
    "  var answersUnlocked = false;\n",
    "  var attemptState = null;\n",
    "  var stateQueue = Promise.resolve();\n",
    "\n",
    "  function stateRequest(action, extra) {\n",
    "    return fetch('/api/v1/preview-options.php', {\n",
    "      method: 'POST', credentials: 'same-origin',\n",
    "      headers: { 'Content-Type': 'application/json' },\n",
    "      body: JSON.stringify(Object.assign({\n",
    "        assignment_id: quizId, version: questionSetVersion, action: action,\n",
    "        page: window.location.pathname.replace(/^\\/+/, '').replace(/\\.html$/, ''),\n",
    "        questions: authoredQuestions, legacy_questions: legacyQuestions, legacy_version: legacyVersion, token: attemptState && attemptState.token,\n",
    "        revision: attemptState && attemptState.revision\n",
    "      }, extra || {}))\n",
    "    }).then(function (response) {\n",
    "      return response.json().then(function (payload) {\n",
    "        if (!response.ok || !payload.ok) throw new Error(payload.error || 'Could not save Preview state.');\n",
    "        attemptState = payload.state;\n",
    "        return payload;\n",
    "      });\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function draftAnswers(quiz) {\n",
    "    var answers = {};\n",
    "    questions.forEach(function (question) {\n",
    "      var input = quiz.querySelector('input[name=' + question.id + ']:checked');\n",
    "      if (input) answers[question.id] = input.value;\n",
    "    });\n",
    "    return answers;\n",
    "  }\n",
    "\n",
    "  function saveAnswers(quiz) {\n",
    "    var answers = draftAnswers(quiz);\n",
    "    stateQueue = stateQueue.catch(function () {}).then(function () {\n",
    "      return stateRequest('save', { answers: answers });\n",
    "    });\n",
    "    return stateQueue;\n",
    "  }\n",
    "  var questions = [\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"A company predicts a numerical delivery time from travel distance. Which modeling task fits this goal?\",\n",
    "    \"concepts\": [\n",
    "      \"Regression\",\n",
    "      \"Prediction\"\n",
    "    ],\n",
    "    \"explanation\": \"Regression models or predicts an outcome from input features; here the outcome is numerical delivery time. Classification predicts categories, while clustering seeks groups without a known target label.\",\n",
    "    \"retry_hint\": \"Identify the kind of outcome the model is asked to predict.\",\n",
    "    \"id\": \"q1\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1202-regression-line.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Classification into delivery categories\",\n",
    "      \"B\": \"Clustering deliveries without a target\",\n",
    "      \"C\": \"Random assignment of delivery routes\",\n",
    "      \"D\": \"Regression\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"true_false\",\n",
    "    \"prompt\": \"True or false: A correlation between two variables, by itself, proves that changing one causes a change in the other.\",\n",
    "    \"concepts\": [\n",
    "      \"Correlation\",\n",
    "      \"Association\"\n",
    "    ],\n",
    "    \"explanation\": \"False. Correlation describes linear association, not proof of causation. Other influences, selection, or coincidence may help explain an observed relationship.\",\n",
    "    \"retry_hint\": \"Separate a pattern in paired data from a claim about cause and effect.\",\n",
    "    \"id\": \"q2\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1201-correlation.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"True\",\n",
    "      \"B\": \"False\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"true_false\",\n",
    "    \"prompt\": \"True or false: A feature is an input used to predict the target outcome.\",\n",
    "    \"concepts\": [\n",
    "      \"Feature\",\n",
    "      \"Target\"\n",
    "    ],\n",
    "    \"explanation\": \"True. Features are the inputs used for prediction, while the target is the outcome being predicted. Distinguishing their roles helps describe what a regression model does.\",\n",
    "    \"retry_hint\": \"Review the roles of model inputs and the outcome of interest.\",\n",
    "    \"id\": \"q3\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1202-regression-line.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"True\",\n",
    "      \"B\": \"False\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"Which quantity describes the predicted change in the outcome when a regression-line feature increases by one unit?\",\n",
    "    \"concepts\": [\n",
    "      \"Slope\",\n",
    "      \"Regression line\"\n",
    "    ],\n",
    "    \"explanation\": \"Slope expresses the model-predicted change in the outcome for a one-unit feature increase. The intercept is the prediction at a feature value of zero; a residual is an observed prediction error.\",\n",
    "    \"retry_hint\": \"Distinguish the line's change per unit from its value at zero.\",\n",
    "    \"id\": \"q4\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1202-regression-line.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Intercept\",\n",
    "      \"B\": \"Residual\",\n",
    "      \"C\": \"Slope\",\n",
    "      \"D\": \"Fitted value\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"Which name is given to the model prediction for a case already observed in the data?\",\n",
    "    \"concepts\": [\n",
    "      \"Fitted value\",\n",
    "      \"Prediction\"\n",
    "    ],\n",
    "    \"explanation\": \"A fitted value is the model prediction for an observed case. It may differ from the observed target; that difference, with the stated sign convention, is the residual.\",\n",
    "    \"retry_hint\": \"Separate the predicted value from the actual observation and their difference.\",\n",
    "    \"id\": \"q5\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1202-regression-line.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Fitted value\",\n",
    "      \"B\": \"Residual\",\n",
    "      \"C\": \"Influential point\",\n",
    "      \"D\": \"Feature\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"What distinguishes the least-squares criterion used to fit a regression line?\",\n",
    "    \"concepts\": [\n",
    "      \"Least squares\",\n",
    "      \"Residual\"\n",
    "    ],\n",
    "    \"explanation\": \"Least squares compares candidate lines using squared residuals and chooses the smallest total. Squaring prevents positive and negative errors from canceling in that criterion.\",\n",
    "    \"retry_hint\": \"Review how prediction errors are combined into a fitting criterion.\",\n",
    "    \"id\": \"q6\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1203-least-squares.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Minimizing the sum of squared residuals\",\n",
    "      \"B\": \"Minimizing the sum of absolute residuals\",\n",
    "      \"C\": \"Allowing signed residuals to cancel without squaring\",\n",
    "      \"D\": \"Making only the largest residual small\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"true_false\",\n",
    "    \"prompt\": \"True or false: A residual is defined as the fitted value minus the observed value.\",\n",
    "    \"concepts\": [\n",
    "      \"Residual\",\n",
    "      \"Fitted value\"\n",
    "    ],\n",
    "    \"explanation\": \"False. This chapter defines a residual as observed value minus fitted value. A positive residual means the observed outcome is higher than the model predicted.\",\n",
    "    \"retry_hint\": \"Check the subtraction order in the glossary before interpreting the sign.\",\n",
    "    \"id\": \"q7\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"reading\": \"../1203-least-squares.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"True\",\n",
    "      \"B\": \"False\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"A residual plot shows a curved pattern rather than an unstructured cloud around zero. Which connection is most relevant?\",\n",
    "    \"concepts\": [\n",
    "      \"Residual plot\",\n",
    "      \"Diagnostics\",\n",
    "      \"Linearity\"\n",
    "    ],\n",
    "    \"explanation\": \"Residual diagnostics connect patterns in prediction errors to model limitations. A curve can indicate that a straight line misses systematic structure; it does not establish a causal relationship.\",\n",
    "    \"retry_hint\": \"Connect the purpose of diagnostics with the assumption a regression line makes.\",\n",
    "    \"id\": \"q8\",\n",
    "    \"cognitive_level\": \"connection\",\n",
    "    \"reading\": \"../1204-visual-diag-residual.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"A curved residual pattern proves causation\",\n",
    "      \"B\": \"Residuals are the input features themselves\",\n",
    "      \"C\": \"Residual diagnostics can reveal a problem with the straight-line assumption\",\n",
    "      \"D\": \"A curved pattern guarantees zero prediction error\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"A model predicts demand using product price as its feature. Its training prices range from $10 to $50. Which caution applies when it is used for a $500 price?\",\n",
    "    \"concepts\": [\n",
    "      \"Extrapolation\",\n",
    "      \"Prediction\"\n",
    "    ],\n",
    "    \"explanation\": \"Extrapolation predicts outside the range represented by the data. A relationship fitted within one range may not continue in a distant range, so the new prediction needs particular caution.\",\n",
    "    \"retry_hint\": \"Compare the prediction's feature value with the range represented by the fitted data.\",\n",
    "    \"id\": \"q9\",\n",
    "    \"cognitive_level\": \"connection\",\n",
    "    \"reading\": \"../1204-visual-diag-residual.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Interpolation within the observed feature range\",\n",
    "      \"B\": \"Random assignment of products\",\n",
    "      \"C\": \"Extrapolation beyond the observed feature range\",\n",
    "      \"D\": \"The definition of a residual\"\n",
    "    }\n",
    "  },\n",
    "  {\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"prompt\": \"A manager wants an error summary stated in the same units as the predicted sales amount. Which measure fits that purpose?\",\n",
    "    \"concepts\": [\n",
    "      \"Root mean squared error\",\n",
    "      \"Residual\"\n",
    "    ],\n",
    "    \"explanation\": \"Root mean squared error summarizes squared prediction errors and then takes their square root, returning to the outcome units. Correlation describes standardized linear association rather than prediction error in sales units.\",\n",
    "    \"retry_hint\": \"Distinguish an error measured in target units from a standardized association measure.\",\n",
    "    \"id\": \"q10\",\n",
    "    \"cognitive_level\": \"connection\",\n",
    "    \"reading\": \"../1203-least-squares.html\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Correlation coefficient\",\n",
    "      \"B\": \"The number of input features\",\n",
    "      \"C\": \"The row number of the largest residual\",\n",
    "      \"D\": \"Root mean squared error\"\n",
    "    }\n",
    "  }\n",
    "];\n",
    "\n",
    "  var legacyQuestions = [\n",
    "  {\n",
    "    \"id\": \"q1\",\n",
    "    \"prompt\": \"Which term matches this explanation? A method for modeling or predicting an outcome from one or more features.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Target\",\n",
    "      \"B\": \"Prediction\",\n",
    "      \"C\": \"Feature\",\n",
    "      \"D\": \"Regression\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Regression\"\n",
    "    ],\n",
    "    \"explanation\": \"Regression: A method for modeling or predicting an outcome from one or more features. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q2\",\n",
    "    \"prompt\": \"Which term matches this explanation? An input variable used to predict an outcome.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Target\",\n",
    "      \"B\": \"Feature\",\n",
    "      \"C\": \"Scatter plot\",\n",
    "      \"D\": \"Association\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Feature\"\n",
    "    ],\n",
    "    \"explanation\": \"Feature: An input variable used to predict an outcome. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q3\",\n",
    "    \"prompt\": \"Which term matches this explanation? A plot showing paired numeric values.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Scatter plot\",\n",
    "      \"B\": \"Prediction\",\n",
    "      \"C\": \"Regression\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Scatter plot\"\n",
    "    ],\n",
    "    \"explanation\": \"Scatter plot: A plot showing paired numeric values. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q4\",\n",
    "    \"prompt\": \"Which term matches this explanation? A straight line used to predict an outcome from a feature.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Regression\",\n",
    "      \"B\": \"Prediction\",\n",
    "      \"C\": \"Regression line\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Regression line\"\n",
    "    ],\n",
    "    \"explanation\": \"Regression line: A straight line used to predict an outcome from a feature. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q5\",\n",
    "    \"prompt\": \"Which term matches this explanation? The predicted outcome when the feature is zero.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Intercept\",\n",
    "      \"B\": \"Regression\",\n",
    "      \"C\": \"Prediction\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Intercept\"\n",
    "    ],\n",
    "    \"explanation\": \"Intercept: The predicted outcome when the feature is zero. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q6\",\n",
    "    \"prompt\": \"Which term matches this explanation? The observed value minus the fitted value.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Residual\",\n",
    "      \"B\": \"Regression\",\n",
    "      \"C\": \"Prediction\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Residual\"\n",
    "    ],\n",
    "    \"explanation\": \"Residual: The observed value minus the fitted value. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q7\",\n",
    "    \"prompt\": \"Which term matches this explanation? A plot of residuals used to check model patterns.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Regression\",\n",
    "      \"B\": \"Residual plot\",\n",
    "      \"C\": \"Prediction\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Residual plot\"\n",
    "    ],\n",
    "    \"explanation\": \"Residual plot: A plot of residuals used to check model patterns. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q8\",\n",
    "    \"prompt\": \"Which term matches this explanation? The assumption that a straight line captures the main trend.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Regression\",\n",
    "      \"B\": \"Prediction\",\n",
    "      \"C\": \"Linearity\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Linearity\"\n",
    "    ],\n",
    "    \"explanation\": \"Linearity: The assumption that a straight line captures the main trend. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q9\",\n",
    "    \"prompt\": \"Which term matches this explanation? An observation that strongly affects a fitted model.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Prediction\",\n",
    "      \"B\": \"Regression\",\n",
    "      \"C\": \"Influential point\",\n",
    "      \"D\": \"Feature\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Influential point\"\n",
    "    ],\n",
    "    \"explanation\": \"Influential point: An observation that strongly affects a fitted model. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  },\n",
    "  {\n",
    "    \"id\": \"q10\",\n",
    "    \"prompt\": \"Which term matches this explanation? Fitting noise instead of a durable pattern.\",\n",
    "    \"choices\": {\n",
    "      \"A\": \"Regression\",\n",
    "      \"B\": \"Feature\",\n",
    "      \"C\": \"Prediction\",\n",
    "      \"D\": \"Overfitting\"\n",
    "    },\n",
    "    \"format\": \"multiple_choice\",\n",
    "    \"cognitive_level\": \"recognition\",\n",
    "    \"concepts\": [\n",
    "      \"Overfitting\"\n",
    "    ],\n",
    "    \"explanation\": \"Overfitting: Fitting noise instead of a durable pattern. This is the glossary meaning used by this original question.\",\n",
    "    \"retry_hint\": \"Compare the related terms in the assigned chapter glossary; identify what the description refers to.\",\n",
    "    \"reading\": \"../1200-regression.html\"\n",
    "  }\n",
    "];\n",
    "  var legacyVersion = 'dsm-ch12-preview-before-2026-10-19';\n",
    "  var authoredQuestions = JSON.parse(JSON.stringify(questions));\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",
    "    container.textContent = '';\n",
    "    questions.forEach(function (question, index) {\n",
    "      var card = document.createElement('div');\n",
    "      card.className = 'ch12-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",
    "      (attemptState ? attemptState.orders[question.id] : Object.keys(question.choices)).forEach(function (letter, position) {\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",
    "        input.checked = !!(attemptState && attemptState.answers[question.id] === letter);\n",
    "        label.appendChild(input);\n",
    "        label.appendChild(text(String.fromCharCode(65 + position) + '. ' + question.choices[letter]));\n",
    "        card.appendChild(label);\n",
    "      });\n",
    "\n",
    "      var feedback = document.createElement('div');\n",
    "      feedback.className = 'ch12-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",
    "    // Press stores this marker with the submitted answers; it is not a scored question.\n",
    "    answers.__question_set = attemptState ? attemptState.version : questionSetVersion;\n",
    "    if (attemptState) answers.preview_attempt = attemptState.token;\n",
    "    return answers;\n",
    "  }\n",
    "\n",
    "  function loadFeedbackPolicy() {\n",
    "    // Use the existing server release setting; a network failure must not unlock answers.\n",
    "    return fetch('/api/v1/assignment-settings.php?assignment_id=' + encodeURIComponent(quizId), {\n",
    "      credentials: 'same-origin'\n",
    "    })\n",
    "      .then(function (response) { return response.ok ? response.json() : null; })\n",
    "      .then(function (payload) {\n",
    "        answersUnlocked = !!(payload && payload.ok && payload.answers_unlocked === true);\n",
    "      })\n",
    "      .catch(function () { answersUnlocked = false; });\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('.ch12-preview-feedback');\n",
    "      var item = feedback[question.id];\n",
    "      message.className = 'ch12-preview-feedback';\n",
    "      var status = (!item || !item.submitted) ? 'No answer submitted.' :\n",
    "        (item.correct ? 'Correct.' : 'Review and try again.');\n",
    "      var guidance = answersUnlocked ? question.explanation : question.retry_hint;\n",
    "      message.textContent = status + ' ' + guidance;\n",
    "      var reading = document.createElement('a');\n",
    "      reading.href = question.reading || \"../1200-regression.html\";\n",
    "      reading.appendChild(text('Review this concept'));\n",
    "      message.appendChild(text(' '));\n",
    "      message.appendChild(reading);\n",
    "      message.classList.add(item && item.correct ? 'correct' : 'incorrect');\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function initLegacyPreviewQuiz() {\n",
    "    var quiz = document.getElementById('ch12-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('ch12-preview-questions');\n",
    "    var result = document.getElementById('ch12-preview-result');\n",
    "    var submit = document.getElementById('ch12-preview-submit');\n",
    "    var actions = quiz.querySelector('.ch12-preview-actions');\n",
    "    var reset = document.getElementById('ch12-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",
    "          question_set_version: questionSetVersion,\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",
    "          return loadFeedbackPolicy().then(function () {\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",
    "            if (!answersUnlocked) result.textContent += ' Full explanations are available when answers are released.';\n",
    "          });\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('.ch12-preview-feedback').forEach(function (feedback) {\n",
    "        feedback.className = 'ch12-preview-feedback';\n",
    "        feedback.textContent = '';\n",
    "      });\n",
    "      result.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "\n",
    "  function initPreviewQuiz() {\n",
    "    // The legacy PHP host retains its existing fixed-order delivery until it migrates to Press.\n",
    "    if (!/(^|\\.)thinkpress\\.org$/.test(window.location.hostname) &&\n",
    "        !['localhost', '127.0.0.1'].includes(window.location.hostname)) return initLegacyPreviewQuiz();\n",
    "    var quiz = document.getElementById('ch12-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('ch12-preview-questions');\n",
    "    var result = document.getElementById('ch12-preview-result');\n",
    "    var submit = document.getElementById('ch12-preview-submit');\n",
    "    var actions = quiz.querySelector('.ch12-preview-actions');\n",
    "    var reset = document.getElementById('ch12-preview-reset');\n",
    "\n",
    "    // Press owns this quiz's draft and option order; the generic runner must not restore older inputs over it.\n",
    "    quiz.classList.add('press-preview-managed');\n",
    "    var next = document.createElement('button');\n",
    "    next.type = 'button';\n",
    "    next.textContent = 'Start a new attempt';\n",
    "    next.hidden = true;\n",
    "    actions.appendChild(next);\n",
    "\n",
    "    function lockQuestions(locked) {\n",
    "      quiz.querySelectorAll('input[type=radio]').forEach(function (input) { input.disabled = locked; });\n",
    "      reset.disabled = locked;\n",
    "    }\n",
    "    function showState(payload) {\n",
    "      questions = attemptState.questions;\n",
    "      renderQuestions(questionsContainer);\n",
    "      lockQuestions(payload.completed);\n",
    "      submit.disabled = payload.completed;\n",
    "      next.hidden = !payload.completed;\n",
    "      if (!payload.completed) result.textContent = '';\n",
    "      if (attemptState.preserved_draft && !payload.completed) {\n",
    "        result.textContent = 'Resumed your saved answers with their original option order.';\n",
    "      }\n",
    "      if (payload.result) {\n",
    "        return loadFeedbackPolicy().then(function () {\n",
    "          applyFeedback(quiz, payload.result.feedback || {});\n",
    "          result.textContent = 'Saved attempt. Score: ' + payload.result.score + ' / ' + payload.result.max_score +\n",
    "            '. Best score: ' + payload.result.best_score + ' / ' + payload.result.max_score + '.';\n",
    "          if (!answersUnlocked) result.textContent += ' Full explanations are available when answers are released.';\n",
    "        });\n",
    "      }\n",
    "      return Promise.resolve();\n",
    "    }\n",
    "    renderQuestions(questionsContainer);\n",
    "    lockQuestions(true);\n",
    "    loadSessionIdentity(identity, actions, submit, result).then(function () {\n",
    "      if (submit.disabled) return;\n",
    "      submit.disabled = true;\n",
    "      result.textContent = 'Resuming your Preview...';\n",
    "      return stateRequest('resume').then(showState).catch(function (error) {\n",
    "        submit.disabled = true;\n",
    "        result.textContent = 'Could not resume this Preview: ' + error.message + ' Reload to retry.';\n",
    "      });\n",
    "    });\n",
    "\n",
    "    quiz.addEventListener('change', function (event) {\n",
    "      if (!event.target || event.target.type !== 'radio' || !attemptState) return;\n",
    "      saveAnswers(quiz).catch(function (error) { result.textContent = 'Answers not saved: ' + error.message; });\n",
    "    });\n",
    "\n",
    "    submit.addEventListener('click', async function () {\n",
    "      submit.disabled = true;\n",
    "      lockQuestions(true);\n",
    "      result.textContent = 'Submitting...';\n",
    "      try {\n",
    "        await saveAnswers(quiz);\n",
    "        var response = await fetch('/api/v1/quiz-attempts.php', {\n",
    "          method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin',\n",
    "          body: JSON.stringify({ quiz_id: quizId, answers: collectAnswers(quiz),\n",
    "            question_set_version: attemptState.version,\n",
    "            student_identifier: identity.student_identifier || identity.canvas_user_id || '',\n",
    "            canvas_course_id: identity.canvas_course_id, canvas_assignment_id: identity.canvas_assignment_id,\n",
    "            canvas_user_id: identity.canvas_user_id })\n",
    "        });\n",
    "        var payload = await response.json();\n",
    "        if (!response.ok || !payload.ok) throw new Error(payload.message || payload.error || 'Submission failed.');\n",
    "        await loadFeedbackPolicy();\n",
    "        applyFeedback(quiz, payload.feedback || {});\n",
    "        result.textContent = 'Submission saved. Attempt ' + payload.attempt_count + '. Score: ' + payload.score +\n",
    "          ' / ' + payload.max_score + '. Best score: ' + payload.best_score + ' / ' + payload.max_score + '.';\n",
    "        if (!answersUnlocked) result.textContent += ' Full explanations are available when answers are released.';\n",
    "        next.hidden = false;\n",
    "      } catch (error) {\n",
    "        result.textContent = 'Could not save this attempt: ' + error.message;\n",
    "        submit.disabled = false;\n",
    "        lockQuestions(false);\n",
    "      }\n",
    "    });\n",
    "\n",
    "    reset.addEventListener('click', function () {\n",
    "      submit.disabled = true;\n",
    "      stateQueue = stateQueue.catch(function () {}).then(function () { return stateRequest('clear'); });\n",
    "      stateQueue.then(function (payload) { result.textContent = ''; return showState(payload); })\n",
    "        .catch(function (error) { result.textContent = error.message; submit.disabled = false; });\n",
    "    });\n",
    "    next.addEventListener('click', function () {\n",
    "      next.disabled = true;\n",
    "      stateRequest('next').then(function (payload) { result.textContent = ''; return showState(payload); })\n",
    "        .catch(function (error) { result.textContent = error.message; })\n",
    "        .finally(function () { next.disabled = false; });\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
}
