#!/usr/bin/env python3
"""Assessment Design Playbook generator.

Renders a styled HTML (and optional PDF) playbook for the five assessment
categories, reusing the assurance report's CSS. One file, one data dict, no new
dependencies.

CLI:
    assessment_design_playbook.py --output PATH.html [--pdf PATH.pdf]
"""
from __future__ import annotations

import argparse
import html
import sys
from pathlib import Path


def _e(value: object) -> str:
    if value is None:
        return ""
    return html.escape(str(value))


# ---------------------------------------------------------------------------
# Content: five assessment categories
# ---------------------------------------------------------------------------

# Keep roughly in sync with standards/assessment-assurance-design.md (the generation-facing distillation of these categories).
_CATEGORIES = [
    {
        "title": "Discussions",
        "vulnerable": (
            "Async text posts are the easiest target for current AI: reusable prompts that "
            "result in generic responses, no authorship signal, and grade weight tied to word "
            "count rather than demonstrated thinking. A student who never engaged with the "
            "material can generate a plausible-looking post in seconds."
        ),
        "resistant": [
            "Bind prompts to specific named evidence from the course (a case, a reading passage, a peer's argument).",
            "Require cohort-specific references so a response that skips the named material will not meet the assignment criteria.",
            "Gate posting so students submit before they can see peers, which removes the ability to summarize others' ideas.",
            "Grade integration and specificity, not length; a short response with named evidence outweighs a 500-word wall.",
        ],
        "forward": [
            "Ask students to critique an AI-generated response to the prompt, naming what it missed or distorted.",
            "Run a verify-and-disclose cycle: students draft with AI, mark what they accepted, rejected, or corrected, and submit the annotated log alongside the final post.",
            "Position AI as a sparring partner: students state a position, ask AI to argue the opposite, then defend their view against the best counter.",
        ],
        "tech": [
            (
                "EdDiscussions",
                "gated posting (students submit before seeing peers) and prompts bound to "
                "specific cohort content; grade integration and specificity, not length.",
            ),
            (
                "VoiceThread",
                "voice or video posts where identity is the anchor; a scripted AI voiceover "
                "is exposed in a short live or threaded follow-up.",
            ),
            (
                "Harmonize",
                "multi-stage peer-review discussions with rubric-based review. Note: "
                "Harmonize includes built-in AI coaching; disable it in a no-AI course or use "
                "it to move that course to AI-forward.",
            ),
            (
                "Yellowdig",
                "community participation points. Engagement signal only; it does not verify "
                "authorship, so do not treat it as a securing anchor.",
            ),
        ],
        "lanes": {
            "lane1": [
                "Bind every prompt to cohort-specific evidence (a case, a peer's post) so a generic answer fails.",
                "Gate posting (submit before seeing peers) and grade specificity, not length.",
                "A generic discussion cannot carry secured weight; keep it low unless anchored.",
            ],
            "lane2": [
                "Let students use AI openly to draft, then require a short disclosure of what they kept or changed.",
                "Keep it low-stakes and formative; its value is practice, not certification.",
            ],
            "tilt": [
                "Post the rubric and two annotated model posts up front.",
                "State what AI use is allowed for this discussion.",
            ],
        },
        "sample_artifact_html": """
<h3 class="category-title">Discussions: Model Artifacts</h3>
<p class="body-text" style="margin-bottom:10px;">Share these with students up front as the models (the Criteria component of TILT). Both come from a weekly discussion that asks students to apply a chapter concept to the assigned case.</p>
<p class="subsection-heading">AI-Forward Model: Critique an AI Post</p>
<p class="body-text" style="margin-bottom:6px;">Use this when AI is permitted and you grade the student's judgment about it.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:44%;">What the AI post claimed</th><th>The student's correction</th></tr></thead>
  <tbody>
    <tr><td>"Groupthink is when a group agrees too much and makes a bad decision."</td><td>Janis defines groupthink as the suppression of dissent for cohesion, which is narrower than simple agreement.</td></tr>
    <tr><td>"The Challenger disaster is an example."</td><td>The post cites no specific pressure. In our case the engineers did raise concerns, so the failure was management overriding dissent, not the absence of disagreement.</td></tr>
    <tr><td>"Teams should talk openly to avoid it."</td><td>Generic. A stronger fix uses Janis's self-censorship symptom and a named devil's advocate, and quotes the meeting notes on page 3.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student names each concept error, corrects it from the assigned definition, and grounds the correction in the specific case.</p>
<p class="subsection-heading" style="margin-top:14px;">AI-Resistant Model: A Cohort-Anchored Post</p>
<p class="body-text" style="margin-bottom:6px;">Use this when the securing move is specificity a reusable prompt cannot predict.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:52%;">Passage from the student's post</th><th>The move it makes</th></tr></thead>
  <tbody>
    <tr><td>"In the Week 4 case the product team shows Janis's illusion of unanimity. The meeting notes record no objection, yet two engineers later disagreed (case, p. 3)."</td><td>Cites the specific case and page.</td></tr>
    <tr><td>"Building on Maria's point about the deadline, the launch date created the incentive to self-censor."</td><td>Answers a named peer in the thread.</td></tr>
    <tr><td>"The fix is a named devil's advocate, which our reading ties to lower groupthink."</td><td>Applies a precise concept from the reading.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the post pins evidence a generic answer could not predict, engages the cohort, and applies the assigned concept.</p>
""",
    },
    {
        "title": "Quizzes and Exams",
        "vulnerable": (
            "An unproctored, untimed quiz with a static item bank is an "
            "open-book-with-AI test. Recall-heavy items such as definitions, rule statements, "
            "and basic identification are what current AI handles best. Leaky "
            "item banks (screenshots, contract-cheating services) compound the risk."
        ),
        "resistant": [
            "Time-box and randomize: draw from a pool large enough that each student sees a different subset, and let time pressure rule out leisurely AI consultation.",
            "Shift from recall to application: items that require applying a rule to a novel scenario, identifying an error, or choosing among competing interpretations demand more than a lookup.",
            "Auto-vary the details: the same question with different numbers, names, or dates for each student, so a shared answer does not transfer.",
            "Reserve proctoring for the assessments where it is worth the equity cost, meaning high-weight exams rather than low-stakes checks.",
        ],
        "forward": [
            "Open-AI verification: give students an AI-generated answer or analysis; they find and correct its errors and explain what it missed, graded on the correction and reasoning, not recall.",
            "Two-stage exams: an individual sitting followed immediately by a small-group re-do of flagged items, where the discussion reveals reasoning.",
        ],
        "tech": [
            (
                "Canvas New Quizzes",
                "item banks with randomization or parameterization and time limits; the "
                "securing lever is a large randomized application pool, not the timer.",
            ),
            (
                "iClicker or Top Hat",
                "synchronous comprehension checks. Formative and sync-only; not a securing "
                "control for async courses.",
            ),
            (
                "Proctoring (Last Resort)",
                "Honorlock or Respondus LockDown Browser for high-stakes exams only. Carries "
                "equity and surveillance burdens (device requirements, cost) that must be "
                "weighed against the assurance gain, and is not AI-proof.",
            ),
        ],
        "lanes": {
            "lane1": [
                "Secure with a large randomized or auto-varied application pool; recall items alone do not hold.",
                "Reserve proctoring (Honorlock/Respondus) for the highest-stakes exam only, named last.",
            ],
            "lane2": [
                "Use frequent low-stakes checks as open retrieval practice; AI lookup matters little at low weight.",
                "Allow AI as a study partner and grade the exam, not the practice.",
            ],
            "tilt": [
                "Give a worked model of a strong open-AI correction before any verify-the-AI item.",
                "State the AI policy for each quiz explicitly.",
            ],
        },
        "sample_artifact_html": """
<h3 class="category-title">Quizzes and Exams: Model Artifacts</h3>
<p class="body-text" style="margin-bottom:10px;">Share these with students up front as the models (the Criteria component of TILT). Both use a first-year contracts topic.</p>
<p class="subsection-heading">AI-Forward Model: Open-AI Verification</p>
<p class="body-text" style="margin-bottom:6px;">Use this when AI is permitted and you grade the correction, not recall.</p>
<p class="body-text" style="margin-bottom:4px;font-size:12.5px;"><b style="color:var(--accent);">Question:</b> An offer says "open for 10 days." On day 3 the offeror sells the item to someone else and says nothing. On day 5 the offeree accepts. Is there a contract?</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:40%;">The AI's answer</th><th>The student's correction</th></tr></thead>
  <tbody>
    <tr><td>"Yes. The offer said 10 days, so it stayed open, and the day-5 acceptance formed a contract."</td><td>A promise to hold an offer open is not binding without consideration, so the 10 days created no option. Selling to a third party is an indirect revocation once the offeree learns of it (Dickinson v. Dodds). If the offeree knew before accepting, no contract formed. The AI applied the wrong rule.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student applies the correct doctrine to the facts, names the authority, and states exactly what the AI missed.</p>
<p class="subsection-heading" style="margin-top:14px;">AI-Resistant Model: Recall Item Rebuilt as Application</p>
<p class="body-text" style="margin-bottom:6px;">Use this when the securing move is application items drawn from a randomized pool.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:38%;">Weak recall item (AI-easy)</th><th>Rebuilt application item</th></tr></thead>
  <tbody>
    <tr><td>"Define consideration in contract law."</td><td>"Jordan promises to paint Alex's fence as a favor, for free. Relying on the promise, Alex buys 200 dollars of paint. Jordan backs out. State whether consideration exists and which doctrine, if any, lets Alex recover."</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student applies the rule to a novel fact pattern and chooses among doctrines, which a lookup does not finish. Draw each student's version from a pool that varies the parties, amounts, and promised act, so no two share an item.</p>
""",
    },
    {
        "title": "Papers and Written Assignments",
        "vulnerable": (
            "Papers are the canonical target for AI substitution. A final, upload-only model "
            "with a reusable prompt presents the easiest avenue for students to substitute "
            "critical thinking with AI-generated content. AI-detection tools do not reliably "
            "identify AI-generated prose and carry known equity risks; they are not a "
            "substitute for structural design."
        ),
        "resistant": [
            "Situate the prompt: tie it to specific course material, a dataset provided in class, or a named case discussed only internally.",
            "Scaffold the process and collect the artifacts: proposal, annotated draft, and revision with tracked changes or version history in Google Docs or Word. Grade the process, not just the product.",
            "For high-weight papers where the process record is not enough, a short recorded walkthrough or oral summary on a sampled subset adds a check. Keep it sampled and brief, and reserve it as a last-resort addition rather than a default.",
            "Require personal situatedness: connect the argument to the student's professional context, organization, or a live case they are working on.",
        ],
        "forward": [
            "AI-use appendix: students document the prompts they used, flag which suggestions they accepted versus rejected, and justify each decision. The judgment log is graded alongside the paper.",
            "Critique-and-improve: students receive an AI-produced draft, identify its weaknesses, fill its gaps, and produce an improved version with a written account of what changed and why.",
            "Source generation and verification: students use an LLM to draft a bibliography, then independently verify every source, report the hallucinated or misattributed ones, and keep only what holds, graded on the verification judgment.",
            "Reflective memo: after submitting, students describe where AI helped, where it led them wrong, and what they would do differently.",
        ],
        "tech": [
            (
                "Harmonize",
                "multi-stage draft, rubric peer review, and revision in one assignment; the "
                "staged submissions are the authorship and process trail. (AI-coaching caveat "
                "as above.)",
            ),
            (
                "VoiceThread",
                "a short recorded explanation or defense of the paper tied to its specific "
                "source, a recorded authorship signal that passes back to Canvas.",
            ),
            (
                "Canvas Assignments",
                "native submission with rubric and SpeedGrader feedback; pair with a required "
                "outline or draft milestone to capture process.",
            ),
        ],
        "lanes": {
            "lane1": [
                "Secure through documented process: staged drafts, version history, a short defense; grade the process, not just the product.",
                "Situate the prompt in internal course material so a reusable prompt cannot answer it.",
            ],
            "lane2": [
                "Permit AI for brainstorming or drafting when the graded artifact is the disclosed judgment (an AI-use appendix).",
                "Keep unsecured drafts low-weight relative to the process-anchored final.",
            ],
            "tilt": [
                "Share the rubric plus an annotated model of a strong verification or revision log up front.",
                "State per assignment which AI uses are allowed and must be disclosed.",
            ],
        },
        "sample_artifact_html": """
<h3 class="category-title">Papers: Model Artifacts</h3>
<p class="body-text" style="margin-bottom:10px;">Share these with students up front as the models (the Criteria component of TILT), so a student who has never produced one can aim at the target. Both come from the same assignment: a short persuasion-analysis paper on an advertisement the student chose.</p>

<p class="subsection-heading">AI-Forward Model: AI-Use Appendix</p>
<p class="body-text" style="margin-bottom:6px;">Use this when AI is permitted and you grade the student's judgment about it.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:28%;">Prompt the student used</th><th>What the AI suggested</th><th style="width:15%;">Decision</th><th style="width:26%;">Why</th></tr></thead>
  <tbody>
    <tr><td>"List Cialdini's six principles of persuasion with a one-line definition."</td><td>An accurate list of the six principles.</td><td>Kept; rewrote in own words</td><td>Correct but generic. I needed my own framing to apply it to my ad.</td></tr>
    <tr><td>"Analyze this Nike ad using the scarcity principle."</td><td>Claimed the ad used scarcity (a "limited-edition drop").</td><td>Rejected</td><td>My ad shows no scarcity cue. The AI invented one. I analyzed social proof instead, which the athlete endorsements actually show.</td></tr>
    <tr><td>"Draft a thesis about emotional appeal in advertising."</td><td>A broad thesis about ads and emotion.</td><td>Changed</td><td>Too general. I narrowed it to my ad and the reciprocity principle it relies on.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:4px;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student names each prompt, rejects a fabricated claim with a reason from the reading, and ties every decision to the specific ad. That judgment is what the appendix grades.</p>

<p class="subsection-heading" style="margin-top:14px;">AI-Resistant Model: Process Trail</p>
<p class="body-text" style="margin-bottom:6px;">Use this when the securing move is documented authorship and the task needs no AI.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:24%;">Stage</th><th>What the student submitted</th></tr></thead>
  <tbody>
    <tr><td>Proposal (Week 1)</td><td>"I will analyze the reciprocity principle in a Dove Real Beauty ad from my own Instagram feed."</td></tr>
    <tr><td>Annotated draft (Week 3)</td><td>Draft carrying the instructor note "tie the reciprocity claim to a specific frame," with the student's two marked changes in response.</td></tr>
    <tr><td>Final and revision note (Week 5)</td><td>"I changed my thesis after peer feedback, from a general emotional appeal to reciprocity, because a peer showed the ad offers self-worth before it asks for brand loyalty."</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the dated development record, the self-chosen ad, and the documented thesis change are authorship evidence a generic AI submission cannot produce.</p>
""",
    },
    {
        "title": "Projects",
        "vulnerable": (
            "Deliverable-only grading on a project with a reusable scope is nearly as easy to substitute "
            "as a paper. Group projects add a layer of complexity but can reduce individual "
            "accountability: one contributor's AI-generated section is invisible when the group submits a "
            "single document."
        ),
        "resistant": [
            "Design a unique or local scope: use a dataset, client, organization, or problem set that belongs to this cohort and this term.",
            "Gate with milestone check-ins: an early scope proposal, a mid-project status update, and a final deliverable create a verifiable record of development over time.",
            "Require a process log: a decision journal, commit history (for code projects), or a build log where each entry is dated and describes what changed and why.",
            "Add individual accountability within group projects: contribution statements, individual oral reports on a specific piece, or a short reflection on what each contributor produced.",
        ],
        "forward": [
            "Document AI use as a project tool: individual students or teams keep a log of where AI assisted, what prompts produced, and what their judgment overrode.",
            "Grade the judgment, not just the output: the criteria emphasize the decisions the student or team made, the tradeoffs they navigated, and what they would do differently.",
            "Reflect on AI limits: part of the final deliverable identifies where AI tools fell short and how the student or team compensated.",
        ],
        "tech": [
            (
                "Harmonize",
                "milestone check-ins, peer review, and multimedia submission that build a "
                "verifiable development record.",
            ),
            (
                "VoiceThread",
                "a recorded walkthrough where each contributor explains their own "
                "contribution, adding individual accountability.",
            ),
            (
                "Canvas Assignments",
                "deliverable submission with a rubric; require a dated process log or, for "
                "code, commit history alongside.",
            ),
        ],
        "lanes": {
            "lane1": [
                "Secure with milestone check-ins and a dated process or decision log (individual or team) that create a development record.",
                "Use a unique or local scope (a real dataset, client, or problem) a generic solution cannot fit.",
            ],
            "lane2": [
                "Let individual students or teams use AI as a tool and grade the judgment that overrode it, logged in the deliverable.",
                "Keep exploratory or practice components open and low-stakes.",
            ],
            "tilt": [
                "Provide models of a strong AI-use log and a clear rubric before the project starts.",
                "State the AI policy and the individual-accountability expectation up front.",
            ],
        },
        "sample_artifact_html": """
<h3 class="category-title">Projects: Model Artifacts</h3>
<p class="body-text" style="margin-bottom:10px;">Share these with students up front as the models (the Criteria component of TILT). Both come from a team research project.</p>
<p class="subsection-heading">AI-Forward Model: AI-Use Log</p>
<p class="body-text" style="margin-bottom:6px;">Use this when AI is permitted and you grade the judgment the team applied to it.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:11%;">When</th><th>What AI produced</th><th style="width:15%;">Decision</th><th style="width:27%;">Judgment applied</th></tr></thead>
  <tbody>
    <tr><td>Week 3</td><td>Five draft survey questions, two of them leading ("Do you agree the app is easy to use?").</td><td>Rejected the two, kept two after rewording</td><td>Leading questions bias responses, from our methods reading.</td></tr>
    <tr><td>Week 4</td><td>A suggested chart type for our results.</td><td>Changed</td><td>The AI chose a pie chart for change over time; we used a line chart, which fits time-series data.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the log names what AI produced, what the team rejected or changed, and the course concept behind each call.</p>
<p class="subsection-heading" style="margin-top:14px;">AI-Resistant Model: Milestone and Contribution Record</p>
<p class="body-text" style="margin-bottom:6px;">Use this when the securing move is a verifiable development trail with individual accountability.</p>
<table class="considerations-table" style="margin-bottom:6px;">
  <thead><tr><th style="width:24%;">Milestone</th><th style="width:12%;">Week</th><th>Evidence submitted</th></tr></thead>
  <tbody>
    <tr><td>Scope proposal</td><td>Week 2</td><td>One-page problem statement and data plan.</td></tr>
    <tr><td>Client check-in</td><td>Week 5</td><td>Midpoint memo with the client's feedback noted.</td></tr>
    <tr><td>Final delivery</td><td>Week 8</td><td>Report plus commit history from October 3 to 20.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:4px;font-size:12.5px;"><b style="color:var(--accent);">Contribution statement:</b> "I built the data pipeline and wrote the limitations section; my teammate ran the interviews and coded the transcripts."</p>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> dated milestones and per-member contributions tied to commit history create a record one AI submission cannot fake.</p>
""",
    },
    {
        "title": "Presentations and Oral Assessments",
        "vulnerable": (
            "A live oral exchange binds identity to real-time reasoning, which is a strong "
            "assurance signal, but it is a high-cost one: it takes faculty time, presents access "
            "barriers for some students, and works against the flexibility online students are "
            "promised, so it belongs to the highest-stakes work rather than every module. A "
            "pre-recorded presentation with scripted Q&A can be prepared with AI just as a paper "
            "can, and live video is not fully AI-proof, so the signal holds only when the exchange "
            "is unscripted."
        ),
        "resistant": [
            "Ground the work in a unique context: tie the task to the student's own work, organization, or a personal setting a generic response cannot fit.",
            "Require process-oriented work: submit in pieces (outline, draft, revision) with a short decision log.",
            "Incorporate targeted reflection that points to a specific change, for example: 'Identify one recommendation you changed after receiving peer feedback. Explain why you changed it and cite the feedback that influenced your decision.'",
            "As a last resort for the highest-stakes work, make it a live synchronous check with unscripted follow-up questions where the student defends specific choices on the spot.",
        ],
        "forward": [
            "Present with AI's help, then critique it: students prepare with AI assistance and open by describing where AI contributed and where they overrode it.",
            "Disclose prep use: a brief account of which AI tools the student used in preparation and what they changed, graded as part of the presentation.",
        ],
        "tech": [
            (
                "VoiceThread",
                "asynchronous oral or narrated-slide response, a strong identity signal; not "
                "fully AI-proof (deepfakes, AI voiceovers), so pair high-weight items with a "
                "short unscripted follow-up.",
            ),
            (
                "WeVideo (PlayPosit)",
                "embedded prompts inside the recorded presentation for comprehension and "
                "engagement evidence.",
            ),
            (
                "Live Sync (Last Resort)",
                "a live synchronous Q&A for the highest-weight presentations only, given its "
                "scheduling cost.",
            ),
        ],
        "lanes": {
            "lane1": [
                "Secure through process evidence (staged pieces, a decision log) plus, for the highest stakes only, a live unscripted check.",
                "Ground the work in the student's own context so a generic script fails.",
            ],
            "lane2": [
                "Allow AI-assisted prep when the student opens by disclosing what AI contributed and what they changed.",
                "Keep low-stakes practice talks open and formative.",
            ],
            "tilt": [
                "Share the rubric and an annotated model before students prepare.",
                "State what AI use is allowed in preparation and what must be disclosed.",
            ],
        },
        "sample_artifact_html": """
<h3 class="category-title">Presentations: Model Artifacts</h3>
<p class="body-text" style="margin-bottom:10px;">Share these with students up front as the models (the Criteria component of TILT). Both come from a recorded recommendation talk.</p>
<p class="subsection-heading">AI-Forward Model: Prep-Use Disclosure</p>
<p class="body-text" style="margin-bottom:6px;">Use this when AI is permitted in preparation and disclosed.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:34%;">Where AI helped</th><th>What the student did with it</th></tr></thead>
  <tbody>
    <tr><td>Outlined the talk.</td><td>Kept the structure but rewrote every slide in own words.</td></tr>
    <tr><td>Suggested counterarguments.</td><td>Added the cost counterargument, which the student had not considered, and answered it with the project's data.</td></tr>
    <tr><td>The live question and answer.</td><td>Did not use AI; answered from knowledge of the project.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student states what AI contributed, what they changed, and where they did not use it, and the disclosure is graded as part of the talk.</p>
<p class="subsection-heading" style="margin-top:14px;">AI-Resistant Model: Targeted Reflection</p>
<p class="body-text" style="margin-bottom:6px;">Use this when the securing move is a reflection tied to specific feedback.</p>
<table class="considerations-table" style="margin-bottom:8px;">
  <thead><tr><th style="width:30%;">The change</th><th style="width:35%;">The feedback that drove it</th><th>The evidence</th></tr></thead>
  <tbody>
    <tr><td>Moved the central recommendation from adding staff to redesigning the intake form.</td><td>A peer noted on slide 6 that the bottleneck was duplicate data entry, not headcount.</td><td>The project's own intake-time data, re-cited in the cost section.</td></tr>
  </tbody>
</table>
<p class="body-text" style="margin-bottom:0;font-size:12.5px;"><b style="color:var(--accent);">Why it earns credit:</b> the student names a specific change, the exact feedback that drove it, and the evidence, which requires engagement a generic reflection lacks.</p>
""",
    },
]

