{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Homework\n",
    "\n",
    "- Use this assignment to practice the chapter's concepts and coding skills in runnable Python code.\n",
    "- Sign in from the account menu before you start working on the assignment.\n",
    "- Write and run your code directly in the coding cells.\n",
    "- Multiple submissions are allowed. The highest score is kept.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::::{div} assignment-question-card\n",
    "\n",
    "**1. Stating the Null Hypothesis**\n",
    "\n",
    "A store tests a new checkout page. The null hypothesis is that the new page increases the purchase rate.\n",
    "\n",
    "<div class=\"tf-options\" role=\"radiogroup\" aria-label=\"Question 1 answer\">\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q1\" value=\"true\"> True</label>\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q1\" value=\"false\"> False</label>\n",
    "</div>\n",
    "\n",
    "::::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# False\n",
    "# The null hypothesis is the chance model: the new page makes no difference to the purchase rate. \"The page increases the rate\" is the alternative.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::::{div} assignment-question-card\n",
    "\n",
    "**2. Reading a p-Value**\n",
    "\n",
    "A test of a new pricing policy gives a p-value of 0.03. This means there is a 3% chance that the null hypothesis is true.\n",
    "\n",
    "<div class=\"tf-options\" role=\"radiogroup\" aria-label=\"Question 2 answer\">\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q2\" value=\"true\"> True</label>\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q2\" value=\"false\"> False</label>\n",
    "</div>\n",
    "\n",
    "::::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# False\n",
    "# The p-value is the chance, computed assuming the null hypothesis is true, of a test statistic at least as extreme as the one observed. It is not the probability that the null hypothesis is true.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::::{div} assignment-question-card\n",
    "\n",
    "**3. Choosing a Test Statistic**\n",
    "\n",
    "An analyst checks whether a jury panel's mix of five ethnic groups matches the eligible population. Large values of the total variation distance between the panel and the population are evidence against the model of random selection.\n",
    "\n",
    "<div class=\"tf-options\" role=\"radiogroup\" aria-label=\"Question 3 answer\">\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q3\" value=\"true\"> True</label>\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q3\" value=\"false\"> False</label>\n",
    "</div>\n",
    "\n",
    "::::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# True\n",
    "# The TVD measures how far apart the two distributions are. Random selection tends to give small TVDs, so large values favor the alternative.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::::{div} assignment-question-card\n",
    "\n",
    "**4. Not Rejecting**\n",
    "\n",
    "A test of a new warehouse process gives a p-value of 0.30. This proves that the new process has no effect.\n",
    "\n",
    "<div class=\"tf-options\" role=\"radiogroup\" aria-label=\"Question 4 answer\">\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q4\" value=\"true\"> True</label>\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q4\" value=\"false\"> False</label>\n",
    "</div>\n",
    "\n",
    "::::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# False\n",
    "# A large p-value means the data are consistent with the null hypothesis, not that the null is proven. The effect may be small, or the sample may be too small to detect it.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "::::{div} assignment-question-card\n",
    "\n",
    "**5. Many Tests**\n",
    "\n",
    "A marketing team runs 20 independent tests in which the null hypothesis is actually true, using a 5% cutoff. They should expect about one of them to come out statistically significant.\n",
    "\n",
    "<div class=\"tf-options\" role=\"radiogroup\" aria-label=\"Question 5 answer\">\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q5\" value=\"true\"> True</label>\n",
    "  <label><input type=\"radio\" name=\"ch09-hw-q5\" value=\"false\"> False</label>\n",
    "</div>\n",
    "\n",
    "::::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# True\n",
    "# With a 5% cutoff, a true null hypothesis is rejected about 5% of the time, and 5% of 20 tests is 1. That significant result is a Type I error (a false alarm).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### 6. Defect Rate Statistic\n",
    "# A supplier claims that 5% of its parts are defective. In a shipment of 600 parts,\n",
    "# 42 were defective.\n",
    "#   1. Compute the observed percent defective, rounded to 2 decimals.\n",
    "#   2. Compute the test statistic | observed percent - 5 |, rounded to 2 decimals.\n",
    "# Print exactly:\n",
    "# Observed percent: 7.0\n",
    "# Test statistic: 2.0\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "defective = 42\n",
    "shipment = 600\n",
    "claimed_percent = 5\n",
    "\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": [
     "hide-input",
     "homework-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "Observed percent: 7.0\nTest statistic: 2.0\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "defective = 42\n",
    "shipment = 600\n",
    "claimed_percent = 5\n",
    "\n",
    "observed_percent = round(100 * defective / shipment, 2)\n",
    "print(\"Observed percent:\", observed_percent)\n",
    "print(\"Test statistic:\", round(abs(observed_percent - claimed_percent), 2))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### 7. Channel Mix TVD\n",
    "# Compare this month's sales channel mix with last year's mix.\n",
    "#   1. Compute the total variation distance between the two distributions,\n",
    "#      rounded to 2 decimals.\n",
    "# Print exactly:\n",
    "# TVD: 0.08\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "mix = pd.DataFrame({\n",
    "    \"channel\": [\"Web\", \"App\", \"Store\", \"Phone\"],\n",
    "    \"last_year\": [0.40, 0.25, 0.30, 0.05],\n",
    "    \"this_month\": [0.42, 0.31, 0.22, 0.05],\n",
    "})\n",
    "\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": [
     "hide-input",
     "homework-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "TVD: 0.08\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "mix = pd.DataFrame({\n",
    "    \"channel\": [\"Web\", \"App\", \"Store\", \"Phone\"],\n",
    "    \"last_year\": [0.40, 0.25, 0.30, 0.05],\n",
    "    \"this_month\": [0.42, 0.31, 0.22, 0.05],\n",
    "})\n",
    "\n",
    "tvd = round((mix['this_month'] - mix['last_year']).abs().sum() / 2, 2)\n",
    "print(\"TVD:\", tvd)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### 8. P-value for the Defect Rate\n",
    "# Under the supplier's 5% claim, an analyst simulated 25 shipments of 600 parts and\n",
    "# recorded the defective counts below. The setup converts those counts to\n",
    "# | simulated percent - 5 | without rounding before the comparison.\n",
    "# These 25 repetitions are for practice; use many more for a stable estimate.\n",
    "#   1. Compute the p-value: the proportion of simulated statistics at least as\n",
    "#      large as the observed statistic of 2.0.\n",
    "# Print exactly:\n",
    "# P-value: 0.08\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "observed_statistic = 2.0\n",
    "# Counts generated by np.random.default_rng(6).binomial(600, 0.05, size=25).\n",
    "simulated_defects = pd.Series([30, 28, 28, 28, 43, 32, 32, 28, 32, 24, 22, 36, 18,\n",
    "                                 41, 35, 34, 21, 26, 36, 29, 32, 24, 25, 30, 34])\n",
    "simulated = (100 * simulated_defects / 600 - 5).abs()\n",
    "\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": [
     "hide-input",
     "homework-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "P-value: 0.08\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "observed_statistic = 2.0\n",
    "# Counts generated by np.random.default_rng(6).binomial(600, 0.05, size=25).\n",
    "simulated_defects = pd.Series([30, 28, 28, 28, 43, 32, 32, 28, 32, 24, 22, 36, 18,\n",
    "                                 41, 35, 34, 21, 26, 36, 29, 32, 24, 25, 30, 34])\n",
    "simulated = (100 * simulated_defects / 600 - 5).abs()\n",
    "\n",
    "p_value = (simulated >= observed_statistic).mean()\n",
    "print(\"P-value:\", p_value)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### 9. The Cutoff Matters\n",
    "# The defect-rate test gave a p-value of 0.08.\n",
    "#   1. Decide at a 5% cutoff: print \"reject\" if the p-value is below the cutoff,\n",
    "#      otherwise \"do not reject\".\n",
    "#   2. Decide again at a 10% cutoff.\n",
    "# These are two policies chosen in advance for comparison. Do not switch to\n",
    "# 10% after seeing p = 0.08 merely to obtain a rejection. In a comment, explain\n",
    "# why failing to reject at 5% does not prove that the supplier meets its claim.\n",
    "# Print exactly:\n",
    "# At 0.05: do not reject\n",
    "# At 0.1: reject\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "p_value = 0.08\n",
    "cutoffs = [0.05, 0.1]\n",
    "\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "tags": [
     "hide-input",
     "homework-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "At 0.05: do not reject\nAt 0.1: reject\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "p_value = 0.08\n",
    "cutoffs = [0.05, 0.1]\n",
    "\n",
    "for cutoff in cutoffs:\n",
    "    if p_value < cutoff:\n",
    "        decision = \"reject\"\n",
    "    else:\n",
    "        decision = \"do not reject\"\n",
    "    print(\"At \" + str(cutoff) + \":\", decision)\n",
    "# Report the p-value along with the decision, so readers can judge for themselves.\n",
    "\n",
    "# At 5%, the evidence is insufficient to reject the model. That does not\n",
    "# establish that the claimed rate is correct or that the shipment is acceptable.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### 10. The Cutoff as a Statistic\n",
    "# As in the chapter's percentile example, estimate an upper-tail boundary\n",
    "# with the 95th percentile. For a small or discrete simulation, interpolation\n",
    "# and ties mean it may not match a p-value decision exactly.\n",
    "#   1. Compute the 95th percentile with np.percentile(simulated, 95).\n",
    "#      Keep the unrounded value for comparisons; round to 3 decimals for display.\n",
    "#   2. Decide whether the observed TVD of 0.071 is above the unrounded value.\n",
    "# Print exactly:\n",
    "# 95th percentile: 0.068\n",
    "# Observed is above it: True\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "observed_tvd = 0.071\n",
    "simulated = pd.Series([0.037, 0.028, 0.044, 0.051, 0.065, 0.050, 0.062, 0.010, 0.031, 0.025,\n",
    "                       0.054, 0.061, 0.013, 0.046, 0.068, 0.073, 0.022, 0.064, 0.058, 0.039])\n",
    "\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": [
     "hide-input",
     "homework-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "95th percentile: 0.068\nObserved is above it: True\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "observed_tvd = 0.071\n",
    "simulated = pd.Series([0.037, 0.028, 0.044, 0.051, 0.065, 0.050, 0.062, 0.010, 0.031, 0.025,\n",
    "                       0.054, 0.061, 0.013, 0.046, 0.068, 0.073, 0.022, 0.064, 0.058, 0.039])\n",
    "\n",
    "cutoff_value = np.percentile(simulated, 95)\n",
    "print(\"95th percentile:\", round(cutoff_value, 3))\n",
    "print(\"Observed is above it:\", observed_tvd > cutoff_value)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<style>\n",
    ".ch09-hw-grader { max-width: 900px; margin-top: 1rem; }\n",
    ".ch09-hw-feedback-list { display: grid; gap: 0.45rem; margin: 0 0 1rem; }\n",
    ".ch09-hw-feedback-list[hidden] { display: none; }\n",
    ".ch09-hw-feedback-row { border: 1px solid #d8dee4; border-radius: 6px; padding: 0.55rem 0.65rem; }\n",
    ".ch09-hw-feedback-row strong { margin-right: 0.35rem; }\n",
    ".ch09-hw-feedback-row.correct { border-color: #aceebb; background: #dafbe1; color: #116329; }\n",
    ".ch09-hw-feedback-row.incorrect { border-color: #ffcecb; background: #ffebe9; color: #cf222e; }\n",
    ".ch09-hw-actions { display: flex; gap: 0.75rem; align-items: center; margin: 1rem 0 0; flex-wrap: wrap; }\n",
    ".ch09-hw-actions button { border: 1px solid #176de8; border-radius: 6px; background: #176de8; color: white; padding: 0.55rem 0.9rem; cursor: pointer; font-weight: 600; }\n",
    ".ch09-hw-actions button.secondary { background: white; color: #176de8; }\n",
    ".ch09-hw-actions.is-locked { opacity: 0.55; }\n",
    ".ch09-hw-actions button:disabled { cursor: not-allowed; opacity: 0.65; }\n",
    ".ch09-hw-result { font-weight: 700; }\n",
    "</style>\n",
    "\n",
    "<div class='ch09-hw-grader' id='ch09-hw-grader'>\n",
    "  <div class='ch09-hw-feedback-list' aria-live='polite' hidden>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q1'><strong>Question 1</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q2'><strong>Question 2</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q3'><strong>Question 3</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q4'><strong>Question 4</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q5'><strong>Question 5</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q6'><strong>Question 6</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q7'><strong>Question 7</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q8'><strong>Question 8</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q9'><strong>Question 9</strong><span></span></div>\n",
    "    <div class='ch09-hw-feedback-row' data-question='q10'><strong>Question 10</strong><span></span></div>\n",
    "  </div>\n",
    "  <div class='ch09-hw-actions'>\n",
    "    <button type='button' id='ch09-hw-submit' disabled>Submit</button>\n",
    "    <button type='button' class='secondary' id='ch09-hw-reset'>Clear Results</button>\n",
    "    <span class='ch09-hw-result' id='ch09-hw-result' aria-live='polite'></span>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(function () {\n",
    "  var homeworkId = 'ch09-homework';\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",
    "    };\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 || '';\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 codeFromCell(cell) {\n",
    "    var cm = cell.querySelector('.CodeMirror');\n",
    "    if (cm && cm.CodeMirror && typeof cm.CodeMirror.getValue === 'function') return cm.CodeMirror.getValue();\n",
    "    var cmContent = cell.querySelector('.cm-content');\n",
    "    if (cmContent && cmContent.cmView && cmContent.cmView.view) return cmContent.cmView.view.state.doc.toString();\n",
    "    var textarea = cell.querySelector('textarea');\n",
    "    if (textarea) return textarea.value;\n",
    "    var pre = cell.querySelector('.cell_input pre');\n",
    "    return pre ? pre.textContent : '';\n",
    "  }\n",
    "\n",
    "  function collectCodeCells() {\n",
    "    var cells = Array.prototype.slice.call(document.querySelectorAll('div.cell.tag_thebe-interactive')).slice(0, 5);\n",
    "    var code = {};\n",
    "    cells.forEach(function (cell, index) {\n",
    "      code['q' + (index + 6)] = codeFromCell(cell);\n",
    "    });\n",
    "    return code;\n",
    "  }\n",
    "\n",
    "  function collectAnswers() {\n",
    "    var answers = {};\n",
    "    ['q1', 'q2', 'q3', 'q4', 'q5'].forEach(function (question) {\n",
    "      var checked = document.querySelector('input[name=\"ch09-hw-' + question + '\"]:checked');\n",
    "      answers[question] = checked ? checked.value : '';\n",
    "    });\n",
    "    return answers;\n",
    "  }\n",
    "\n",
    "  function applyFeedback(grader, feedback) {\n",
    "    var list = grader.querySelector('.ch09-hw-feedback-list');\n",
    "    list.hidden = false;\n",
    "    ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10'].forEach(function (question) {\n",
    "      var row = grader.querySelector('[data-question=' + question + ']');\n",
    "      var message = row.querySelector('span');\n",
    "      var item = feedback[question];\n",
    "      row.className = 'ch09-hw-feedback-row';\n",
    "      if (!item) return;\n",
    "      message.textContent = item.message + ' (' + item.score + ' / ' + item.max_score + ')';\n",
    "      row.classList.add(item.correct ? 'correct' : 'incorrect');\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function clearFeedback(grader) {\n",
    "    var list = grader.querySelector('.ch09-hw-feedback-list');\n",
    "    list.hidden = true;\n",
    "    grader.querySelectorAll('.ch09-hw-feedback-row').forEach(function (row) {\n",
    "      row.className = 'ch09-hw-feedback-row';\n",
    "      var message = row.querySelector('span');\n",
    "      if (message) message.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function saveAttempt(payload) {\n",
    "    return fetch('/api/v1/homework-attempts.php', {\n",
    "      method: 'POST',\n",
    "      headers: { 'Content-Type': 'application/json' },\n",
    "      credentials: 'same-origin',\n",
    "      body: JSON.stringify(payload)\n",
    "    }).then(function (response) {\n",
    "      return response.text().then(function (text) {\n",
    "        var payload = text ? JSON.parse(text) : null;\n",
    "        if (!payload || !response.ok || !payload.ok) {\n",
    "          throw new Error((payload && (payload.message || payload.error)) || 'Submission API is unavailable.');\n",
    "        }\n",
    "        return payload;\n",
    "      });\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function initHomeworkGrader() {\n",
    "    var grader = document.getElementById('ch09-hw-grader');\n",
    "    if (!grader || grader.dataset.ready === 'true') return;\n",
    "    grader.dataset.ready = 'true';\n",
    "\n",
    "    var identity = getParams();\n",
    "    var actions = grader.querySelector('.ch09-hw-actions');\n",
    "    var result = document.getElementById('ch09-hw-result');\n",
    "    var submit = document.getElementById('ch09-hw-submit');\n",
    "    var reset = document.getElementById('ch09-hw-reset');\n",
    "\n",
    "    loadSessionIdentity(identity, actions, submit, result);\n",
    "\n",
    "    submit.addEventListener('click', function () {\n",
    "      clearFeedback(grader);\n",
    "      submit.disabled = true;\n",
    "      result.textContent = 'Submitting homework.';\n",
    "\n",
    "      saveAttempt({\n",
    "        homework_id: homeworkId,\n",
    "        answers: collectAnswers(),\n",
    "        code: collectCodeCells(),\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",
    "        .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(grader, 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",
    "      clearFeedback(grader);\n",
    "      result.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "  if (document.readyState === 'loading') {\n",
    "    document.addEventListener('DOMContentLoaded', initHomeworkGrader);\n",
    "  } else {\n",
    "    initHomeworkGrader();\n",
    "  }\n",
    "})();\n",
    "</script>\n"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