# ---------------------------------------------------------------------------
# CSS (shared with assurance_report_html.py)
# ---------------------------------------------------------------------------

_CSS = """
  :root {
    --ink: #211f1c;
    --ink-strong: #1a1714;
    --muted: #8a8275;
    --secondary: #4a463f;
    --border: #e8e3da;
    --border-faint: #f1ece3;
    --cream: #faf8f4;
    --paper: #ffffff;
    --accent: #8c1d40;
    --accent-tint: #fbf4f6;
    --gold: #ffc627;
    --serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', serif;
    --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  }
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: var(--sans); background: var(--paper); color: var(--ink); line-height: 1.62; -webkit-font-smoothing: antialiased; }
  .report { max-width: 760px; margin: 0 auto; padding: 44px 28px 24px; }
  .report-header { border-bottom: 3px solid var(--gold); padding-bottom: 22px; margin-bottom: 30px; }
  .report-header-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; color: var(--accent); margin-bottom: 10px; }
  .report-header h1 { font-family: var(--serif); font-size: 27px; font-weight: 600; letter-spacing: -0.2px; color: var(--ink-strong); margin-bottom: 7px; }
  .report-header-meta { font-size: 12.5px; color: var(--muted); letter-spacing: 0.2px; }
  .section { padding: 0 0 6px; margin-bottom: 20px; break-inside: avoid; }
  .section-heading { font-family: var(--serif); font-size: 18px; font-weight: 600; letter-spacing: -0.1px; color: var(--accent); margin-bottom: 16px; padding-bottom: 9px; border-bottom: 2px solid var(--gold); }
  .subsection-heading { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--accent); margin: 12px 0 6px; }
  .category-title { font-family: var(--serif); font-size: 19px; font-weight: 600; color: var(--ink-strong); margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1.5px solid var(--gold); }
  .category-block { padding: 0; margin-bottom: 10px; break-inside: avoid; }
  .description-text { font-size: 13.5px; color: var(--secondary); max-width: 68ch; margin-bottom: 10px; }
  .body-text { font-size: 12.5px; color: var(--secondary); line-height: 1.5; margin-bottom: 6px; max-width: 68ch; }
  .tech-mini-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 4px; }
  .tech-cell { border: 1px solid var(--border); padding: 9px 11px; break-inside: avoid; }
  .tech-cell-title { font-size: 12.5px; font-weight: 700; color: var(--ink-strong); margin-bottom: 3px; }
  .tech-cell-body { font-size: 11.5px; color: var(--secondary); line-height: 1.45; }
  .moves-table, .considerations-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 6px; }
  .moves-table th, .considerations-table th { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: var(--accent); text-align: left; padding: 4px 8px; border-bottom: 1.5px solid var(--gold); }
  .moves-table td, .considerations-table td { vertical-align: top; padding: 6px 8px; border: 1px solid var(--border); font-size: 11.5px; }
  .moves-table tr, .considerations-table tr { break-inside: avoid; }
  .table-list { margin: 0; padding-left: 14px; font-size: 11.5px; line-height: 1.4; }
  .table-list li { margin-bottom: 3px; }
  .table-list li:last-child { margin-bottom: 0; }
  .report-footer { text-align: center; padding: 20px; font-size: 10.5px; letter-spacing: 0.3px; color: var(--muted); margin-top: 14px; border-top: 1px solid var(--border); }
  @page { size: Letter; margin: 14mm; }  /* ponytail: US Letter; change to A4 if a program needs it */
  @media print {
    html, body { background: var(--paper); }
    /* Print the ASU maroon/gold backgrounds and borders, not stripped white. */
    body, .section, .category-block {
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }
    .report { padding: 0; max-width: 100%; margin: 0; }
    .section { break-inside: auto; }
    .category-block { break-inside: avoid; }
    .category-block + .category-block { break-before: page; }
    .moves-table tr, .considerations-table tr { break-inside: avoid; }
    h1, h2, h3, .section-heading, .category-title { break-after: avoid; }
    p, li { orphans: 3; widows: 3; }
  }
"""

# ---------------------------------------------------------------------------
# Renderer
# ---------------------------------------------------------------------------

def render(*, generated_date: str = "") -> str:
    """Render the Assessment Design Playbook as self-contained HTML."""
    date_html = _e(generated_date) if generated_date else ""

    categories_html = ""
    for cat in _CATEGORIES:
        resistant_items = "".join(f"<li>{_e(m)}</li>" for m in cat["resistant"])
        forward_items = "".join(f"<li>{_e(m)}</li>" for m in cat["forward"])
        tech_items = "".join(
            f'<div class="tech-cell">'
            f'<p class="tech-cell-title">{_e(name)}</p>'
            f'<p class="tech-cell-body">{_e(body)}</p>'
            f"</div>"
            for name, body in cat["tech"]
        )
        lane1_items = "".join(f"<li>{_e(m)}</li>" for m in cat["lanes"]["lane1"])
        lane2_items = "".join(f"<li>{_e(m)}</li>" for m in cat["lanes"]["lane2"])
        tilt_items = "".join(f"<li>{_e(m)}</li>" for m in cat["lanes"]["tilt"])

        categories_html += f"""
<div class="category-block">
  <h3 class="category-title">{_e(cat["title"])}</h3>
  <p class="subsection-heading">Where It Is Substitutable</p>
  <p class="body-text">{_e(cat["vulnerable"])}</p>
  <table class="moves-table">
    <thead><tr><th>AI-Resistant Moves</th><th>AI-Forward Moves</th></tr></thead>
    <tbody><tr>
      <td><ul class="table-list">{resistant_items}</ul></td>
      <td><ul class="table-list">{forward_items}</ul></td>
    </tr></tbody>
  </table>
  <p class="subsection-heading">Technologies That Help</p>
  <div class="tech-mini-grid">{tech_items}</div>
  <p class="subsection-heading">Two-Lane &amp; TILT Considerations</p>
  <table class="considerations-table">
    <thead><tr><th>Lane 1: Secured</th><th>Lane 2: Open / AI-Permitted</th><th>TILT</th></tr></thead>
    <tbody><tr>
      <td><ul class="table-list">{lane1_items}</ul></td>
      <td><ul class="table-list">{lane2_items}</ul></td>
      <td><ul class="table-list">{tilt_items}</ul></td>
    </tr></tbody>
  </table>
</div>
<div class="category-block">{cat["sample_artifact_html"]}</div>"""

    meta_html = ""
    if date_html:
        meta_html = f'<div class="report-header-meta">{date_html}</div>'
    footer_date = f" &middot; {date_html}" if date_html else ""

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Assessment Design Playbook</title>
<style>
{_CSS}
</style>
</head>
<body>
<div class="report">
  <div class="report-header">
    <div class="report-header-label">AI Assurance Audit</div>
    <h1>Assessment Design Playbook</h1>
    {meta_html}
  </div>

  <section class="section">
    <h2 class="section-heading">How to Use This Playbook</h2>
    <p class="description-text">
      Each category names where the assessment is substitutable with current AI, then leads with
      AI-resistant moves that secure the grade, followed by AI-forward moves that build AI use
      into the learning, the technologies that support each, and where the secured lane and TILT
      pairing sit for that category. The goal is to certify learning, not to
      catch students or watch them fail.
    </p>
    <p class="description-text" style="margin-bottom: 8px;">Two design rules run through every category:</p>
    <ul style="margin: 0 0 10px; padding-left: 20px; font-size: 13.5px; color: var(--secondary); line-height: 1.5; max-width: 68ch;">
      <li style="margin-bottom: 8px;">No method is fully AI-proof: proctoring, oral defense, and live video all have known
      limits (deepfakes and AI voiceovers among them), and each carries a cost in faculty time, student access, and the
      flexibility the program promises online students. Treat these as last-resort moves reserved for the highest-stakes
      work, not the first reach.</li>
      <li>Whenever a move asks students to verify, critique, or improve AI output, pair it with the rubric and several
      annotated examples of strong work shared in advance. This is the Criteria component of the Transparency in Learning
      and Teaching (TILT) framework (Winkelmes); models let students aim at the target and close the gap for students who
      do not already know the conventions, which is the equity gain TILT was built to deliver.</li>
    </ul>
  </section>

  <section class="section">
    <h2 class="section-heading">Key Terms</h2>
    <table class="considerations-table" style="margin-bottom:0;">
      <tbody>
        <tr><td style="width:22%;"><b>Lane 1: Secured</b></td><td>The assessments that certify the grade. Secured one of two ways: an identity control (a proctored or LockDown exam) or authorship evidence (staged drafts, a process or decision log, a short defense). Aim to secure roughly the top 40% or more of total grade weight here.</td></tr>
        <tr><td><b>Lane 2: Open</b></td><td>AI-permitted, lower-stakes work for learning and practice. It does not prove authorship, so keep its weight modest.</td></tr>
        <tr><td><b>Secured anchor</b></td><td>The specific assessment that carries the certified weight. A course needs at least one.</td></tr>
        <tr><td><b>TILT</b></td><td>Transparency in Learning and Teaching. Any move that asks students to verify, critique, or improve AI output ships with the rubric plus two or three annotated model examples up front, so students aim at the target.</td></tr>
      </tbody>
    </table>
  </section>
  <section class="section">
    <h2 class="section-heading">How to Triage Your Course</h2>
    <p class="description-text" style="margin-bottom:10px;">Before applying any category, find the assessment that carries the most unaddressed risk and secure it first. Work in order:</p>
    <ol style="margin:0 0 14px; padding-left:20px; font-size:13.5px; color:var(--secondary); line-height:1.5; max-width:68ch;">
      <li style="margin-bottom:6px;">List every graded assessment with its share of the final grade.</li>
      <li style="margin-bottom:6px;">Rate each for AI-substitutability: could a student produce it with AI and have it look the same?</li>
      <li style="margin-bottom:6px;">Rank by grade weight multiplied by substitutability. The highest-weight, most-substitutable item is the biggest unaddressed risk.</li>
      <li style="margin-bottom:6px;">Secure that item first using its category page, then work down the list until roughly 40% or more of the grade weight sits on Lane 1 anchors.</li>
      <li>Leave low-weight, low-risk work in Lane 2, open and AI-permitted.</li>
    </ol>
    <p class="subsection-heading">Match the Securing Move to Your Enrollment</p>
    <p class="description-text" style="margin-bottom:8px;">Securing costs grading time. Pick anchors you can sustain at your course size.</p>
    <table class="considerations-table" style="margin-bottom:12px;">
      <thead><tr><th>Securing move</th><th>Grading cost</th><th>Scales to</th></tr></thead>
      <tbody>
        <tr><td>Randomized or auto-varied application pool (New Quizzes)</td><td>Low, auto-graded</td><td>Any enrollment</td></tr>
        <tr><td>Staged drafts or a process or decision log, skimmed</td><td>Low to medium</td><td>Most course sizes</td></tr>
        <tr><td>AI-use appendix or verification log, read closely</td><td>Medium</td><td>Sample above about 40 students</td></tr>
        <tr><td>Recorded walkthrough or defense (VoiceThread)</td><td>Medium to high</td><td>Sample a rotating subset</td></tr>
        <tr><td>Live oral defense or viva</td><td>High</td><td>Highest-stakes only, always sampled</td></tr>
      </tbody>
    </table>
    <p class="description-text" style="margin-bottom:6px;"><b style="color:var(--accent);">Accessibility.</b> Timed exams and live synchronous defenses carry disability and accommodation implications. Coordinate timing extensions and alternative formats with the campus accommodations office before requiring them.</p>
    <p class="description-text" style="margin-bottom:0;"><b style="color:var(--accent);">If you suspect a violation.</b> This playbook prevents substitution by design; it is not a detection tool. If you still suspect a violation, follow the ASU Academic Integrity policy and your college Academic Integrity Officer (provost.asu.edu/academic-integrity) rather than acting on an AI-detector score.</p>
  </section>

  <section class="section" id="categories">
    <h2 class="section-heading">Assessment Categories</h2>
    {categories_html}
  </section>

  <div class="report-footer">
    Assessment Design Playbook &middot; ASU Canvas Course Builder{footer_date}<br>Last reviewed: 2026-07-16 &middot; Owner: Michael Miller &middot; Review cycle: each session term
  </div>
</div>
</body>
</html>"""


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Render the Assessment Design Playbook as self-contained HTML."
    )
    parser.add_argument("--output", required=True, help="Output HTML file path.")
    parser.add_argument(
        "--generated-date",
        default="",
        help="Date string for the header (YYYY-MM-DD). Omit to leave blank.",
    )
    parser.add_argument(
        "--pdf",
        default=None,
        help="Optional PDF output path via the report_pdf_exporter.",
    )
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> None:
    args = _parse_args(argv)
    html_out = render(generated_date=args.generated_date)
    output_path = Path(args.output)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(html_out, encoding="utf-8")
    print(f"Playbook written to: {output_path}")

    if args.pdf:
        import report_pdf_exporter

        try:
            pdf_path = report_pdf_exporter.export_pdf(
                html_path=output_path, output_path=args.pdf
            )
        except report_pdf_exporter.PDFExportUnavailable as exc:
            print(str(exc), file=sys.stderr)
            sys.exit(1)
        print(f"PDF written to: {pdf_path}")


if __name__ == "__main__":
    main()
