Lab reference intervals nclex

Reading File
Searching the Web

NCLEX lab reference intervals normal values 2024

Finding Sources
Reading Web Page

https://goodnurse.com/article/200/lab-values-cheat-sheet-for…

Reading File
I now have solid reference data from Goldman-Cecil Medicine plus the NCLEX-focused web source. Here is the complete, organized reference table:

NCLEX Lab Reference Intervals

NCLEX note (Next Gen NCLEX update): As of the NGN format, exam items that include a numeric lab value provide the reference range alongside it. You still need to recognize what is abnormal and know the clinical action to take.

1. Complete Blood Count (CBC)

LabNormal RangeKey NCLEX Flag
Hemoglobin (male)13.5 - 17.5 g/dL<7 = likely transfusion threshold
Hemoglobin (female)12.0 - 16.0 g/dL
Hematocrit (male)41 - 53%Roughly 3x Hgb
Hematocrit (female)36 - 46%
RBC (male)4.5 - 5.9 million/mcL
RBC (female)4.0 - 5.2 million/mcL
WBC5,000 - 10,000/mcL<1,000 neutrophils = neutropenic precautions
Platelets150,000 - 400,000/mcL<50,000 = bleeding risk; <20,000 = spontaneous bleed risk
MCV80 - 100 fLLow = microcytic; High = macrocytic

2. Serum Electrolytes (BMP/CMP)

LabNormal RangeCritical Values / Clinical Flag
Sodium (Na+)136 - 145 mEq/L<120 or >160 = seizure/neuro risk
Potassium (K+)3.5 - 5.0 mEq/L<3.0 or >6.0 = cardiac arrhythmia; hold digoxin if low K+
Chloride (Cl-)98 - 106 mEq/LFollows Na+ and pH changes
CO2/Bicarb (HCO3-)22 - 26 mEq/LMetabolic component of ABG
BUN10 - 20 mg/dLElevated = dehydration, renal disease
Creatinine0.6 - 1.3 mg/dLBest indicator of renal function
BUN:Creatinine ratio10:1 to 20:1>20:1 = prerenal azotemia
Glucose (fasting)70 - 100 mg/dL<70 = hypoglycemia; >126 = diabetes threshold
Calcium (Ca2+)9.0 - 10.5 mg/dL<8.0 = Trousseau/Chvostek signs; >12 = cardiac issues
Magnesium (Mg2+)1.5 - 2.5 mEq/LLow = cardiac arrhythmia, seizures
Phosphate3.0 - 4.5 mg/dLInverse relationship with Ca2+

3. Arterial Blood Gas (ABG)

ComponentNormal RangeInterpretation
pH7.35 - 7.45<7.35 = acidosis; >7.45 = alkalosis
PaCO235 - 45 mmHgRespiratory component (inverse with pH)
HCO3-22 - 26 mEq/LMetabolic component (moves same direction as pH)
PaO280 - 100 mmHg<60 = hypoxemia; needs supplemental O2
SaO295 - 100%<90% = critical
ROME mnemonic: Respiratory Opposite, Metabolic Equal (pH and component move in opposite vs. same direction)

4. Coagulation Studies

LabNormal RangeKey Flag
PT (Prothrombin Time)11 - 13.5 secondsMonitors warfarin (Coumadin)
INR0.8 - 1.1 (therapeutic for warfarin: 2 - 3)>3 = hold warfarin; antidote = Vitamin K
aPTT25 - 35 secondsMonitors heparin; therapeutic = 60 - 80 seconds
Thrombin Time17 - 23 seconds

5. Liver Function Tests (LFTs)

LabNormal Range
AST (SGOT)10 - 40 units/L
ALT (SGPT)9 - 45 units/L (slight sex variation)
ALP30 - 120 units/L
Total Bilirubin0.2 - 1.2 mg/dL
Direct Bilirubin0 - 0.3 mg/dL
Albumin3.5 - 5.0 g/dL
Total Protein6.0 - 8.0 g/dL

6. Cardiac Markers

LabNormal RangeKey Flag
Troponin I<0.04 ng/mLAny elevation = cardiac injury; most specific for MI
Troponin T<0.01 ng/mLRises 3-4 hrs after MI, peaks 24-48 hrs
CK-MB0 - 3 ng/mLRises 4-6 hrs; back to normal in 48-72 hrs
BNP<100 pg/mL>500 = heart failure likely
NT-proBNP<125 pg/mLAge-adjusted; elevated in HF

7. Renal Function / Urinalysis

LabNormal Range
Urine specific gravity1.005 - 1.030
Urine pH4.5 - 8.0
Urine proteinNegative / trace
Urine glucoseNegative
Urine RBCs0 - 2/hpf
Urine WBCs0 - 4/hpf
GFR>60 mL/min/1.73m2 (CKD if <60 for 3+ months)

8. Glucose / Diabetes

LabValueMeaning
Fasting glucose<100 mg/dLNormal
Fasting glucose100 - 125 mg/dLPrediabetes
Fasting glucose≥126 mg/dLDiabetes
HbA1c<5.7%Normal
HbA1c5.7 - 6.4%Prediabetes
HbA1c≥6.5%Diabetes; goal for diabetics: <7%

9. Thyroid

LabNormal Range
TSH0.4 - 4.0 mIU/L
Free T40.8 - 1.8 ng/dL
Free T32.3 - 4.2 pg/mL
Flag: High TSH = hypothyroid; Low TSH = hyperthyroid

10. Therapeutic Drug Levels

DrugTherapeutic RangeToxic Level
Digoxin0.5 - 2.0 ng/mL>2.0 ng/mL
Lithium0.6 - 1.2 mEq/L (acute: 0.8-1.4)>1.5 mEq/L
Phenytoin (Dilantin)10 - 20 mcg/mL>20 mcg/mL
Theophylline10 - 20 mcg/mL>20 mcg/mL
Vancomycin (trough)10 - 20 mcg/mL>20 mcg/mL
Carbamazepine4 - 12 mcg/mL

Quick Memory Tricks

  • K+ 3.5-5.0: "3.5 to stay alive, 5.0 to the door" (hypo = LOW, hyper = HIGH cardiac risk)
  • Na+ 136-145: Think "1-3-6 to 1-4-5"
  • pH 7.35-7.45: "35-45, stay alive"
  • Platelet 150K-400K: Below 50K = bleeding precautions; below 20K = spontaneous bleed
  • Heparin vs Warfarin: aPTT monitors heparin; PT/INR monitors warfarin

Sources: Goldman-Cecil Medicine (Appendix 2 - Reference Intervals), GoodNurse 2026 Lab Values Cheat Sheet, NursesLabs Normal Lab Values

Create a printable lab values PDF I can study from

Reading File
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"

Running Command

mkdir -p /home/daytona/workspace/nclex-lab-values

Writing File

~/nclex-lab-values/generate_pdf.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import PageBreak

OUTPUT = "/home/daytona/workspace/nclex-lab-values/NCLEX_Lab_Values.pdf"

# ── Colour palette ──────────────────────────────────────────────────────────
NAVY      = colors.HexColor("#1B3A6B")
TEAL      = colors.HexColor("#1A7F8E")
LIGHT_BG  = colors.HexColor("#EBF4F7")
HEADER_BG = colors.HexColor("#1B3A6B")
ROW_ALT   = colors.HexColor("#F0F7FA")
WARN_RED  = colors.HexColor("#C0392B")
WARN_AMB  = colors.HexColor("#E67E22")
WHITE     = colors.white
BLACK     = colors.black
GREY_LINE = colors.HexColor("#BDC3C7")

def make_doc():
    doc = SimpleDocTemplate(
        OUTPUT,
        pagesize=letter,
        leftMargin=0.6*inch, rightMargin=0.6*inch,
        topMargin=0.6*inch, bottomMargin=0.6*inch,
        title="NCLEX Lab Reference Intervals",
        author="Orris Study Aid"
    )
    return doc

# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

title_style = ParagraphStyle(
    "DocTitle", fontSize=22, leading=28,
    textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold"
)
subtitle_style = ParagraphStyle(
    "Subtitle", fontSize=11, leading=14,
    textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica"
)
section_style = ParagraphStyle(
    "Section", fontSize=13, leading=17,
    textColor=WHITE, alignment=TA_LEFT, fontName="Helvetica-Bold",
    leftIndent=6
)
body_style = ParagraphStyle(
    "Body", fontSize=8.5, leading=12,
    textColor=BLACK, fontName="Helvetica"
)
tip_style = ParagraphStyle(
    "Tip", fontSize=8, leading=11,
    textColor=NAVY, fontName="Helvetica-Oblique",
    leftIndent=8, rightIndent=8
)
flag_style = ParagraphStyle(
    "Flag", fontSize=7.5, leading=10,
    textColor=WARN_RED, fontName="Helvetica-Bold"
)
mnemonic_style = ParagraphStyle(
    "Mnemonic", fontSize=8.5, leading=12,
    textColor=NAVY, fontName="Helvetica-Bold"
)

# ── Helpers ──────────────────────────────────────────────────────────────────
def section_banner(title_text):
    """Coloured banner acting as a section header."""
    tbl = Table([[Paragraph(title_text, section_style)]], colWidths=[7.3*inch])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), TEAL),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("ROUNDEDCORNERS", [4]),
    ]))
    return tbl

def data_table(headers, rows, col_widths, critical_rows=None):
    """Styled data table with alternating rows and optional red-highlight rows."""
    critical_rows = critical_rows or []
    data = [headers] + rows
    tbl = Table(data, colWidths=col_widths, repeatRows=1)

    style_cmds = [
        # Header row
        ("BACKGROUND",    (0,0), (-1,0), HEADER_BG),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("TOPPADDING",    (0,0), (-1,0), 5),
        ("BOTTOMPADDING", (0,0), (-1,0), 5),
        # Body
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 7.5),
        ("TOPPADDING",    (0,1), (-1,-1), 3),
        ("BOTTOMPADDING", (0,1), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 5),
        ("RIGHTPADDING",  (0,0), (-1,-1), 5),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    for i, row in enumerate(rows, start=1):
        if i in critical_rows:
            style_cmds.append(("BACKGROUND", (0,i), (-1,i), colors.HexColor("#FDECEA")))
            style_cmds.append(("TEXTCOLOR",  (0,i), (-1,i), WARN_RED))
        elif i % 2 == 0:
            style_cmds.append(("BACKGROUND", (0,i), (-1,i), ROW_ALT))

    tbl.setStyle(TableStyle(style_cmds))
    return tbl

# ═══════════════════════════════════════════════════════════════════════════════
#  BUILD CONTENT
# ═══════════════════════════════════════════════════════════════════════════════
story = []

# ── Title block ──────────────────────────────────────────────────────────────
title_block = Table(
    [[Paragraph("NCLEX Lab Reference Intervals", title_style)],
     [Paragraph("Complete Study Guide · All Critical Values · Mnemonics", subtitle_style)]],
    colWidths=[7.3*inch]
)
title_block.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), NAVY),
    ("TOPPADDING",    (0,0), (-1,-1), 12),
    ("BOTTOMPADDING", (0,0), (-1,-1), 12),
    ("ROUNDEDCORNERS", [6]),
]))
story.append(title_block)
story.append(Spacer(1, 10))

# ─── IMPORTANT NOTE ──────────────────────────────────────────────────────────
note = Table([[Paragraph(
    "📌  NGN NCLEX Update: Items that include a numeric lab value now provide "
    "the reference range alongside it. Focus on recognising abnormal patterns "
    "and selecting the correct nursing action.", tip_style
)]], colWidths=[7.3*inch])
note.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FFF9E6")),
    ("BOX",        (0,0), (-1,-1), 0.8, WARN_AMB),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
]))
story.append(note)
story.append(Spacer(1, 10))

# ════════════════════════════════════════════════════════════════════════════
# 1. CBC
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("1.  Complete Blood Count (CBC)"),
    Spacer(1, 4),
    data_table(
        headers=["Lab Test", "Normal Range", "Critical Flags / NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["Hemoglobin – Male",   "13.5 – 17.5 g/dL", "< 7 g/dL → likely transfusion threshold"],
            ["Hemoglobin – Female", "12.0 – 16.0 g/dL", "Pale, fatigue, tachycardia = anemia signs"],
            ["Hematocrit – Male",   "41 – 53 %",         "Hct ≈ 3× Hgb rule-of-thumb"],
            ["Hematocrit – Female", "36 – 46 %",         "< 30% = significant; needs intervention"],
            ["RBC – Male",          "4.5 – 5.9 M/mcL",  "Low = anemia; high = polycythemia/dehydration"],
            ["RBC – Female",        "4.0 – 5.2 M/mcL",  ""],
            ["WBC (Total)",         "5,000 – 10,000/mcL","ANC < 1,000 = neutropenic precautions"],
            ["Platelets",           "150,000 – 400,000/mcL", "< 50,000 = bleeding precautions; < 20,000 = spontaneous bleed"],
            ["MCV",                 "80 – 100 fL",       "Low = microcytic (iron-def); High = macrocytic (B12/folate)"],
            ["MCH",                 "27 – 33 pg",        "Reflects Hgb content per RBC"],
        ],
        critical_rows=[1, 8]
    ),
    Spacer(1, 4),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 2. Serum Electrolytes
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("2.  Serum Electrolytes (BMP / CMP)"),
    Spacer(1, 4),
    data_table(
        headers=["Electrolyte", "Normal Range", "Critical Flags / NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["Sodium (Na⁺)",       "136 – 145 mEq/L",  "< 120 or > 160 = seizures / neuro changes"],
            ["Potassium (K⁺)",     "3.5 – 5.0 mEq/L",  "< 3.0 or > 6.0 = cardiac arrhythmia; hold digoxin if K⁺ low"],
            ["Chloride (Cl⁻)",     "98 – 106 mEq/L",   "Follows Na⁺ and acid–base changes"],
            ["CO₂ / HCO₃⁻",       "22 – 26 mEq/L",    "Metabolic component; low = metabolic acidosis"],
            ["BUN",                "10 – 20 mg/dL",     "Elevated = dehydration, renal disease, GI bleed"],
            ["Creatinine",         "0.6 – 1.3 mg/dL",  "Best single indicator of glomerular filtration"],
            ["BUN : Creatinine",   "10:1 – 20:1",       "> 20:1 = pre-renal azotemia (dehydration)"],
            ["Glucose (fasting)",  "70 – 100 mg/dL",    "< 70 = hypoglycemia; > 126 = diabetes threshold"],
            ["Calcium (Ca²⁺)",     "9.0 – 10.5 mg/dL", "< 8.0 = Chvostek/Trousseau; > 12.0 = cardiac effects"],
            ["Magnesium (Mg²⁺)",   "1.5 – 2.5 mEq/L",  "Low = arrhythmias, tremors, seizures; repleted before K⁺"],
            ["Phosphate (PO₄)",    "3.0 – 4.5 mg/dL",  "Inverse to Ca²⁺; low in refeeding syndrome"],
        ],
        critical_rows=[1, 2, 9, 10]
    ),
    Spacer(1, 4),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 3. ABG
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("3.  Arterial Blood Gas (ABG)"),
    Spacer(1, 4),
    data_table(
        headers=["Component", "Normal Range", "Interpretation"],
        col_widths=[1.5*inch, 1.5*inch, 4.3*inch],
        rows=[
            ["pH",      "7.35 – 7.45",   "< 7.35 = acidosis  |  > 7.45 = alkalosis"],
            ["PaCO₂",   "35 – 45 mmHg",  "RESPIRATORY component; rises in hypoventilation (acidosis); falls in hyperventilation (alkalosis)"],
            ["HCO₃⁻",   "22 – 26 mEq/L", "METABOLIC component; low in metabolic acidosis; high in metabolic alkalosis"],
            ["PaO₂",    "80 – 100 mmHg", "< 60 mmHg = hypoxemia; supplement O₂; < 40 = severe"],
            ["SaO₂",    "95 – 100 %",    "< 90% = critical; correlate with PaO₂"],
            ["Anion Gap","8 – 12 mEq/L", "High-gap acidosis: MUDPILES (Methanol, Uraemia, DKA, Propylene glycol, INH, Lactic acidosis, Ethylene glycol, Salicylates)"],
        ],
        critical_rows=[1, 4, 5]
    ),
    Spacer(1, 4),
    Table([[Paragraph(
        "ROME Mnemonic: Respiratory = Opposite (pH ↑, PaCO₂ ↓ / pH ↓, PaCO₂ ↑) | "
        "Metabolic = Equal (pH ↑, HCO₃⁻ ↑ / pH ↓, HCO₃⁻ ↓)",
        mnemonic_style
    )]], colWidths=[7.3*inch], style=[
        ("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#E8F4FD")),
        ("BOX", (0,0), (-1,-1), 0.6, TEAL),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 4. Coagulation
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("4.  Coagulation Studies"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Normal Range", "Therapeutic / Critical / NCLEX Tips"],
        col_widths=[1.7*inch, 1.7*inch, 3.9*inch],
        rows=[
            ["PT",             "11 – 13.5 sec",     "Monitors WARFARIN (Coumadin); prolonged = bleeding risk"],
            ["INR",            "0.8 – 1.1",          "Therapeutic (warfarin) = 2.0–3.0; > 3.0 → hold warfarin; antidote = Vitamin K"],
            ["aPTT",           "25 – 35 sec",        "Monitors HEPARIN; therapeutic = 60–80 sec; antidote = Protamine Sulfate"],
            ["Fibrinogen",     "200 – 400 mg/dL",   "Low = DIC, liver disease, massive transfusion"],
            ["D-Dimer",        "< 0.5 mcg/mL",      "Elevated = DVT/PE, DIC; not specific alone"],
        ],
        critical_rows=[1, 3]
    ),
    Spacer(1, 4),
    Table([[Paragraph(
        "Memory Hook: PT/INR → Warfarin (starts with W like Warfarin = 'W'arfarin → 'W'arfarin monitors P'T')  |  "
        "aPTT → Heparin (both have a 'P' and are IV/injectable)",
        mnemonic_style
    )]], colWidths=[7.3*inch], style=[
        ("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#E8F4FD")),
        ("BOX", (0,0), (-1,-1), 0.6, TEAL),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 5. LFTs
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("5.  Liver Function Tests (LFTs)"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Normal Range", "NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["AST (SGOT)",       "10 – 40 units/L",   "Elevated in hepatitis, MI, muscle damage (less liver-specific)"],
            ["ALT (SGPT)",       "9 – 45 units/L",    "Most liver-specific transaminase; elevated in hepatitis/cirrhosis"],
            ["ALP",              "30 – 120 units/L",  "Elevated in cholestasis, bone disease, pregnancy"],
            ["Total Bilirubin",  "0.2 – 1.2 mg/dL",  "> 2.5 = visible jaundice; monitor in newborns"],
            ["Direct Bilirubin", "0 – 0.3 mg/dL",    "Conjugated; elevated in obstructive jaundice"],
            ["Albumin",          "3.5 – 5.0 g/dL",   "Best marker of chronic liver function; low = malnutrition/cirrhosis"],
            ["Total Protein",    "6.0 – 8.0 g/dL",   "Low = malnutrition, nephrotic syndrome"],
            ["Ammonia",          "15 – 45 mcg/dL",   "Elevated in hepatic encephalopathy; low-protein diet helps"],
        ],
        critical_rows=[6, 8]
    ),
]))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════
# 6. Cardiac Markers
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("6.  Cardiac Markers"),
    Spacer(1, 4),
    data_table(
        headers=["Marker", "Normal Range", "Timing / NCLEX Tips"],
        col_widths=[1.9*inch, 1.7*inch, 3.7*inch],
        rows=[
            ["Troponin I",    "< 0.04 ng/mL",   "Rises 3–4 hr after MI; peaks 24–48 hr; returns to normal in 7–10 days; GOLD STANDARD"],
            ["Troponin T",    "< 0.01 ng/mL",   "Same timing as Troponin I; most specific for myocardial injury"],
            ["CK-MB",         "0 – 3 ng/mL",    "Rises 4–6 hr; peaks 18–24 hr; normalises in 48–72 hr; re-elevation = re-infarction"],
            ["BNP",           "< 100 pg/mL",    "> 500 = heart failure likely; used for HF diagnosis & monitoring"],
            ["NT-proBNP",     "< 125 pg/mL",    "Age-adjusted; elevated in HF; better for outpatient monitoring"],
            ["Myoglobin",     "< 90 ng/mL",     "Earliest to rise (1–3 hr) but NOT cardiac-specific"],
        ],
        critical_rows=[1, 2]
    ),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 7. Renal / Urinalysis
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("7.  Renal Function & Urinalysis"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Normal Range", "NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["GFR",                  "> 60 mL/min/1.73m²", "< 60 for ≥ 3 months = CKD; < 15 = kidney failure (dialysis)"],
            ["Urine Specific Gravity","1.005 – 1.030",      "Low (1.001–1.005) = dilute/overhydration; High (> 1.030) = dehydration/SIADH"],
            ["Urine pH",             "4.5 – 8.0",           "Alkaline urine helps with uric-acid stones; acidic with UTI prevention"],
            ["Urine Protein",        "Negative / trace",    "Proteinuria = glomerular damage, nephrotic syndrome"],
            ["Urine Glucose",        "Negative",            "Glycosuria if blood glucose > 180 mg/dL (renal threshold)"],
            ["Urine RBCs",           "0 – 2 /hpf",         "> 3 = hematuria; rule out UTI, stones, cancer"],
            ["Urine WBCs",           "0 – 4 /hpf",         "> 5 = pyuria; indicates UTI or pyelonephritis"],
            ["Urine Output",         "≥ 30 mL/hr (adult)",  "< 30 mL/hr = oliguria; notify provider; may indicate shock/AKI"],
        ],
        critical_rows=[1, 8]
    ),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 8. Glucose / Diabetes
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("8.  Glucose & Diabetes Lab Values"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Normal / Threshold", "NCLEX Tips"],
        col_widths=[2.0*inch, 1.6*inch, 3.7*inch],
        rows=[
            ["Fasting Glucose – Normal",      "70 – 99 mg/dL",   "Euglycemic; no action needed"],
            ["Fasting Glucose – Prediabetes",  "100 – 125 mg/dL", "Lifestyle modification; monitor annually"],
            ["Fasting Glucose – Diabetes",     "≥ 126 mg/dL",     "Two separate tests required for diagnosis"],
            ["Random Glucose – Diabetes",      "≥ 200 mg/dL",     "With symptoms (polyuria, polydipsia, weight loss)"],
            ["2-hr OGTT – Diabetes",           "≥ 200 mg/dL",     "75 g oral glucose load"],
            ["HbA1c – Normal",                 "< 5.7 %",         "Reflects 2–3 month average glucose"],
            ["HbA1c – Prediabetes",            "5.7 – 6.4 %",     ""],
            ["HbA1c – Diabetes",               "≥ 6.5 %",         "Diabetic goal: < 7.0 % (ADA); < 8.0 % in elderly"],
            ["Critical Hypoglycemia",          "< 40 mg/dL",      "Give 15–20 g fast-acting carbs OR IV D50; recheck in 15 min"],
            ["Critical Hyperglycemia (DKA)",   "> 250 + ketones", "Kussmaul breathing, fruity breath, anion-gap acidosis"],
        ],
        critical_rows=[3, 9, 10]
    ),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 9. Thyroid
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("9.  Thyroid Function Tests"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Normal Range", "NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["TSH",     "0.4 – 4.0 mIU/L",  "High TSH = HYPOTHYROID (pituitary compensating); Low TSH = HYPERTHYROID"],
            ["Free T4", "0.8 – 1.8 ng/dL",  "Low T4 + High TSH = primary hypothyroidism; treat with levothyroxine"],
            ["Free T3", "2.3 – 4.2 pg/mL",  "Most active thyroid hormone; T3 toxicosis possible with normal T4"],
        ],
        critical_rows=[]
    ),
    Spacer(1, 4),
    Table([[Paragraph(
        "Thyroid Memory: TSH is the THERMOSTAT — if temp is too low (hypothyroid), "
        "the thermostat turns UP (high TSH). If temp is too high (hyperthyroid), "
        "the thermostat turns DOWN (low TSH).",
        mnemonic_style
    )]], colWidths=[7.3*inch], style=[
        ("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#E8F4FD")),
        ("BOX", (0,0), (-1,-1), 0.6, TEAL),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]),
]))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════
# 10. Therapeutic Drug Levels
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("10.  Therapeutic Drug Monitoring"),
    Spacer(1, 4),
    data_table(
        headers=["Drug", "Therapeutic Range", "Toxic Level", "Key Nursing Actions"],
        col_widths=[1.5*inch, 1.4*inch, 1.1*inch, 3.3*inch],
        rows=[
            ["Digoxin",         "0.5 – 2.0 ng/mL",  "> 2.0",    "Hold if K⁺ < 3.5; check apical pulse 1 min; antidote = Digibind"],
            ["Lithium",         "0.6 – 1.2 mEq/L",  "> 1.5",    "Maintain hydration; monitor Na⁺; toxic: tremor, ataxia, confusion"],
            ["Phenytoin",       "10 – 20 mcg/mL",   "> 20",     "Nystagmus, ataxia at toxic levels; gingival hyperplasia with long use"],
            ["Carbamazepine",   "4 – 12 mcg/mL",    "> 12",     "Monitor CBC (aplastic anemia risk); teratogenic"],
            ["Vancomycin (trough)","10–20 mcg/mL",  "> 20",     "Nephrotoxic; monitor SCr; 'Red Man Syndrome' = too-rapid infusion"],
            ["Theophylline",    "10 – 20 mcg/mL",   "> 20",     "N/V, seizures, arrhythmias at toxic levels; caffeine interaction"],
            ["Methotrexate",    "Variable",          "> threshold","Monitor CBC, LFTs; leucovorin rescue; avoid NSAIDs"],
            ["Cyclosporine",    "100–400 ng/mL",     "Variable", "Nephrotoxic; monitor BP, SCr; grapefruit interaction"],
        ],
        critical_rows=[1, 2, 3]
    ),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 11. Lipid Panel
# ════════════════════════════════════════════════════════════════════════════
story.append(KeepTogether([
    section_banner("11.  Lipid Panel"),
    Spacer(1, 4),
    data_table(
        headers=["Test", "Desirable Level", "NCLEX Tips"],
        col_widths=[1.9*inch, 1.5*inch, 3.9*inch],
        rows=[
            ["Total Cholesterol",    "< 200 mg/dL",   "200–239 = borderline; ≥ 240 = high"],
            ["LDL (bad)",            "< 100 mg/dL",   "Goal < 70 in high CV risk (post-MI, DM with CV disease)"],
            ["HDL (good)",           "≥ 60 mg/dL",    "< 40 (M) / < 50 (F) = risk factor for CVD"],
            ["Triglycerides",        "< 150 mg/dL",   "150–199 = borderline; ≥ 500 = pancreatitis risk"],
        ],
        critical_rows=[]
    ),
]))

story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# 12. Quick-Memorise Summary
# ════════════════════════════════════════════════════════════════════════════
story.append(section_banner("12.  Quick-Memorise Cheat Sheet"))
story.append(Spacer(1, 6))

quick = [
    ["K⁺  3.5 – 5.0",     "Na⁺  136 – 145",      "Ca²⁺  9.0 – 10.5",   "Mg²⁺  1.5 – 2.5"],
    ["Glucose  70 – 100",  "BUN  10 – 20",         "Creatinine 0.6–1.3",  "HCO₃⁻  22 – 26"],
    ["Hgb M  13.5–17.5",   "Hgb F  12.0–16.0",    "Hct M  41–53%",       "Hct F  36–46%"],
    ["WBC  5,000–10,000",  "Platelets 150–400K",   "INR  0.8–1.1",        "aPTT  25–35 sec"],
    ["pH  7.35–7.45",      "PaCO₂  35–45 mmHg",   "PaO₂  80–100 mmHg",   "SaO₂  95–100%"],
    ["TSH  0.4–4.0",       "HbA1c  < 5.7% normal","BNP < 100 pg/mL",     "Troponin I < 0.04"],
    ["Digoxin 0.5–2.0",    "Lithium 0.6–1.2",      "Phenytoin 10–20",     "Vancomycin 10–20"],
]

quick_tbl = Table(quick, colWidths=[1.82*inch]*4)
quick_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,0), NAVY),
    ("BACKGROUND", (0,1), (-1,1), colors.HexColor("#1A7F8E")),
    ("BACKGROUND", (0,2), (-1,2), NAVY),
    ("BACKGROUND", (0,3), (-1,3), colors.HexColor("#1A7F8E")),
    ("BACKGROUND", (0,4), (-1,4), NAVY),
    ("BACKGROUND", (0,5), (-1,5), colors.HexColor("#1A7F8E")),
    ("BACKGROUND", (0,6), (-1,6), NAVY),
    ("TEXTCOLOR",  (0,0), (-1,-1), WHITE),
    ("FONTNAME",   (0,0), (-1,-1), "Helvetica-Bold"),
    ("FONTSIZE",   (0,0), (-1,-1), 8),
    ("ALIGN",      (0,0), (-1,-1), "CENTER"),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("GRID",       (0,0), (-1,-1), 0.5, WHITE),
]))
story.append(quick_tbl)
story.append(Spacer(1, 8))

# ════════════════════════════════════════════════════════════════════════════
# Footer note
# ════════════════════════════════════════════════════════════════════════════
story.append(HRFlowable(width="100%", thickness=0.5, color=GREY_LINE))
story.append(Spacer(1, 4))
story.append(Paragraph(
    "Reference: Goldman-Cecil Medicine (Appendix 2 – Reference Intervals) | "
    "Values reflect typical adult non-pregnant ranges. Always verify against your facility's reference ranges.",
    ParagraphStyle("Footer", fontSize=7, textColor=colors.grey, alignment=TA_CENTER)
))

# ════════════════════════════════════════════════════════════════════════════
# BUILD
# ════════════════════════════════════════════════════════════════════════════
doc = make_doc()
doc.build(story)
print(f"PDF written to: {OUTPUT}")
Running Command

python /home/daytona/workspace/nclex-lab-values/generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/nclex-lab-values/NCLEX_Lab_Values.pdf

Your printable study PDF is ready:

NCLEX Lab Values

PDF Document · PDF

Here's what's included across the 3-page document:
SectionContent
CBCHgb, Hct, RBC, WBC, Platelets, MCV with critical flags
Electrolytes (BMP/CMP)Na, K, Cl, HCO3, BUN, Creatinine, Glucose, Ca, Mg, Phosphate
ABGpH, PaCO2, HCO3, PaO2, SaO2, Anion Gap + ROME mnemonic
CoagulationPT, INR, aPTT, Fibrinogen, D-Dimer + Warfarin/Heparin tips
LFTsAST, ALT, ALP, Bilirubin, Albumin, Ammonia
Cardiac MarkersTroponin I/T, CK-MB, BNP, NT-proBNP with timing
Renal/UrinalysisGFR, specific gravity, proteinuria, urine output thresholds
Glucose/DiabetesFasting, OGTT, HbA1c, DKA flags
ThyroidTSH, T3, T4 + thermostat mnemonic
Therapeutic DrugsDigoxin, Lithium, Phenytoin, Vancomycin, and more
Lipid PanelLDL, HDL, Total Cholesterol, Triglycerides
Quick Cheat SheetAll key values in a compact color-coded grid
The design uses navy/teal color coding, red highlights for critical/toxic values, and mnemonic boxes throughout.

Provision of safe environment nclex

Reading File
Searching the Web

NCLEX provision safe environment safety and infection control topics 2024

Finding Sources
Finding Sources
Reading Web Page

https://www.kaptest.com/study/nclex/whats-tested-on-the-ncle…

Finding Sources
Finding Sources
I have enough from the Kaplan NCLEX source and Red Book (infectious disease precautions) to build a thorough, well-organized answer. Here it is:

Provision of a Safe Environment - NCLEX

"Provision of a Safe Environment" falls under the Safe and Effective Care Environment client need on the NCLEX, specifically the Safety and Infection Control subcategory (~12% of exam questions). Here is everything you need to know, organized by testable topic.

NCLEX Safety and Infection Control - Tested Topics

Topic AreaWeight on Exam
Safety & Infection Control (total)~12% of NCLEX
Management of Care (parent category)~20% of NCLEX

1. Accident and Injury Prevention

Fall Prevention

  • Assess fall risk on every admission using a validated tool (Morse Fall Scale, STRATIFY)
  • High-risk interventions: bed in lowest position, call light within reach, non-slip footwear, side rails up (check facility policy - raising all 4 rails = restraint)
  • Hourly rounding reduces falls
  • Place "Fall Risk" armband and signage
  • NCLEX tip: The patient asking for help to the commode is a priority - assistive need = fall risk

Restraints

  • Always try alternatives first: reorientation, family presence, diversional activities, bed alarms
  • Require a physician's order (time-limited; must be renewed per policy)
  • Assessment required every 2 hours - circulation, skin integrity, ROM
  • Release and reposition every 2 hours
  • Never tie restraints to the side rail - tie to the bed frame
  • Document rationale, type, response, alternatives tried
  • Chemical restraints (sedation for behavior control) have the same ethical/legal requirements

Safe Use of Equipment

  • Verify equipment is functioning before use
  • Ensure electrical equipment is grounded
  • Know how to operate IV pumps, PCA pumps, ventilators, sequential compression devices
  • Report malfunctioning equipment immediately; remove from service and tag

2. Standard Precautions (apply to ALL patients, always)

ElementAction
Hand hygieneBefore/after every patient contact; after removing gloves
GlovesContact with blood, body fluids, mucous membranes, non-intact skin
GownIf clothing/skin likely to be soiled
Mask + eye protectionSplashing or spraying of body fluids likely
Safe needle handlingNo recapping; sharps container at point of use
Patient care equipmentClean and disinfect between patients
Hand hygiene: Soap and water (mandatory) for C. difficile and norovirus - alcohol gel does NOT kill spores.

3. Transmission-Based Precautions

Contact Precautions

  • Gown + gloves upon room entry
  • Private room (or cohort)
  • Diseases: MRSA, VRE, C. difficile, scabies, wound infections, RSV, norovirus
  • Dedicated equipment (stethoscope, BP cuff) stays in room

Droplet Precautions

  • Surgical mask within 3 feet / upon room entry
  • Private room preferred
  • Diseases: influenza, pertussis, meningococcal meningitis, rubella, mumps, Group A strep pharyngitis, Mycoplasma pneumoniae
  • Patient wears mask during transport

Airborne Precautions

  • N-95 respirator (fit-tested) for healthcare workers
  • Negative-pressure room (air exhausted outside, 6-12 air exchanges/hour)
  • Keep door closed at all times
  • Diseases: TB, measles (rubeola), varicella/chickenpox, disseminated herpes zoster
  • Patient wears surgical mask during transport

Quick Memory: "My Chicken Needs Airborne"

  • Measles, Chickenpox (varicella), TB = Airborne

Contact + Airborne (both required)

  • Disseminated/open herpes zoster (shingles)
  • SARS, smallpox (in outbreaks)

4. Surgical Asepsis (Sterile Technique)

  • Required for invasive procedures: IV insertion, urinary catheterization, wound care, surgical prep
  • Sterile field rules:
    • 1-inch border around sterile field is considered contaminated
    • Never reach across a sterile field
    • Items below waist level are contaminated
    • Sterile to sterile; unsterile to unsterile
    • If in doubt - it's contaminated; discard and restart
    • Turn away from sterile field when coughing/sneezing
  • Wet/soaked sterile dressings = contaminated (strike-through contamination)

5. Error Prevention - The Rights of Medication Administration

RightDetails
Right patient2 identifiers: name + date of birth (never room number)
Right drugVerify order; clarify if unclear
Right doseCalculate; double-check with second nurse for high-alert drugs
Right routeOral vs IV vs IM vs sublingual - never substitute
Right timeWithin 30-60 min of scheduled time (facility policy)
Right documentationSign off immediately after giving, not before
Right reasonKnow why the patient is receiving the drug
Right to refusePatient has the right; document and notify provider
High-alert medications requiring double-checks: insulin, heparin, IV potassium, chemotherapy, opioids, anticoagulants

6. Handling Hazardous / Infectious Materials

  • PPE sequence (Donning): Gown → Mask/respirator → Goggles/face shield → Gloves
  • PPE sequence (Doffing): Gloves → Goggles/face shield → Gown → Mask/respirator (gloves off FIRST - they are most contaminated)
  • Biohazard waste: red bags or containers labeled biohazard
  • Chemotherapy waste: yellow/purple labeled containers; double-bagged
  • Sharps: puncture-resistant containers, never overfill past fill line
  • Needlestick protocol: wash with soap and water, report immediately, baseline labs, follow exposure protocol

7. Reporting Incidents

  • Incident/variance reports filed for: falls, medication errors, equipment failure, patient elopement, needlesticks, unexpected patient events
  • Do NOT document that an incident report was filed in the patient's chart
  • Document objective facts in the chart (what happened, patient response, actions taken)
  • Incident reports are for quality improvement and are not part of the legal medical record

8. Use of Restraints - Summary Card

RequirementDetail
Order requiredYes - with time limit
Assessment frequencyEvery 2 hours
Release and repositionEvery 2 hours
Circulation checkEach assessment
DocumentationType, rationale, alternatives, response
Tie toBed frame (NOT side rail)
Knot typeQuick-release knot

9. Home Safety

  • Remove throw rugs, loose cords, clutter (fall hazards)
  • Install grab bars in bathroom, handrails on stairs
  • Smoke detectors on every floor; test monthly; batteries changed annually
  • Carbon monoxide detector near sleeping areas
  • Medications stored safely away from children, in original labeled containers
  • Safe medication disposal: take-back programs or mix with coffee grounds/kitty litter before discarding
  • Proper food storage to prevent foodborne illness

10. Emergency Response Plan / Disaster Preparedness

RACE (fire safety):

  • R - Rescue (move patients from immediate danger)
  • A - Alarm (activate fire alarm)
  • C - Contain (close doors to contain fire/smoke)
  • E - Extinguish or Evacuate

PASS (fire extinguisher):

  • P - Pull the pin
  • A - Aim at base of fire
  • S - Squeeze the handle
  • S - Sweep side to side

Triage (disaster/mass casualty - START triage):

ColorPriorityMeaning
RedImmediateLife-threatening but salvageable
YellowDelayedSerious but stable
GreenMinor"Walking wounded"
BlackExpectantDead or unsurvivable injuries

11. Security Plan

  • Infant abduction prevention: verify ID bands match mother and baby; never hand infant to someone without proper ID
  • Elopement prevention: locked units for confused/dementia patients; document wandering risk
  • Workplace violence: know facility protocol; de-escalate when safe; maintain exit access
  • Visitor management: verify identity before sharing patient information

12. Ergonomic Principles

  • Use proper body mechanics: wide base of support, bend at knees not waist, keep load close to body
  • Use assistive devices (mechanical lifts, slide boards, transfer belts) for patient repositioning
  • Ask for help for heavy or uncooperative patients - never lift alone above safe limit
  • Prevents nurse back injuries (leading occupational injury in nursing)

High-Yield NCLEX Tips for Safe Environment Questions

  1. Airborne vs Droplet: "My Chicken Needs Airborne" (Measles, Chickenpox/Varicella, TB)
  2. C. diff and norovirus = soap and water only (alcohol gel fails)
  3. Restraint order = bed frame knot + every-2-hour checks
  4. Sterile field doubt = discard, restart
  5. Incident reports = never chart that one was filed
  6. Right patient = 2 identifiers (name + DOB), never room number
  7. Donning PPE = gown first; doffing = gloves first
  8. RACE before PASS in fire emergencies
  9. N-95 = airborne; surgical mask = droplet
  10. Negative-pressure room = airborne precautions (TB, measles, varicella)

Sources: Kaplan NCLEX Safe & Effective Care Environment | Red Book 2021 - Report of the Committee on Infectious Diseases (transmission precautions) | NCSBN NCLEX-RN Test Plan

Create a study guide PDF on safe environment topics

Running Command

mkdir -p /home/daytona/workspace/nclex-safe-env

Writing File

~/nclex-safe-env/generate_safe_env.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY

OUTPUT = "/home/daytona/workspace/nclex-safe-env/NCLEX_Safe_Environment.pdf"

# ── Colour palette ─────────────────────────────────────────────────────────
NAVY      = colors.HexColor("#1B3A6B")
TEAL      = colors.HexColor("#1A7F8E")
GREEN     = colors.HexColor("#1E7E34")
ORANGE    = colors.HexColor("#E67E22")
RED_DARK  = colors.HexColor("#C0392B")
LIGHT_BG  = colors.HexColor("#EBF4F7")
ROW_ALT   = colors.HexColor("#F0F7FA")
ROW_GREEN = colors.HexColor("#EAF7EC")
ROW_RED   = colors.HexColor("#FDECEA")
ROW_AMBER = colors.HexColor("#FEF9E7")
WHITE     = colors.white
GREY_LINE = colors.HexColor("#BDC3C7")
GREY_TEXT = colors.HexColor("#555555")

# ── Document ───────────────────────────────────────────────────────────────
def make_doc():
    return SimpleDocTemplate(
        OUTPUT, pagesize=letter,
        leftMargin=0.55*inch, rightMargin=0.55*inch,
        topMargin=0.55*inch, bottomMargin=0.55*inch,
        title="NCLEX – Provision of a Safe Environment",
        author="Orris Study Aid"
    )

# ── Styles ─────────────────────────────────────────────────────────────────
title_style = ParagraphStyle("T", fontSize=21, leading=27, textColor=WHITE,
    alignment=TA_CENTER, fontName="Helvetica-Bold")
sub_style   = ParagraphStyle("S", fontSize=10, leading=14, textColor=WHITE,
    alignment=TA_CENTER, fontName="Helvetica")
sec_style   = ParagraphStyle("Sec", fontSize=12, leading=16, textColor=WHITE,
    fontName="Helvetica-Bold", leftIndent=6)
body_style  = ParagraphStyle("B", fontSize=8.5, leading=12, textColor=colors.black,
    fontName="Helvetica")
tip_style   = ParagraphStyle("Tip", fontSize=8, leading=11, textColor=NAVY,
    fontName="Helvetica-Oblique", leftIndent=8)
mnem_style  = ParagraphStyle("M", fontSize=8.5, leading=12, textColor=NAVY,
    fontName="Helvetica-Bold", leftIndent=6)
bullet_style= ParagraphStyle("Bul", fontSize=8.5, leading=13, textColor=colors.black,
    fontName="Helvetica", leftIndent=12, bulletIndent=4)
warn_style  = ParagraphStyle("W", fontSize=8.5, leading=12, textColor=RED_DARK,
    fontName="Helvetica-Bold", leftIndent=6)
footer_style= ParagraphStyle("F", fontSize=6.8, textColor=GREY_TEXT,
    alignment=TA_CENTER, fontName="Helvetica")

def B(text): return Paragraph(text, bullet_style)
def T(text): return Paragraph(text, body_style)
def M(text): return Paragraph(text, mnem_style)
def W(text): return Paragraph(text, warn_style)

# ── Helpers ────────────────────────────────────────────────────────────────
def banner(text, color=TEAL):
    tbl = Table([[Paragraph(text, sec_style)]], colWidths=[7.4*inch])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    return tbl

def dtable(headers, rows, col_widths, hi_rows=None, hi_color=None):
    hi_rows   = hi_rows   or []
    hi_color  = hi_color  or ROW_RED
    data = [headers] + rows
    tbl  = Table(data, colWidths=col_widths, repeatRows=1)
    cmds = [
        ("BACKGROUND",    (0,0), (-1,0), NAVY),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 5),
        ("RIGHTPADDING",  (0,0), (-1,-1), 5),
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 7.5),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    for i, _ in enumerate(rows, 1):
        if i in hi_rows:
            cmds.append(("BACKGROUND", (0,i), (-1,i), hi_color))
        elif i % 2 == 0:
            cmds.append(("BACKGROUND", (0,i), (-1,i), ROW_ALT))
    tbl.setStyle(TableStyle(cmds))
    return tbl

def mbox(text, bg=colors.HexColor("#E8F4FD"), border=TEAL):
    tbl = Table([[M(text)]], colWidths=[7.4*inch])
    tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.7, border),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    return tbl

def wbox(text):
    tbl = Table([[W(text)]], colWidths=[7.4*inch])
    tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), colors.HexColor("#FFF3CD")),
        ("BOX",           (0,0), (-1,-1), 0.7, ORANGE),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    return tbl

sp = lambda n=6: Spacer(1, n)

# ══════════════════════════════════════════════════════════════════════════════
# STORY
# ══════════════════════════════════════════════════════════════════════════════
story = []

# ── Title block ───────────────────────────────────────────────────────────
hdr = Table([
    [Paragraph("NCLEX – Provision of a Safe Environment", title_style)],
    [Paragraph("Safety & Infection Control · 12% of Exam  |  Safe & Effective Care Environment Client Need", sub_style)],
], colWidths=[7.4*inch])
hdr.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), NAVY),
    ("TOPPADDING",    (0,0), (-1,-1), 12),
    ("BOTTOMPADDING", (0,0), (-1,-1), 12),
]))
story += [hdr, sp(8)]

# NGN note
story += [
    mbox("📌  NGN Update: Reference ranges are now provided with lab items. For safe-environment questions, "
         "focus on identifying the RISK, selecting the PRIORITY nursing action, and knowing the correct "
         "SEQUENCE (e.g. don before patient contact; doff gloves first).",
         bg=colors.HexColor("#FFF9E6"), border=ORANGE),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 1. STANDARD PRECAUTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("1.  Standard Precautions — Apply to EVERY Patient, EVERY Time"),
    sp(4),
    dtable(
        headers=["Element", "When to Use", "NCLEX Tips"],
        col_widths=[1.4*inch, 2.2*inch, 3.8*inch],
        rows=[
            ["Hand Hygiene",        "Before & after ALL patient contact; after glove removal",
             "Soap & water REQUIRED for C. diff and norovirus — alcohol gel does NOT kill spores"],
            ["Gloves",              "Blood, body fluids, mucous membranes, non-intact skin",
             "Change between tasks on same patient; remove before touching clean surfaces"],
            ["Gown",                "Clothing/skin likely to be soiled",
             "Single use; discard before leaving room"],
            ["Mask + Eye Protection","Splash or spray of body fluids possible",
             "Face shield covers eyes + face; surgical mask + goggles are equivalent"],
            ["Safe Needle Handling", "After every injection/IV procedure",
             "NO recapping; activate safety device immediately; sharps container at point of use"],
            ["Patient Equipment",   "Between every patient use",
             "Dedicate equipment (stethoscope, BP cuff) for isolation patients"],
            ["Respiratory Hygiene", "Symptomatic patients in waiting areas",
             "'Cough etiquette': mask the coughing patient; tissues available"],
        ],
        hi_rows=[1], hi_color=ROW_GREEN,
    ),
    sp(4),
    wbox("⚠  C. difficile & Norovirus: SOAP AND WATER only — alcohol-based hand rub is ineffective against spores."),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 2. TRANSMISSION-BASED PRECAUTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("2.  Transmission-Based Precautions"),
    sp(4),
    dtable(
        headers=["Type", "PPE Required", "Room", "Key Diseases"],
        col_widths=[1.1*inch, 1.8*inch, 1.2*inch, 3.3*inch],
        rows=[
            ["CONTACT",   "Gown + Gloves on room entry",
             "Private or cohort", "MRSA, VRE, C. diff, scabies, RSV, wound infections, norovirus"],
            ["DROPLET",   "Surgical mask within 3 ft",
             "Private preferred", "Influenza, pertussis, meningococcal meningitis, rubella, mumps, Group A strep, Mycoplasma pneumoniae"],
            ["AIRBORNE",  "N-95 respirator (fit-tested)",
             "NEGATIVE PRESSURE; door closed", "TB, Measles (rubeola), Varicella (chickenpox), Disseminated herpes zoster"],
            ["CONTACT + AIRBORNE", "Gown + Gloves + N-95",
             "Negative pressure", "Disseminated/open herpes zoster, SARS, smallpox"],
        ],
        hi_rows=[3, 4], hi_color=ROW_RED,
    ),
    sp(4),
    mbox('🔑  Memory: "My Chicken Needs Airborne" = Measles · Chickenpox (varicella) · TB  →  all require N-95 + negative-pressure room'),
    sp(4),
    mbox("PPE Donning Order:  Gown → Mask/Respirator → Goggles/Face Shield → Gloves\n"
         "PPE Doffing Order:  Gloves FIRST (most contaminated) → Goggles/Face Shield → Gown → Mask LAST"),
    sp(4),
    dtable(
        headers=["Scenario", "Correct Precaution Type"],
        col_widths=[3.7*inch, 3.7*inch],
        rows=[
            ["Patient admitted with active pulmonary TB", "Airborne — N-95, negative-pressure room"],
            ["Post-op patient with MRSA wound", "Contact — gown + gloves"],
            ["Child with meningococcal meningitis", "Droplet — surgical mask"],
            ["Patient with chickenpox", "Airborne — N-95, negative-pressure room"],
            ["Patient with C. difficile diarrhea", "Contact + soap-and-water hand hygiene"],
            ["Patient with active influenza", "Droplet — surgical mask"],
            ["Shingles (disseminated)", "Airborne + Contact"],
        ],
        hi_rows=[1, 4, 7],
    ),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 3. SURGICAL ASEPSIS / STERILE TECHNIQUE
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("3.  Surgical Asepsis — Sterile Technique"),
    sp(4),
]
sterile_rules = [
    ["Rule", "Detail"],
    ["1-inch border", "Outer 1 inch of sterile field is CONTAMINATED — never place sterile items there"],
    ["Never reach across", "Reaching over a sterile field contaminates it; always work from the sides"],
    ["Below waist = contaminated", "Any item dropped below waist level is considered unsterile"],
    ["Sterile ↔ sterile only", "Only sterile items may touch sterile items; gloved hands touch sterile field only"],
    ["Doubt = contaminated", "If you question sterility of an item, discard it and start over — never assume"],
    ["Wet = contaminated", "Moisture wicks microorganisms through packaging (strike-through contamination)"],
    ["Back = unsterile", "Your back is never sterile — never turn your back on the sterile field"],
    ["Cough/sneeze", "Turn away from the field or discard and restart if coughing/sneezing near it"],
]
sr_tbl = Table(sterile_rules, colWidths=[2.0*inch, 5.4*inch])
sr_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), NAVY),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 7.5),
    ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
    ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
    ("TOPPADDING",    (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    *[("BACKGROUND", (0,i), (-1,i), ROW_ALT) for i in range(2, 9, 2)],
]))
story += [sr_tbl, sp(4),
    mbox("Required for: urinary catheterisation, IV insertion, central line care, wound care, surgical prep, "
         "lumbar puncture, thoracentesis, amniocentesis"),
    sp(10),
]

# PAGE BREAK
story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# 4. FALL PREVENTION
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("4.  Fall Prevention"),
    sp(4),
    dtable(
        headers=["Intervention", "Details / NCLEX Tips"],
        col_widths=[2.2*inch, 5.2*inch],
        rows=[
            ["Fall Risk Assessment", "Morse Fall Scale or STRATIFY on admission, every shift, after a fall, after change in condition"],
            ["Environment",          "Bed in LOWEST position; call light within reach; non-slip footwear; clear path to bathroom; adequate lighting"],
            ["Side Rails",           "Raising all 4 = RESTRAINT (requires order); 2 rails up = positional support (generally allowed)"],
            ["Hourly Rounding",      "Ask about the 4 Ps: Pain, Position, Potty, Personal items"],
            ["High-Risk Armband",    "Yellow 'Fall Risk' band + door/bed signage — communicate to all staff"],
            ["Medications",          "Opioids, sedatives, antihypertensives, diuretics, hypoglycemics increase fall risk — reassess after giving"],
            ["Post-Fall Protocol",   "Assess patient first; call provider; complete incident report; document in chart objectively; no mention of incident report in chart"],
        ],
        hi_rows=[3, 7],
    ),
    sp(4),
    mbox("Priority NCLEX: A confused patient trying to get out of bed → Re-orient, lower bed, place call light, increase surveillance. "
         "Restraint is LAST resort — try alternatives first."),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 5. RESTRAINTS
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("5.  Use of Restraints / Safety Devices"),
    sp(4),
    dtable(
        headers=["Requirement", "Detail"],
        col_widths=[2.4*inch, 5.0*inch],
        rows=[
            ["Try alternatives FIRST",    "Reorientation, sitter/family presence, bed alarm, remove tubes if possible, mitt gloves"],
            ["Order required",             "Written, time-limited physician order; must be renewed per facility policy (usually q24h)"],
            ["Assessment frequency",       "Every 2 HOURS — circulation, sensation, skin integrity, ROM"],
            ["Release & reposition",       "Every 2 hours — perform ROM, offer fluids/toileting, re-evaluate need"],
            ["Tie to",                     "BED FRAME (not side rail) — use quick-release knot; never tie in a knot that pulls tighter"],
            ["Documentation",              "Type of restraint, rationale, alternatives tried, patient response, neurovascular checks"],
            ["Chemical restraints",        "Sedation given for behaviour control carries same ethical/legal standards as physical restraint"],
            ["Least restrictive",          "Use least restrictive device that meets patient safety need; wrist > vest > full body"],
        ],
        hi_rows=[3, 5],
    ),
    sp(4),
    wbox("⚠  NEVER tie restraints to a side rail — side rails move and can injure the patient. "
         "ALWAYS use a quick-release knot tied to the fixed bed frame."),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 6. MEDICATION SAFETY — 8 RIGHTS
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("6.  Medication Safety — The 8 Rights"),
    sp(4),
    dtable(
        headers=["Right", "How to Apply"],
        col_widths=[1.8*inch, 5.6*inch],
        rows=[
            ["Right Patient",        "2 identifiers: name + date of birth — NEVER room number; scan armband if available"],
            ["Right Drug",           "Verify order; question unusual drugs or doses; know generic AND brand names"],
            ["Right Dose",           "Calculate independently; double-check with second RN for high-alert meds"],
            ["Right Route",          "Never substitute routes; oral ≠ sublingual ≠ IV; verify route before administration"],
            ["Right Time",           "Within 30–60 min of scheduled time; STAT orders immediately"],
            ["Right Documentation",  "Sign off AFTER giving — never chart medication before administering"],
            ["Right Reason",         "Know the indication; question if unclear; patient education required"],
            ["Right to Refuse",      "Patient has right to refuse; document, educate, and notify provider"],
        ],
        hi_rows=[1, 6],
    ),
    sp(4),
]

# high-alert meds
high_alert = Table(
    [["HIGH-ALERT MEDICATIONS — Always Double-Check with 2nd RN"],
     ["Insulin  |  Heparin / Anticoagulants  |  IV Potassium (KCl)  |  Opioids / Narcotics  |  Chemotherapy  |  Concentrated Electrolytes  |  Neuromuscular Blocking Agents"]],
    colWidths=[7.4*inch]
)
high_alert.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), RED_DARK),
    ("BACKGROUND", (0,1), (0,1), ROW_RED),
    ("TEXTCOLOR",  (0,0), (0,0), WHITE),
    ("TEXTCOLOR",  (0,1), (0,1), RED_DARK),
    ("FONTNAME",   (0,0), (-1,-1), "Helvetica-Bold"),
    ("FONTSIZE",   (0,0), (-1,-1), 8),
    ("ALIGN",      (0,0), (-1,-1), "CENTER"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("BOX",        (0,0), (-1,-1), 0.8, RED_DARK),
]))
story += [high_alert, sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# 7. FIRE SAFETY
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("7.  Fire Safety — RACE & PASS"),
    sp(4),
]

fire_tbl = Table([
    [
        Table([
            [Paragraph("RACE — Fire Response", ParagraphStyle("FH", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_CENTER))],
            [dtable(
                headers=["Letter", "Action"],
                col_widths=[0.5*inch, 2.9*inch],
                rows=[
                    ["R", "RESCUE — move patients from immediate danger"],
                    ["A", "ALARM — pull fire alarm; call facility number"],
                    ["C", "CONTAIN — close all doors to confine smoke/fire"],
                    ["E", "EXTINGUISH or EVACUATE (if safe to do so)"],
                ],
            )],
        ], colWidths=[3.6*inch]),
        Table([
            [Paragraph("PASS — Fire Extinguisher", ParagraphStyle("FH2", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_CENTER))],
            [dtable(
                headers=["Letter", "Action"],
                col_widths=[0.5*inch, 2.9*inch],
                rows=[
                    ["P", "PULL the pin"],
                    ["A", "AIM at the base of the fire"],
                    ["S", "SQUEEZE the handle"],
                    ["S", "SWEEP side to side"],
                ],
            )],
        ], colWidths=[3.6*inch]),
    ]
], colWidths=[3.7*inch, 3.7*inch])
fire_tbl.setStyle(TableStyle([
    ("VALIGN", (0,0), (-1,-1), "TOP"),
    ("LEFTPADDING", (0,0), (-1,-1), 0),
    ("RIGHTPADDING", (0,0), (-1,-1), 4),
]))
story += [fire_tbl, sp(4),
    mbox("RACE comes BEFORE PASS — rescue patients and activate alarm BEFORE attempting to extinguish fire. "
         "Only use extinguisher if fire is small, contained, and you have an exit behind you."),
    sp(10),
]

# PAGE BREAK
story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# 8. DISASTER / MASS CASUALTY TRIAGE
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("8.  Emergency Response — Disaster & Mass Casualty (START Triage)"),
    sp(4),
    dtable(
        headers=["Tag Color", "Priority", "Condition", "Examples"],
        col_widths=[1.0*inch, 1.0*inch, 2.0*inch, 3.4*inch],
        rows=[
            ["RED",    "Immediate",  "Life-threatening but SALVAGEABLE",    "Airway obstruction, uncontrolled bleeding, tension pneumothorax"],
            ["YELLOW", "Delayed",    "Serious but currently STABLE",         "Fractures without vascular compromise, burns < 20% BSA"],
            ["GREEN",  "Minor",      "'Walking wounded'",                    "Minor lacerations, sprains, psychological distress"],
            ["BLACK",  "Expectant",  "Dead OR unsurvivable injuries",        "No pulse after airway repositioning, unsurvivable head trauma"],
        ],
        hi_rows=[1, 4], hi_color=ROW_RED,
    ),
    sp(4),
    mbox("In a mass casualty event, the nurse DOES NOT leave assigned area to help others unless directed. "
         "Follow incident command structure. The charge nurse/supervisor directs reassignment."),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 9. ERGONOMICS & SAFE PATIENT HANDLING
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("9.  Ergonomic Principles & Safe Patient Handling"),
    sp(4),
    dtable(
        headers=["Principle", "Application"],
        col_widths=[2.2*inch, 5.2*inch],
        rows=[
            ["Body mechanics",        "Wide base of support; bend at KNEES not waist; keep load close to body; pivot do not twist"],
            ["Mechanical lifts",      "Use for dependent patients; weight limit per lift type; 2 nurses for lateral transfers"],
            ["Slide boards / sheets", "For lateral transfers in bed; reduce friction and shear forces on patient skin"],
            ["Transfer belts (gait)", "Always used for ambulation assistance; grasp belt, not clothing; stand on patient's weaker side"],
            ["Ask for help",          "Safe lifting limit = 35 lbs; never lift alone above this; back injury is #1 occupational hazard in nursing"],
            ["Footwear",              "Non-slip shoes for nurse; non-slip socks or shoes for patient"],
        ],
        hi_rows=[5],
    ),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 10. HAZARDOUS / INFECTIOUS MATERIALS
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("10.  Handling Hazardous & Infectious Materials"),
    sp(4),
    dtable(
        headers=["Material Type", "Container / Disposal", "Nurse Action"],
        col_widths=[1.8*inch, 2.2*inch, 3.4*inch],
        rows=[
            ["Sharps",            "Puncture-resistant container at point of use",    "Never recap; never overfill past fill line; activate safety device immediately"],
            ["Biohazard waste",   "Red biohazard bags / labeled containers",         "All blood/body fluid–saturated items; double-bag if leaking"],
            ["Chemotherapy waste","Yellow or purple labeled containers",             "PPE: gown, gloves, eye protection; closed-system transfer; special spill kit"],
            ["Radioactive waste", "Lead-shielded containers; label clearly",         "Time, distance, shielding principles; dosimeter badge required"],
            ["Needlestick",       "Wash with soap & water immediately",              "Report to supervisor; baseline labs; follow facility exposure protocol; do NOT squeeze wound"],
        ],
        hi_rows=[5],
    ),
    sp(4),
    wbox("⚠  Needlestick Protocol: Wash → Report immediately → Baseline labs (HIV, HBV, HCV) → "
         "Post-exposure prophylaxis (PEP) if HIV exposure — must start within 72 hours."),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 11. INCIDENT REPORTING
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("11.  Reporting Incidents / Variance Reports"),
    sp(4),
    dtable(
        headers=["What to Do", "What NOT to Do"],
        col_widths=[3.7*inch, 3.7*inch],
        rows=[
            ["File an incident report for: falls, med errors, equipment failure, elopement, needlesticks",
             "Do NOT document that an incident report was filed in the patient's chart"],
            ["Document objective facts in the chart: what happened, patient response, actions taken",
             "Do NOT include opinions, judgments, or blame in documentation"],
            ["Report to charge nurse and provider immediately",
             "Do NOT delay reporting to complete paperwork first"],
            ["Incident reports are for quality improvement and risk management",
             "Incident reports are NOT part of the legal medical record — not discoverable in same way"],
        ],
    ),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 12. HOME SAFETY
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("12.  Home Safety"),
    sp(4),
]

home_data = [
    ["Category", "Teaching Points"],
    ["Fall Prevention", "Remove throw rugs, loose cords, clutter; install grab bars in bathroom; handrails on stairs; adequate lighting (especially nightlights)"],
    ["Fire Safety", "Smoke detector on every floor; test monthly; change battery annually; carbon monoxide detector near sleeping areas; escape plan practiced"],
    ["Medication Safety", "Original labeled containers; child-resistant caps; store away from heat/moisture; safe disposal (take-back programs or coffee grounds/kitty litter + sealed bag before trashing)"],
    ["Poisoning", "Lock up cleaning products and medications; poison control number posted: 1-800-222-1222; never store chemicals in food containers"],
    ["Electrical Safety", "GFCI outlets near water; no overloaded outlets; repair frayed cords immediately; avoid extension cords as permanent solution"],
    ["Infant/Child Safety", "Car seat use; never leave in hot car; pool fencing; outlet covers; safety gates on stairs; crib safety (firm mattress, no soft bedding)"],
]
ht = Table(home_data, colWidths=[1.6*inch, 5.8*inch])
ht.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), NAVY),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 7.5),
    ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
    ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
    ("TOPPADDING",    (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    *[("BACKGROUND", (0,i), (-1,i), ROW_ALT) for i in range(2, 7, 2)],
]))
story += [ht, sp(10)]

# PAGE BREAK
story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# 13. SECURITY
# ══════════════════════════════════════════════════════════════════════════════
story += [
    banner("13.  Security Plan"),
    sp(4),
    dtable(
        headers=["Scenario", "Nursing Action"],
        col_widths=[2.5*inch, 4.9*inch],
        rows=[
            ["Infant abduction", "Verify matching ID bands on mother and infant; never hand baby to unverified individual; activate abduction protocol immediately"],
            ["Patient elopement", "Locked/alarmed units for confused/dementia patients; document elopement risk; notify provider; search protocol"],
            ["Workplace violence", "De-escalate when safe; maintain exit access; call security; document; follow facility zero-tolerance policy"],
            ["Visitor management", "Verify identity before sharing ANY patient info; HIPAA applies to all; patient may restrict visitors"],
            ["Confidentiality",    "Patient info shared only on need-to-know basis; even family members have no automatic right to info without patient consent"],
        ],
    ),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# 14. QUICK-MEMORISE CHEAT SHEET
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("14.  High-Yield Quick-Memorise Summary"), sp(6)]

cheat = [
    ["Airborne diseases",        "TB · Measles · Varicella · Disseminated zoster",
     "C. diff / Norovirus hand hygiene", "Soap & water ONLY (no alcohol gel)"],
    ["Donning PPE order",        "Gown → Mask → Goggles → Gloves",
     "Doffing PPE order",        "Gloves FIRST → Goggles → Gown → Mask LAST"],
    ["Restraint checks",         "Every 2 hours; release + ROM each time",
     "Tie restraints to",        "BED FRAME — quick-release knot"],
    ["2 patient identifiers",    "Name + Date of Birth (NEVER room number)",
     "Incident report in chart?","NEVER document that one was filed"],
    ["RACE",                     "Rescue → Alarm → Contain → Extinguish/Evacuate",
     "PASS",                     "Pull → Aim → Squeeze → Sweep"],
    ["Triage: RED",              "Immediate — life-threatening, salvageable",
     "Triage: BLACK",            "Expectant — dead or unsurvivable"],
    ["Needlestick first action", "Wash with soap & water immediately",
     "HIV PEP window",           "Must start within 72 hours of exposure"],
    ["Safe lift limit",          "35 lbs maximum — use mechanical lift above this",
     "Sterile field doubt",      "Discard — NEVER assume sterility"],
]

cheat_tbl = Table(cheat, colWidths=[1.9*inch, 1.85*inch, 1.9*inch, 1.75*inch])
cheat_tbl.setStyle(TableStyle([
    *[("BACKGROUND", (0,i), (0,i), NAVY if i%2==0 else TEAL) for i in range(len(cheat))],
    *[("BACKGROUND", (2,i), (2,i), NAVY if i%2==0 else TEAL) for i in range(len(cheat))],
    *[("BACKGROUND", (1,i), (1,i), ROW_ALT) for i in range(len(cheat))],
    *[("BACKGROUND", (3,i), (3,i), ROW_ALT) for i in range(len(cheat))],
    *[("TEXTCOLOR",  (0,i), (0,i), WHITE) for i in range(len(cheat))],
    *[("TEXTCOLOR",  (2,i), (2,i), WHITE) for i in range(len(cheat))],
    ("FONTNAME",      (0,0), (-1,-1), "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 7.5),
    ("ALIGN",         (0,0), (-1,-1), "LEFT"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.4, WHITE),
]))
story += [cheat_tbl, sp(8)]

# ══════════════════════════════════════════════════════════════════════════════
# 15. NCLEX PRACTICE QUESTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("15.  NCLEX-Style Practice Questions", color=GREEN), sp(6)]

qs = [
    ("Q1. A nurse is preparing to enter the room of a patient placed on airborne precautions for active TB. "
     "Which PPE should the nurse put on first?",
     "A. Gloves\nB. Gown\nC. N-95 respirator\nD. Face shield",
     "Answer: B — Gown is donned first in the correct sequence: Gown → Mask/Respirator → Goggles → Gloves."),
    ("Q2. A patient with C. difficile diarrhea requires contact precautions. After removing gloves, "
     "what hand hygiene method should the nurse use?",
     "A. Alcohol-based hand rub\nB. Soap and water for at least 20 seconds\nC. Either method is acceptable\nD. Hand sanitizer wipes",
     "Answer: B — Soap and water is mandatory for C. difficile (and norovirus). Alcohol-based products do not kill C. diff spores."),
    ("Q3. A confused patient is attempting to climb out of bed at night. Which intervention should the nurse "
     "implement FIRST?",
     "A. Apply wrist restraints\nB. Request a bed alarm from the charge nurse\nC. Lower the bed, place call light within reach, and reorient the patient\nD. Request a sitter",
     "Answer: C — Least restrictive alternatives (environment, reorientation) are attempted before bed alarms, sitters, or restraints."),
    ("Q4. The nurse administered the wrong dose of metoprolol to a patient. What is the FIRST action?",
     "A. Complete an incident report\nB. Notify the charge nurse and physician\nC. Assess the patient\nD. Document the error in the chart",
     "Answer: C — Patient safety is the priority; assess for adverse effects first, then notify provider, then document and complete incident report."),
    ("Q5. Which action by a nursing student indicates correct understanding of restraint use?",
     "A. Checking restraints once per shift\nB. Tying restraints to the side rail for easy removal\nC. Using a quick-release knot tied to the bed frame\nD. Restraining patient without a physician order in an emergency",
     "Answer: C — Restraints are always tied to the fixed bed frame using a quick-release knot. Side rails move and can harm patients."),
]

for i, (q, choices, ans) in enumerate(qs, 1):
    q_block = Table([
        [Paragraph(q, body_style)],
        [Paragraph(choices.replace("\n", "   "), tip_style)],
        [Paragraph(ans, ParagraphStyle("Ans", fontSize=8, fontName="Helvetica-Bold",
                   textColor=GREEN, leftIndent=6))],
    ], colWidths=[7.4*inch])
    q_block.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,0), colors.HexColor("#EBF4F7")),
        ("BACKGROUND",    (0,1), (0,1), WHITE),
        ("BACKGROUND",    (0,2), (0,2), colors.HexColor("#EAF7EC")),
        ("BOX",           (0,0), (-1,-1), 0.6, TEAL),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    story += [q_block, sp(6)]

# ── Footer ────────────────────────────────────────────────────────────────
story += [
    HRFlowable(width="100%", thickness=0.5, color=GREY_LINE),
    sp(4),
    Paragraph(
        "Source: NCSBN NCLEX-RN Test Plan · Kaplan NCLEX Safe & Effective Care Environment · "
        "Red Book 2021 (Committee on Infectious Diseases) · CDC Standard & Transmission-Based Precautions. "
        "Values reflect general guidelines; always follow your facility's policies.",
        footer_style
    ),
]

# ── Build ─────────────────────────────────────────────────────────────────
doc = make_doc()
doc.build(story)
print(f"PDF written to: {OUTPUT}")
Running Command

python /home/daytona/workspace/nclex-safe-env/generate_safe_env.py 2>&1

Running Command

ls -lh /home/daytona/workspace/nclex-safe-env/NCLEX_Safe_Environment.pdf

Your study guide PDF is ready to download:

NCLEX Safe Environment

PDF Document · PDF

Here's what's packed into the 4-page guide:
SectionWhat's Covered
1. Standard PrecautionsHand hygiene, gloves, gown, masks, sharps - with C. diff/norovirus soap-only warning
2. Transmission-Based PrecautionsContact / Droplet / Airborne table with diseases, PPE, room types + donning/doffing order
3. Surgical AsepsisAll 8 sterile field rules, when sterile technique is required
4. Fall PreventionMorse scale, hourly rounding 4 Ps, side rail rules, high-risk meds
5. RestraintsOrder requirements, 2-hour checks, bed frame rule, quick-release knot
6. Medication SafetyAll 8 Rights + highlighted high-alert medication list
7. Fire SafetyRACE and PASS in side-by-side layout
8. Disaster / TriageSTART triage color codes with examples
9. Ergonomics35 lb rule, mechanical lifts, gait belt technique
10. Hazardous MaterialsSharps, biohazard, chemo, needlestick protocol with 72-hr PEP window
11. Incident ReportingDo/don't table - including never charting that a report was filed
12. Home SafetyFalls, fire, meds, poison control, child safety
13. SecurityInfant abduction, elopement, HIPAA, workplace violence
14. Quick Cheat SheetAll key mnemonics and rules in a compact color-coded grid
15. Practice Questions5 NCLEX-style questions with rationales

Biological warfar and chemical warfar weapons nclex

Reading File
Searching the Web

NCLEX biological chemical warfare weapons bioterrorism nursing study guide

Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Excellent. I now have authoritative textbook data from Harrison's, Tintinalli's Emergency Medicine, Red Book 2021, and Goldman-Cecil on all the key agents. Here is the complete NCLEX review:

Biological & Chemical Warfare Weapons - NCLEX

This falls under Safety & Infection Control (emergency response / disaster preparedness) and Reduction of Risk Potential on the NCLEX. It is a high-yield topic for both NCLEX-RN and PN.

CDC Agent Classification

CategoryRisk LevelExamples
Category AHighest priority - easy to disseminate, high mortality, major public health impactAnthrax, Smallpox, Plague, Botulism, Tularemia, Viral hemorrhagic fevers (Ebola, Marburg)
Category BModerately easy to disseminate, lower mortalityBrucellosis, Q fever, Ricin toxin, Salmonella (food safety threats)
Category CEmerging pathogens that could be engineered for mass disseminationNipah virus, hantavirus

BIOLOGICAL WARFARE AGENTS


1. ANTHRAX (Bacillus anthracis) - Category A

Why it's a bioweapon: Spores are highly stable, can be aerosolized, and inhalation anthrax has very high mortality. The 2001 U.S. mail attacks caused 11 inhalation and 11 cutaneous cases, killing 5 of the inhalation victims.
FormRouteIncubationKey Signs/Symptoms
Cutaneous (95% of natural cases)Skin contact with spores< 1 weekPainless papule → ulcer with vesicles → black eschar (painless); edema, lymphadenopathy
Inhalation (bioweapon concern)Respiratory2 days – 6 weeks (spores can persist in lungs)Flu-like prodrome → rapid deterioration → widened mediastinum on CXR, hemorrhagic meningitis, septic shock
GIIngesting contaminated meat< 1 weekUlcers, bloody diarrhea, ascites
Precautions: Standard precautions (person-to-person transmission is rare; cutaneous discharge potentially infectious)
Treatment:
  • Ciprofloxacin or doxycycline (first-line for inhalation/systemic)
  • Penicillin for susceptible strains
  • Post-exposure prophylaxis: ciprofloxacin or doxycycline x 60 days
  • Anthrax vaccine (AVA) available for high-risk individuals (military)
NCLEX flags:
  • Widened mediastinum on CXR = inhalation anthrax until proven otherwise
  • Black eschar = cutaneous anthrax (painless - key differentiator)
  • Incubation for inhalation can be up to 6 weeks

2. SMALLPOX (Variola virus) - Category A

Why it's a bioweapon: Eradicated globally in 1980; most people < 45 years old have no immunity. Highly contagious via airborne droplets. The only known stocks are held in CDC (USA) and VECTOR (Russia).
FeatureDetails
TransmissionAirborne + Contact - highly contagious
Incubation7-19 days (average 12)
ProdromeHigh fever, headache, backache, malaise 2-4 days BEFORE rash
RashAll lesions are in the SAME stage (synchronous); centrifugal distribution (face, palms, soles first)
LesionsDeep, firm, umbilicated pustules
Precautions: Airborne + Contact (N-95, negative-pressure room, gown, gloves)
Treatment:
  • No specific treatment; supportive care
  • Smallpox vaccine (ACAM2000 - vaccinia virus) given within 4 days of exposure can prevent or reduce disease severity
  • Tecovirimat (TPOXX) - antiviral approved for smallpox treatment
NCLEX flags:
  • Smallpox rash = same stage lesions everywhere (vs. chickenpox = different stages simultaneously)
  • Centrifugal rash (outward from center - face and extremities)
  • Vaccinate contacts WITHIN 4 DAYS of exposure
Differentiating Smallpox vs. Chickenpox:
FeatureSmallpoxChickenpox (Varicella)
Lesion stagesAll SAME stageMultiple stages simultaneously
DistributionCentrifugal (face, palms, soles)Centripetal (trunk > extremities)
Lesion depthDeep, firm, hardSuperficial, soft
Prodrome fever2-4 days BEFORE rashMild, appears WITH rash
ContagiousnessExtremely highHigh

3. PLAGUE (Yersinia pestis) - Category A

FormTransmissionSymptomsPrecautions
BubonicFlea bite from infected rodentSudden fever, swollen tender bubo (lymph node, especially groin/axilla), septicemiaStandard precautions
PneumonicPerson-to-person via respiratory droplets; most likely bioweapon formSevere pneumonia, bloody frothy sputum, rapid progressionDroplet precautions
SepticemicBlood-borne spreadDIC, acral necrosis ("Black Death"), shockStandard precautions
Treatment: Streptomycin (first-line), gentamicin, or doxycycline; ciprofloxacin alternative Post-exposure prophylaxis: doxycycline or ciprofloxacin x 7 days
NCLEX flags:
  • Pneumonic plague = droplet precautions (only form with person-to-person spread)
  • Painful buboes (lymph node swelling) = bubonic plague
  • After intentional release: dual antibiotic therapy recommended for severe pneumonic/septicemic plague (Harrison's 22nd ed.)

4. BOTULISM (Clostridium botulinum toxin) - Category A

Why it's a bioweapon: Most acutely lethal toxin known; can be aerosolized or put into food/water.
Mechanism: Toxin blocks acetylcholine release at the neuromuscular junction → descending flaccid paralysis
FeatureDetails
Key presentationDescending flaccid paralysis (starts with cranial nerves: double vision, drooping eyelids, dysphagia → descends)
Classic "3 Ds"Diplopia, Dysarthria, Dysphagia (then descending weakness)
SensoriumAfebrile, alert, no sensory loss (differentiates from Guillain-Barré)
RespiratoryRespiratory failure from descending paralysis - PRIORITY nursing concern
FoodborneBulging cans, home-canned foods, honey in infants
Precautions: Standard precautions (no person-to-person spread)
Treatment (Harrison's 22nd ed., Goldman-Cecil Medicine):
  • Botulinum antitoxin (BAT) - heptavalent equine antitoxin; prevents progression but does NOT reverse existing paralysis; must be given EARLY
  • Infants: BabyBIG (human-derived botulism immune globulin)
  • Mechanical ventilation for respiratory failure
  • Supportive care - patients are conscious but "locked in" - warn caregivers
  • 95% recover with proper supportive care even without antitoxin
NCLEX flags:
  • Antitoxin stops progression - does NOT reverse paralysis already present
  • Patient is ALERT (conscious) despite paralysis - communicate with patient, explain all procedures
  • Respiratory status monitoring is the priority assessment
  • Infants: honey is contraindicated (risk of infant botulism)

5. TULAREMIA (Francisella tularensis) - Category A

FormSymptomsNotes
UlceroglandularUlcer at inoculation site + lymphadenopathyMost common natural form
Pneumonic (bioweapon form)Pneumonia, chest pain, coughHighly infectious via inhalation
TyphoidalSepsis-like, no lymphadenopathyMost severe form
Treatment: Streptomycin or gentamicin (first-line); doxycycline Precautions: Standard precautions (no person-to-person)

6. VIRAL HEMORRHAGIC FEVERS (Ebola, Marburg, Lassa) - Category A

FeatureDetails
TransmissionContact with blood/body fluids; some airborne potential in outbreak
SymptomsHigh fever, hemorrhage (bleeding from mucous membranes, IV sites), organ failure
PrecautionsAirborne + Contact + Eye protection (full PPE); negative-pressure room
TreatmentSupportive; fluid resuscitation; Zmapp/remdesivir for Ebola
NCLEX flags:
  • Full PPE (including face shield) mandatory
  • Strict isolation - dedicated staff
  • No visitors; strict waste management

CHEMICAL WARFARE AGENTS


1. NERVE AGENTS (Sarin, VX, Soman, Tabun) - Most Lethal Chemical Weapons

Mechanism (Tintinalli's Emergency Medicine): Organophosphate compounds that inhibit acetylcholinesterase → acetylcholine accumulates at all receptor sites → excessive cholinergic stimulation
Three receptor sites affected:
  1. Muscarinic (parasympathetic) - DUMBELS
  2. Nicotinic (neuromuscular junction) - muscle fasciculations → paralysis
  3. CNS - seizures, coma
DUMBELS Mnemonic (Muscarinic signs):
LetterSymptom
DDefecation / Diarrhea
UUrination
MMiosis (pinpoint pupils)
BBradycardia / Bronchospasm / Bronchorrhea
EEmesis
LLacrimation
SSalivation / Seizures / Secretions
Also: SLUDGE mnemonic = Salivation, Lacrimation, Urination, Defecation, GI distress, Emesis
Nicotinic signs: Muscle fasciculations → weakness → paralysis (respiratory failure)
Treatment:
DrugMechanismTarget
AtropineMuscarinic antagonistBlocks DUMBELS symptoms; large doses may be needed (up to 20 mg IV); titrate to drying of secretions NOT to pupil size
Pralidoxime (2-PAM)Reactivates acetylcholinesteraseCounters nicotinic/neuromuscular effects; must give BEFORE "aging" of enzyme occurs
BenzodiazepinesCNSTreats seizures
Decontamination: Remove ALL clothing (removes 80% of contamination); flush skin with copious water
NCLEX flags:
  • Pinpoint pupils (miosis) + excessive secretions = nerve agent exposure
  • Atropine is titrated to secretion drying, NOT pupil size or heart rate
  • Pralidoxime must be given early - loses effectiveness after enzyme "aging"
  • Decontaminate OUTSIDE the hospital before entry - protect the healthcare team

2. VESICANTS / BLISTER AGENTS (Mustard Gas, Lewisite)

FeatureMustard GasLewisite
Onset of symptomsDelayed 2-24 hours after exposureImmediate pain
EyesSevere conjunctivitis, corneal damageImmediate eye pain
SkinDelayed blisters, erythemaImmediate blistering
RespiratoryAirway damage, chemical burnsAirway damage
AntidoteNone (supportive care)BAL (British Anti-Lewisite / dimercaprol)
Precautions: Contact precautions; full decontamination required Treatment: Wound care for blisters, eye irrigation, respiratory support, analgesics
NCLEX flags:
  • Mustard gas = DELAYED symptoms (patient feels fine initially, then deteriorates)
  • Lewisite antidote = BAL (dimercaprol)
  • Remove clothing, copious water irrigation

3. PULMONARY / CHOKING AGENTS (Chlorine, Phosgene)

FeatureDetails
MechanismDestroy alveolar-capillary membrane → pulmonary edema
Chlorine smell"Pool/bleach" odor
Phosgene smell"Freshly cut hay/grass" odor
SymptomsEye/respiratory irritation → cough → dyspnea → pulmonary edema (phosgene has delayed onset)
TreatmentRemove from exposure; fresh air; O2; bronchodilators; treat pulmonary edema
NCLEX flags:
  • "Freshly cut hay" smell = phosgene (more dangerous than chlorine)
  • Delayed pulmonary edema with phosgene - patient may appear okay initially

4. BLOOD AGENTS / CYANIDE (Hydrogen Cyanide, Cyanogen Chloride)

FeatureDetails
MechanismBlocks cytochrome oxidase → cells cannot use oxygen → histotoxic hypoxia
SmellBitter almonds (classic but many people cannot detect)
SymptomsRapid onset: headache, dizziness, anxiety → seizures → cardiac arrest; cyanosis is ABSENT (venous blood remains oxygenated)
AntidoteHydroxocobalamin (Cyanokit) - first line; OR sodium thiosulfate + sodium nitrite
NCLEX flags:
  • Patient in respiratory distress but NO cyanosis = cyanide poisoning (cells can't use O2)
  • "Bitter almonds" smell
  • Hydroxocobalamin is the preferred antidote (turns urine red/orange - warn patient)

5. RIOT CONTROL AGENTS (Tear Gas - CS, CN)

FeatureDetails
MechanismIrritants to mucous membranes
SymptomsTearing, burning eyes, rhinorrhea, cough (self-limiting)
TreatmentFresh air, water irrigation to eyes/skin; supportive
DurationSymptoms resolve within 30 min of removal from exposure

NCLEX Priority Actions for Any WMD Event

  1. Personal safety first - do NOT enter contaminated zone without proper PPE
  2. Decontaminate victims OUTSIDE the facility before they enter
  3. Activate emergency response plan / incident command system
  4. Notify charge nurse, facility management, public health authorities, CDC
  5. Triage using START system (Red/Yellow/Green/Black)
  6. Isolate suspected biological agent cases with appropriate precautions
  7. Document and report all suspected exposures

NCLEX Quick-Reference Summary Table

AgentKey SignPrecautionAntidote / Treatment
Anthrax (inhalation)Widened mediastinum; flu → collapseStandardCiprofloxacin or doxycycline
Anthrax (cutaneous)Painless black escharStandardCiprofloxacin
SmallpoxSynchronous umbilicated lesions; centrifugalAirborne + ContactVaccinia vaccine within 4 days
Plague (pneumonic)Bloody frothy sputumDropletStreptomycin / doxycycline
BotulismDescending flaccid paralysis, afebrile, alertStandardBAT antitoxin (stops progression only)
TularemiaUlcer + lymphadenopathy or pneumoniaStandardStreptomycin
Nerve agentsDUMBELS + miosisDecontaminate + standardAtropine + Pralidoxime + Benzo
Mustard gasDelayed blistersContactNone (supportive)
LewisiteImmediate pain + blistersContactBAL (dimercaprol)
CyanideDyspnea, NO cyanosis, bitter almondsStandardHydroxocobalamin
Chlorine/PhosgenePulmonary edemaStandard + decontaminateO2, bronchodilators

Top 10 NCLEX Tips

  1. Anthrax incubation can be up to 6 weeks for inhalation (spores persist in lungs)
  2. Black eschar + painless = cutaneous anthrax (painless differentiates from other skin infections)
  3. Smallpox = ALL lesions same stage; chickenpox = multiple stages
  4. Pneumonic plague = only plague form requiring droplet precautions (person-to-person)
  5. Botulism antitoxin stops progression, does NOT reverse existing paralysis - patient is alert/conscious
  6. DUMBELS = nerve agent (cholinergic crisis) - PINPOINT PUPILS are key
  7. Atropine titrated to secretion drying, not pupil size, in nerve agent exposure
  8. Mustard gas = DELAYED onset (asymptomatic period after exposure)
  9. Cyanide = NO cyanosis despite severe hypoxia (histotoxic, cells can't use O2)
  10. Decontaminate BEFORE hospital entry - always protect healthcare workers first

Sources: Harrison's Principles of Internal Medicine 22nd ed. (Bioterrorism chapter) | Tintinalli's Emergency Medicine (Nerve Agents) | Red Book 2021 (Anthrax, Plague) | Goldman-Cecil Medicine (Botulism treatment)

Create a colorful pdf of biological warfar and chemical warfar saprate

Running Command

mkdir -p /home/daytona/workspace/nclex-warfare

Writing File

~/nclex-warfare/bio_warfare.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT

OUTPUT = "/home/daytona/workspace/nclex-warfare/NCLEX_Biological_Warfare.pdf"

# ── Palette ──────────────────────────────────────────────────────────────────
DARK_GREEN  = colors.HexColor("#145A32")
MID_GREEN   = colors.HexColor("#1E8449")
LIME        = colors.HexColor("#27AE60")
OLIVE       = colors.HexColor("#7D6608")
AMBER       = colors.HexColor("#E67E22")
RED_DARK    = colors.HexColor("#922B21")
RED_MED     = colors.HexColor("#C0392B")
PURPLE      = colors.HexColor("#6C3483")
TEAL        = colors.HexColor("#117A65")
NAVY        = colors.HexColor("#1A237E")
WHITE       = colors.white
BLACK       = colors.black
ROW_LG      = colors.HexColor("#D5F5E3")
ROW_LA      = colors.HexColor("#FEF9E7")
ROW_LR      = colors.HexColor("#FADBD8")
ROW_LP      = colors.HexColor("#E8DAEF")
ROW_LT      = colors.HexColor("#D1F2EB")
ROW_ALT     = colors.HexColor("#EBF5FB")
GREY_LINE   = colors.HexColor("#BDC3C7")
GREY_TXT    = colors.HexColor("#555555")

# ── Styles ────────────────────────────────────────────────────────────────────
def ps(name, size=8.5, color=BLACK, bold=False, italic=False, align=TA_LEFT,
       leading=None, left=0):
    fn = "Helvetica" + ("-BoldOblique" if bold and italic else
                        "-Bold" if bold else "-Oblique" if italic else "")
    return ParagraphStyle(name, fontSize=size, fontName=fn,
                          textColor=color, alignment=align,
                          leading=leading or size*1.4, leftIndent=left)

title_s  = ps("T",  22, WHITE, bold=True,  align=TA_CENTER)
sub_s    = ps("S",  10, WHITE, italic=True, align=TA_CENTER)
sec_s    = ps("Se", 12, WHITE, bold=True,   left=6)
body_s   = ps("B",  8.5)
tip_s    = ps("Ti", 8,  NAVY, italic=True, left=8)
mnem_s   = ps("M",  8.5, DARK_GREEN, bold=True, left=6)
warn_s   = ps("W",  8.5, RED_DARK,   bold=True, left=6)
foot_s   = ps("F",  6.8, GREY_TXT, align=TA_CENTER)
agent_s  = ps("A",  10, WHITE, bold=True, left=6)
cat_s    = ps("C",  9,  WHITE, bold=True, align=TA_CENTER)

def sp(n=6): return Spacer(1, n)

# ── Helpers ───────────────────────────────────────────────────────────────────
def banner(text, color=MID_GREEN, style=None):
    st = style or sec_s
    t = Table([[Paragraph(text, st)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    return t

def agent_header(text, color):
    t = Table([[Paragraph(text, agent_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def dtable(headers, rows, col_widths, row_colors=None):
    data = [headers] + rows
    tbl  = Table(data, colWidths=col_widths, repeatRows=1)
    cmds = [
        ("BACKGROUND",    (0,0), (-1,0), DARK_GREEN),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 7.5),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 5),
        ("RIGHTPADDING",  (0,0), (-1,-1), 5),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    if row_colors:
        for i, c in enumerate(row_colors, 1):
            if c:
                cmds.append(("BACKGROUND", (0,i), (-1,i), c))
    else:
        for i in range(2, len(rows)+1, 2):
            cmds.append(("BACKGROUND", (0,i), (-1,i), ROW_ALT))
    tbl.setStyle(TableStyle(cmds))
    return tbl

def mbox(text, bg=ROW_LG, border=MID_GREEN):
    t = Table([[Paragraph(text, mnem_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.8, border),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def wbox(text):
    t = Table([[Paragraph(text, warn_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), ROW_LR),
        ("BOX",           (0,0), (-1,-1), 0.8, RED_MED),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def two_col(left_items, right_items, left_color, right_color):
    """Two coloured boxes side by side."""
    def box(items, color):
        rows = [[Paragraph(x, body_s)] for x in items]
        t = Table(rows, colWidths=[3.55*inch])
        cmds = [
            ("BACKGROUND",    (0,0), (-1,-1), color),
            ("TOPPADDING",    (0,0), (-1,-1), 3),
            ("BOTTOMPADDING", (0,0), (-1,-1), 3),
            ("LEFTPADDING",   (0,0), (-1,-1), 8),
            ("BOX",           (0,0), (-1,-1), 0.5, GREY_LINE),
        ]
        for i in range(len(rows)):
            if i % 2 == 1:
                cmds.append(("BACKGROUND", (0,i), (0,i),
                              colors.HexColor(
                                  hex(int(color.hexval(), 16) - 0x0A0A0A if color != WHITE else 0xEEEEEE)[2:].zfill(6)
                              ) if False else color))
        t.setStyle(TableStyle(cmds))
        return t
    outer = Table([[box(left_items, left_color), box(right_items, right_color)]],
                  colWidths=[3.6*inch, 3.6*inch])
    outer.setStyle(TableStyle([
        ("LEFTPADDING",  (0,0), (-1,-1), 0),
        ("RIGHTPADDING", (0,0), (-1,-1), 3),
        ("VALIGN",       (0,0), (-1,-1), "TOP"),
    ]))
    return outer

# ══════════════════════════════════════════════════════════════════════════════
story = []
doc = SimpleDocTemplate(OUTPUT, pagesize=letter,
    leftMargin=0.55*inch, rightMargin=0.55*inch,
    topMargin=0.55*inch, bottomMargin=0.55*inch,
    title="NCLEX – Biological Warfare Agents", author="Orris Study Aid")

# ── TITLE ─────────────────────────────────────────────────────────────────────
hdr = Table([
    [Paragraph("☣  BIOLOGICAL WARFARE AGENTS", title_s)],
    [Paragraph("NCLEX Study Guide  ·  Category A, B & C Agents  ·  Signs, Precautions & Antidotes", sub_s)],
], colWidths=[7.4*inch])
hdr.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_GREEN),
    ("TOPPADDING",    (0,0), (-1,-1), 14),
    ("BOTTOMPADDING", (0,0), (-1,-1), 14),
]))
story += [hdr, sp(8)]

# CDC Categories overview
story += [banner("CDC BIOTERRORISM AGENT CATEGORIES", DARK_GREEN), sp(4)]
cat_data = [
    [Paragraph("CATEGORY A", cat_s), Paragraph("CATEGORY B", cat_s), Paragraph("CATEGORY C", cat_s)],
    [Paragraph("Highest priority\nEasily disseminated\nHigh mortality\nMajor public health impact", body_s),
     Paragraph("Moderately easy to spread\nLower mortality than A\nMay cause illness in large numbers", body_s),
     Paragraph("Emerging agents\nCould be engineered\nfor mass dissemination", body_s)],
    [Paragraph("Anthrax · Smallpox\nPlague · Botulism\nTularemia · VHF\n(Ebola, Marburg)", ps("ca", 8, RED_DARK, bold=True)),
     Paragraph("Brucellosis · Q fever\nRicin toxin\nSalmonella (food safety)\nGlanders", ps("cb", 8, AMBER, bold=True)),
     Paragraph("Nipah virus\nHantavirus\nYellow fever", ps("cc", 8, OLIVE, bold=True))],
]
cat_tbl = Table(cat_data, colWidths=[2.46*inch]*3)
cat_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (0,0), RED_DARK),
    ("BACKGROUND",    (1,0), (1,0), AMBER),
    ("BACKGROUND",    (2,0), (2,0), OLIVE),
    ("BACKGROUND",    (0,1), (0,1), ROW_LR),
    ("BACKGROUND",    (1,1), (1,1), ROW_LA),
    ("BACKGROUND",    (2,1), (2,1), colors.HexColor("#F9F3E3")),
    ("BACKGROUND",    (0,2), (0,2), colors.HexColor("#FDECEA")),
    ("BACKGROUND",    (1,2), (1,2), colors.HexColor("#FEF5E7")),
    ("BACKGROUND",    (2,2), (2,2), colors.HexColor("#FDFDE7")),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 8),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("GRID",          (0,0), (-1,-1), 0.5, WHITE),
    ("BOX",           (0,0), (-1,-1), 1.0, DARK_GREEN),
]))
story += [cat_tbl, sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# ANTHRAX
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("1.  ANTHRAX  (Bacillus anthracis)  —  Category A", RED_DARK), sp(4)]
story += [dtable(
    headers=["Form", "Route", "Incubation", "Key Signs & Symptoms"],
    col_widths=[1.3*inch, 1.1*inch, 1.3*inch, 3.7*inch],
    rows=[
        ["Cutaneous\n(95% natural)", "Skin contact\nwith spores", "< 1 week",
         "Painless papule → vesicles → BLACK ESCHAR with edema; lymphadenopathy"],
        ["Inhalation\n★ BIOWEAPON", "Aerosolized\nspores", "2 days – 6 WEEKS\n(spores persist!)",
         "Flu-like prodrome → rapid collapse → WIDENED MEDIASTINUM on CXR → haemorrhagic meningitis, septic shock"],
        ["GI", "Ingesting\ninfected meat", "< 1 week",
         "Ulcers, bloody diarrhea, nausea, ascites, septicaemia"],
    ],
    row_colors=[ROW_LA, ROW_LR, ROW_LG],
), sp(4)]

story += [
    dtable(
        headers=["Precautions", "Treatment / Prophylaxis", "Antidote?"],
        col_widths=[1.5*inch, 3.8*inch, 2.1*inch],
        rows=[["Standard\n(rare person-to-person)",
               "Ciprofloxacin OR Doxycycline (60 days post-exposure prophylaxis)\nPenicillin for susceptible strains\nAnthrax vaccine (AVA) for high-risk/military",
               "No antitoxin;\nantibiotics are key"]],
    ), sp(4),
    mbox("🔑 NCLEX Keys:  (1) BLACK PAINLESS ESCHAR = cutaneous anthrax\n"
         "(2) WIDENED MEDIASTINUM on chest X-ray = inhalation anthrax until proven otherwise\n"
         "(3) Inhalation incubation up to 6 WEEKS — patient may seem fine initially"),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# SMALLPOX
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("2.  SMALLPOX  (Variola virus)  —  Category A", PURPLE), sp(4)]

# Side-by-side comparison
comp_data = [
    [Paragraph("SMALLPOX", ps("sh", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("CHICKENPOX (Varicella)", ps("ch", 9, WHITE, bold=True, align=TA_CENTER))],
    [Paragraph("All lesions SAME STAGE", body_s), Paragraph("Multiple stages simultaneously", body_s)],
    [Paragraph("Centrifugal: face → palms/soles first", body_s), Paragraph("Centripetal: trunk > extremities", body_s)],
    [Paragraph("Deep, firm, UMBILICATED pustules", body_s), Paragraph("Superficial, soft vesicles", body_s)],
    [Paragraph("Prodrome fever 2–4 days BEFORE rash", body_s), Paragraph("Mild fever WITH rash", body_s)],
    [Paragraph("Extremely contagious", body_s), Paragraph("Highly contagious", body_s)],
    [Paragraph("AIRBORNE + CONTACT precautions", ps("sp", 8, RED_DARK, bold=True)), Paragraph("AIRBORNE precautions", body_s)],
]
comp_tbl = Table(comp_data, colWidths=[3.7*inch, 3.7*inch])
comp_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), PURPLE),
    ("BACKGROUND", (1,0), (1,0), TEAL),
    ("TEXTCOLOR",  (0,0), (-1,0), WHITE),
    ("BACKGROUND", (0,1), (0,1), ROW_LP),
    ("BACKGROUND", (1,1), (1,1), ROW_LT),
    ("BACKGROUND", (0,2), (0,2), ROW_LP),
    ("BACKGROUND", (1,2), (1,2), ROW_LT),
    ("BACKGROUND", (0,3), (0,3), ROW_LP),
    ("BACKGROUND", (1,3), (1,3), ROW_LT),
    ("BACKGROUND", (0,4), (0,4), ROW_LP),
    ("BACKGROUND", (1,4), (1,4), ROW_LT),
    ("BACKGROUND", (0,5), (0,5), ROW_LP),
    ("BACKGROUND", (1,5), (1,5), ROW_LT),
    ("BACKGROUND", (0,6), (0,6), colors.HexColor("#FDECEA")),
    ("BACKGROUND", (1,6), (1,6), ROW_LT),
    ("FONTNAME",   (0,0), (-1,-1), "Helvetica"),
    ("FONTSIZE",   (0,0), (-1,-1), 8),
    ("FONTNAME",   (0,0), (-1,0), "Helvetica-Bold"),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("GRID",       (0,0), (-1,-1), 0.5, GREY_LINE),
    ("VALIGN",     (0,0), (-1,-1), "MIDDLE"),
]))
story += [comp_tbl, sp(4),
    mbox("🔑 NCLEX Keys:  Smallpox = SAME STAGE lesions + centrifugal distribution\n"
         "Vaccinia vaccine (ACAM2000) within 4 DAYS of exposure = prevention\n"
         "Precautions: AIRBORNE + CONTACT (N-95 + negative pressure room + gown + gloves)",
         bg=ROW_LP, border=PURPLE),
    sp(10),
]

# PAGE BREAK
story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# PLAGUE
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("3.  PLAGUE  (Yersinia pestis)  —  Category A", colors.HexColor("#5D4037")), sp(4)]
story += [dtable(
    headers=["Form", "Transmission", "Key Symptoms", "Precautions"],
    col_widths=[1.2*inch, 1.6*inch, 2.8*inch, 1.8*inch],
    rows=[
        ["BUBONIC", "Flea bite from\ninfected rodent",
         "Sudden high fever + painful swollen BUBO\n(lymph node, especially groin/axilla)\n→ bacteremia/septicaemia",
         "STANDARD precautions"],
        ["PNEUMONIC ★\n(Bioweapon)", "Person-to-person\nRESPIRATORY DROPLETS",
         "Severe pneumonia, bloody frothy sputum,\nrapid deterioration, high mortality if untreated",
         "⚠ DROPLET\nprecautions"],
        ["SEPTICAEMIC", "Blood-borne spread\nfrom bubonic/pneumonic",
         "DIC, acral necrosis (BLACK DEATH), septic shock,\npurpuric skin lesions",
         "STANDARD precautions"],
    ],
    row_colors=[ROW_LG, ROW_LR, ROW_LA],
), sp(4),
mbox("🔑 NCLEX Keys:  ONLY PNEUMONIC plague requires DROPLET precautions\n"
     "Painful BUBOES = bubonic plague (distinguish from painless anthrax eschar)\n"
     "Treatment: Streptomycin (first-line) or Gentamicin; Doxycycline alternative\n"
     "Post-exposure prophylaxis: Doxycycline or Ciprofloxacin × 7 days",
     bg=colors.HexColor("#EFEBE9"), border=colors.HexColor("#5D4037")),
sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# BOTULISM
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("4.  BOTULISM  (Clostridium botulinum toxin)  —  Category A", NAVY), sp(4)]

story += [dtable(
    headers=["Feature", "Details"],
    col_widths=[2.0*inch, 5.4*inch],
    rows=[
        ["Mechanism", "Toxin blocks ACh release at NMJ → DESCENDING flaccid paralysis (cranial nerves affected first)"],
        ["Classic '3 Ds'", "Diplopia · Dysarthria · Dysphagia → then descending paralysis → respiratory failure"],
        ["KEY: Patient is ALERT", "Afebrile, NO sensory loss, CONSCIOUS but 'locked in' — always warn caregivers, explain all procedures"],
        ["Respiratory", "PRIORITY: Monitor respiratory rate, O2 sat, vital capacity — intubate early if needed"],
        ["Forms", "Foodborne (bulging cans, home-canned) · Wound · Infant (HONEY contraindicated < 1 yr) · Inhalation (bioweapon)"],
        ["Precautions", "STANDARD (no person-to-person transmission)"],
    ],
    row_colors=[ROW_LT, ROW_ALT, ROW_LR, ROW_LA, ROW_ALT, ROW_LG],
), sp(4),
dtable(
    headers=["Treatment", "Action", "NCLEX Notes"],
    col_widths=[2.2*inch, 2.8*inch, 2.4*inch],
    rows=[
        ["Heptavalent BAT\n(equine antitoxin)", "STOPS PROGRESSION of paralysis;\ndoes NOT reverse existing paralysis",
         "Must give EARLY; call state health dept for BAT from federal stockpile"],
        ["BabyBIG\n(infants)", "Human-derived botulism immune globulin\nfor infant botulism types A & B",
         "Honey strictly contraindicated in infants < 12 months"],
        ["Mechanical ventilation", "For respiratory failure from\ndescending paralysis",
         "Paralysis lasts weeks–months; intensive supportive care required"],
        ["Supportive care", ">95% recover with proper supportive care\neven without antitoxin",
         "Signage: patient is CONSCIOUS — communicate with patient"],
    ],
    row_colors=[ROW_LA, ROW_LP, ROW_LR, ROW_LG],
), sp(4),
wbox("⚠  ANTITOXIN STOPS PROGRESSION — it does NOT reverse paralysis already present. Give as early as possible."),
sp(4),
mbox("🔑 NCLEX Keys:  Descending paralysis + afebrile + alert = BOTULISM\n"
     "Baby + constipation + weak cry + poor feeding = INFANT BOTULISM\n"
     "HONEY contraindicated in infants under 1 year"),
sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# TULAREMIA
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("5.  TULAREMIA  (Francisella tularensis)  —  Category A", TEAL), sp(4)]
story += [dtable(
    headers=["Form", "Transmission", "Symptoms", "Precautions"],
    col_widths=[1.5*inch, 1.7*inch, 2.7*inch, 1.5*inch],
    rows=[
        ["Ulceroglandular\n(most common)", "Tick/deer fly bite;\nskin contact",
         "Ulcer at inoculation site + regional lymphadenopathy", "Standard"],
        ["Pneumonic\n(bioweapon form)", "Aerosolized F. tularensis",
         "Pneumonia, chest pain, cough, haemoptysis", "Standard\n(not person-to-person)"],
        ["Typhoidal", "Various", "Fever, sepsis-like illness, no bubo, very severe", "Standard"],
        ["Oculoglandular", "Conjunctival contact", "Conjunctivitis + preauricular lymphadenopathy", "Standard"],
    ],
    row_colors=[ROW_LT, ROW_LA, ROW_LR, ROW_LG],
), sp(4),
mbox("Treatment: Streptomycin or Gentamicin (first-line)  ·  Doxycycline or Ciprofloxacin (alternatives)\n"
     "🔑 NCLEX: No person-to-person transmission → Standard precautions only",
     bg=ROW_LT, border=TEAL),
sp(10)]

story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# VIRAL HEMORRHAGIC FEVERS
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("6.  VIRAL HEMORRHAGIC FEVERS (VHF)  —  Category A  (Ebola, Marburg, Lassa)", RED_DARK), sp(4)]
story += [dtable(
    headers=["Feature", "Details"],
    col_widths=[1.8*inch, 5.6*inch],
    rows=[
        ["Pathogens", "Filoviruses (Ebola, Marburg), Arenaviruses (Lassa fever), Bunyaviruses"],
        ["Transmission", "Direct contact with blood/body fluids; some droplet potential; NOT airborne in normal conditions"],
        ["Symptoms", "High fever, severe headache → bleeding from mucous membranes and IV sites, organ failure, DIC"],
        ["Incubation", "2–21 days (Ebola); 5–10 days (Marburg)"],
        ["Precautions", "AIRBORNE + CONTACT + EYE PROTECTION  — full PPE; NEGATIVE-PRESSURE room; strict isolation"],
        ["Treatment", "Supportive: IV fluids, electrolytes, O2; Zmapp/Remdesivir for Ebola; no specific cure for most"],
        ["Waste management", "All waste treated as highly infectious; double-bag; autoclave; dedicated staff cohort"],
    ],
    row_colors=[ROW_ALT, ROW_LR, ROW_LA, ROW_ALT, ROW_LR, ROW_LG, ROW_LA],
), sp(4),
wbox("⚠  FULL PPE mandatory: gown + gloves + N-95 + face shield/goggles. Dedicate staff. No visitors. "
     "Report immediately to infection control and public health."),
sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# MASTER QUICK REFERENCE
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("MASTER QUICK-REFERENCE — Biological Warfare Agents", DARK_GREEN), sp(4)]
story += [dtable(
    headers=["Agent", "Category", "Key Presentation", "Precautions", "Treatment / Antidote"],
    col_widths=[1.2*inch, 0.7*inch, 2.3*inch, 1.3*inch, 2.0*inch],
    rows=[
        ["Anthrax\n(cutaneous)",  "A", "Painless BLACK ESCHAR",               "Standard",            "Ciprofloxacin / Doxycycline"],
        ["Anthrax\n(inhalation)", "A", "Widened mediastinum, flu → collapse",  "Standard",            "Ciprofloxacin / Doxycycline"],
        ["Smallpox",              "A", "Synchronous umbilicated lesions;\ncentrifugal rash",
         "AIRBORNE +\nCONTACT",   "Vaccinia vaccine\nwithin 4 days"],
        ["Plague\n(bubonic)",     "A", "Painful BUBO + fever",                 "Standard",            "Streptomycin / Doxycycline"],
        ["Plague\n(pneumonic)",   "A", "Bloody frothy sputum, pneumonia",      "DROPLET",             "Streptomycin (dual Abx)"],
        ["Botulism",              "A", "Descending flaccid paralysis; afebrile;\nalert/conscious",
         "Standard",              "BAT antitoxin\n(stops progression)"],
        ["Tularemia",             "A", "Ulcer + lymphadenopathy OR pneumonia", "Standard",            "Streptomycin / Gentamicin"],
        ["VHF (Ebola)",           "A", "Fever + haemorrhage + organ failure",  "AIRBORNE + CONTACT\n+ Eye protection",
         "Supportive; Remdesivir"],
    ],
    row_colors=[ROW_LA, ROW_LR, ROW_LP, ROW_LG, ROW_LR, colors.HexColor("#EBF5FB"),
                ROW_LT, ROW_LR],
), sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# PRACTICE QUESTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("NCLEX-STYLE PRACTICE QUESTIONS — Biological Warfare", MID_GREEN), sp(6)]

qs = [
    ("Q1.  A patient presents with a painless black skin lesion on the forearm after handling animal hides. "
     "Which condition does the nurse suspect, and what is the priority nursing action?",
     "A. Plague — initiate droplet precautions\n"
     "B. Cutaneous anthrax — initiate standard precautions and notify public health\n"
     "C. Smallpox — initiate airborne and contact precautions\n"
     "D. Tularemia — initiate contact precautions",
     "✔  Answer: B — A painless black eschar after animal-hide exposure = cutaneous anthrax. "
     "Standard precautions are sufficient (rare person-to-person transmission). Notify public health immediately."),
    ("Q2.  A patient with suspected inhalation anthrax asks the nurse why antibiotics need to be taken for "
     "60 days. What is the nurse's best response?",
     "A. 'Anthrax bacteria are resistant and need a long course to kill.'\n"
     "B. 'Anthrax spores can remain dormant in the lungs and germinate weeks later, so prolonged treatment prevents reactivation.'\n"
     "C. 'The antibiotics prevent you from infecting others for 60 days.'\n"
     "D. 'This is the standard course for all respiratory infections.'",
     "✔  Answer: B — Inhalation anthrax has an incubation of up to 6 weeks due to spore dormancy. "
     "60-day prophylaxis covers this window."),
    ("Q3.  A nurse caring for a patient with botulism notes he is unable to move his limbs but his eyes "
     "follow the nurse's movements. Which action is the PRIORITY?",
     "A. Reassure the patient that botulism always resolves spontaneously\n"
     "B. Assess respiratory rate, O2 saturation, and vital capacity frequently\n"
     "C. Initiate contact precautions immediately\n"
     "D. Administer atropine as ordered",
     "✔  Answer: B — The patient is conscious (eyes following = alert) but paralysed. "
     "Respiratory failure from descending paralysis is the life-threatening priority. "
     "Atropine is NOT used for botulism (that is for nerve agents)."),
    ("Q4.  During a mass casualty bioterrorism event, a patient is brought in with a rash where all lesions "
     "appear identical in stage and are concentrated on the face, palms, and soles. "
     "Which precautions does the nurse implement?",
     "A. Standard precautions only\n"
     "B. Droplet precautions\n"
     "C. Airborne AND contact precautions; place in negative-pressure room\n"
     "D. Contact precautions only",
     "✔  Answer: C — Synchronous same-stage lesions with centrifugal distribution (face, palms, soles) = smallpox. "
     "Requires both airborne (N-95, negative-pressure room) and contact (gown, gloves) precautions."),
]

for q, choices, ans in qs:
    blk = Table([
        [Paragraph(q, body_s)],
        [Paragraph(choices.replace("\n", "   "), ps("qc", 7.8, italic=True))],
        [Paragraph(ans, ps("qa", 8, MID_GREEN, bold=True))],
    ], colWidths=[7.4*inch])
    blk.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (0,0), colors.HexColor("#EBF5FB")),
        ("BACKGROUND", (0,1), (0,1), WHITE),
        ("BACKGROUND", (0,2), (0,2), ROW_LG),
        ("BOX",        (0,0), (-1,-1), 0.7, MID_GREEN),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    story += [blk, sp(6)]

# FOOTER
story += [
    HRFlowable(width="100%", thickness=0.5, color=GREY_LINE), sp(4),
    Paragraph("Sources: Harrison's Principles of Internal Medicine 22E · Red Book 2021 · CDC Bioterrorism Agent List · "
              "Goldman-Cecil Medicine · NCSBN NCLEX-RN Test Plan", foot_s),
]

doc.build(story)
print(f"Biological Warfare PDF: {OUTPUT}")
Writing File

~/nclex-warfare/chem_warfare.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT

OUTPUT = "/home/daytona/workspace/nclex-warfare/NCLEX_Chemical_Warfare.pdf"

# ── Palette ───────────────────────────────────────────────────────────────────
DARK_MAROON = colors.HexColor("#7B241C")
MED_RED     = colors.HexColor("#C0392B")
BRIGHT_RED  = colors.HexColor("#E74C3C")
ORANGE      = colors.HexColor("#E67E22")
AMBER       = colors.HexColor("#F39C12")
YELLOW_DK   = colors.HexColor("#B7950B")
DARK_BLUE   = colors.HexColor("#1A237E")
TEAL        = colors.HexColor("#117A65")
PURPLE      = colors.HexColor("#6C3483")
GREY_DK     = colors.HexColor("#4A4A4A")
WHITE       = colors.white
BLACK       = colors.black
ROW_LR      = colors.HexColor("#FADBD8")
ROW_LO      = colors.HexColor("#FDEBD0")
ROW_LY      = colors.HexColor("#FEF9E7")
ROW_LB      = colors.HexColor("#D6EAF8")
ROW_LT      = colors.HexColor("#D1F2EB")
ROW_LP      = colors.HexColor("#E8DAEF")
ROW_ALT     = colors.HexColor("#F2F3F4")
GREY_LINE   = colors.HexColor("#BDC3C7")
GREY_TXT    = colors.HexColor("#555555")

def ps(name, size=8.5, color=BLACK, bold=False, italic=False, align=TA_LEFT, leading=None, left=0):
    fn = "Helvetica" + ("-BoldOblique" if bold and italic else
                        "-Bold" if bold else "-Oblique" if italic else "")
    return ParagraphStyle(name, fontSize=size, fontName=fn,
                          textColor=color, alignment=align,
                          leading=leading or size*1.4, leftIndent=left)

title_s = ps("T", 22, WHITE, bold=True,  align=TA_CENTER)
sub_s   = ps("S", 10, WHITE, italic=True, align=TA_CENTER)
sec_s   = ps("Se",12, WHITE, bold=True,  left=6)
body_s  = ps("B", 8.5)
tip_s   = ps("Ti", 8, DARK_BLUE, italic=True, left=8)
mnem_s  = ps("M", 8.5, DARK_MAROON, bold=True, left=6)
warn_s  = ps("W", 8.5, MED_RED,    bold=True, left=6)
foot_s  = ps("F", 6.8, GREY_TXT, align=TA_CENTER)
agent_s = ps("A", 10, WHITE, bold=True, left=6)

def sp(n=6): return Spacer(1, n)

def banner(text, color=DARK_MAROON):
    t = Table([[Paragraph(text, sec_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    return t

def agent_header(text, color):
    t = Table([[Paragraph(text, agent_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def dtable(headers, rows, col_widths, row_colors=None):
    data = [headers] + rows
    tbl  = Table(data, colWidths=col_widths, repeatRows=1)
    cmds = [
        ("BACKGROUND",    (0,0), (-1,0), DARK_MAROON),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 7.5),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 5),
        ("RIGHTPADDING",  (0,0), (-1,-1), 5),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    if row_colors:
        for i, c in enumerate(row_colors, 1):
            if c:
                cmds.append(("BACKGROUND", (0,i), (-1,i), c))
    else:
        for i in range(2, len(rows)+1, 2):
            cmds.append(("BACKGROUND", (0,i), (-1,i), ROW_ALT))
    tbl.setStyle(TableStyle(cmds))
    return tbl

def mbox(text, bg=ROW_LO, border=ORANGE):
    t = Table([[Paragraph(text, mnem_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.8, border),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def wbox(text):
    t = Table([[Paragraph(text, warn_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), ROW_LR),
        ("BOX",           (0,0), (-1,-1), 0.8, MED_RED),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

# ══════════════════════════════════════════════════════════════════════════════
story = []
doc = SimpleDocTemplate(OUTPUT, pagesize=letter,
    leftMargin=0.55*inch, rightMargin=0.55*inch,
    topMargin=0.55*inch, bottomMargin=0.55*inch,
    title="NCLEX – Chemical Warfare Agents", author="Orris Study Aid")

# ── TITLE ─────────────────────────────────────────────────────────────────────
hdr = Table([
    [Paragraph("⚗  CHEMICAL WARFARE AGENTS", title_s)],
    [Paragraph("NCLEX Study Guide  ·  Nerve Agents · Vesicants · Pulmonary · Blood Agents  ·  Antidotes & Decontamination", sub_s)],
], colWidths=[7.4*inch])
hdr.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_MAROON),
    ("TOPPADDING",    (0,0), (-1,-1), 14),
    ("BOTTOMPADDING", (0,0), (-1,-1), 14),
]))
story += [hdr, sp(8)]

# Overview categories
story += [banner("CHEMICAL WARFARE AGENT CLASSES", DARK_MAROON), sp(4)]
cat_data = [
    [Paragraph("NERVE AGENTS", ps("n", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("VESICANTS\n(Blister Agents)", ps("v", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("PULMONARY\n(Choking Agents)", ps("p", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("BLOOD AGENTS\n(Cyanide)", ps("b2", 9, WHITE, bold=True, align=TA_CENTER))],
    [Paragraph("Sarin (GB)\nVX · Soman (GD)\nTabun (GA)", ps("nd", 8, MED_RED, bold=True, align=TA_CENTER)),
     Paragraph("Mustard Gas (HD)\nLewisite (L)\nPhosgene Oxime", ps("vd", 8, ORANGE, bold=True, align=TA_CENTER)),
     Paragraph("Chlorine (Cl₂)\nPhosgene (CG)\nDiphosgene", ps("pd", 8, YELLOW_DK, bold=True, align=TA_CENTER)),
     Paragraph("Hydrogen Cyanide\nCyanogen Chloride\n(AC / CK)", ps("bd", 8, PURPLE, bold=True, align=TA_CENTER))],
    [Paragraph("Inhibit acetylcholinesterase\n→ cholinergic crisis (DUMBELS)", body_s),
     Paragraph("Alkylate DNA\n→ tissue necrosis,\nblistering", body_s),
     Paragraph("Destroy alveolar membrane\n→ pulmonary oedema", body_s),
     Paragraph("Block cytochrome oxidase\n→ histotoxic hypoxia", body_s)],
]
cat_tbl = Table(cat_data, colWidths=[1.85*inch]*4)
cat_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), MED_RED),
    ("BACKGROUND", (1,0), (1,0), ORANGE),
    ("BACKGROUND", (2,0), (2,0), YELLOW_DK),
    ("BACKGROUND", (3,0), (3,0), PURPLE),
    ("BACKGROUND", (0,1), (0,1), ROW_LR),
    ("BACKGROUND", (1,1), (1,1), ROW_LO),
    ("BACKGROUND", (2,1), (2,1), ROW_LY),
    ("BACKGROUND", (3,1), (3,1), ROW_LP),
    ("BACKGROUND", (0,2), (0,2), ROW_ALT),
    ("BACKGROUND", (1,2), (1,2), ROW_ALT),
    ("BACKGROUND", (2,2), (2,2), ROW_ALT),
    ("BACKGROUND", (3,2), (3,2), ROW_ALT),
    ("FONTNAME",   (0,0), (-1,-1), "Helvetica"),
    ("FONTSIZE",   (0,0), (-1,-1), 8),
    ("FONTNAME",   (0,0), (-1,0), "Helvetica-Bold"),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("LEFTPADDING",   (0,0), (-1,-1), 4),
    ("ALIGN",      (0,0), (-1,-1), "CENTER"),
    ("GRID",       (0,0), (-1,-1), 0.5, WHITE),
    ("BOX",        (0,0), (-1,-1), 1.0, DARK_MAROON),
    ("VALIGN",     (0,0), (-1,-1), "MIDDLE"),
]))
story += [cat_tbl, sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# 1. NERVE AGENTS
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("1.  NERVE AGENTS  —  Sarin (GB), VX, Soman, Tabun", MED_RED), sp(4)]

story += [dtable(
    headers=["Receptor Site", "Effect of ACh Excess", "Signs/Symptoms"],
    col_widths=[1.8*inch, 2.0*inch, 3.6*inch],
    rows=[
        ["MUSCARINIC\n(Parasympathetic)", "Glandular overstimulation\n& smooth muscle contraction",
         "DUMBELS: Defecation, Urination, Miosis (PINPOINT PUPILS),\nBradycardia/Bronchospasm/Bronchorrhoea, Emesis, Lacrimation, Salivation"],
        ["NICOTINIC\n(NMJ)", "Neuromuscular\noverstimulation → failure",
         "Muscle fasciculations → weakness → PARALYSIS → RESPIRATORY FAILURE"],
        ["CNS", "Excessive cholinergic\nstimulation in brain",
         "Anxiety → seizures → coma → death"],
    ],
    row_colors=[ROW_LO, ROW_LR, ROW_LP],
), sp(4)]

# DUMBELS big box
dumbels_data = [
    [Paragraph("D U M B E L S  —  Nerve Agent Mnemonic", ps("dh", 11, WHITE, bold=True, align=TA_CENTER))],
    [Table([
        [Paragraph("D", ps("dl", 18, BRIGHT_RED, bold=True, align=TA_CENTER)),
         Paragraph("U", ps("dl", 18, ORANGE,     bold=True, align=TA_CENTER)),
         Paragraph("M", ps("dl", 18, AMBER,      bold=True, align=TA_CENTER)),
         Paragraph("B", ps("dl", 18, MED_RED,    bold=True, align=TA_CENTER)),
         Paragraph("E", ps("dl", 18, PURPLE,     bold=True, align=TA_CENTER)),
         Paragraph("L", ps("dl", 18, TEAL,       bold=True, align=TA_CENTER)),
         Paragraph("S", ps("dl", 18, DARK_BLUE,  bold=True, align=TA_CENTER))],
        [Paragraph("Defecation\n/ Diarrhea", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Urination", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Miosis\n(pinpoint pupils)", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Bradycardia\nBronchospasm\nBronchorrhoea", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Emesis", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Lacrimation\n(tearing)", ps("dt", 7, align=TA_CENTER)),
         Paragraph("Salivation\nSweating\nSeizures", ps("dt", 7, align=TA_CENTER))],
    ], colWidths=[1.0*inch]*7)],
]
dumbels_tbl = Table(dumbels_data, colWidths=[7.4*inch])
dumbels_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (0,0), DARK_MAROON),
    ("BACKGROUND",    (0,1), (0,1), colors.HexColor("#FDF2F8")),
    ("BOX",           (0,0), (-1,-1), 1.0, DARK_MAROON),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
]))
story += [dumbels_tbl, sp(6)]

# Also SLUDGE
story += [mbox("Also remembered as SLUDGE:  Salivation · Lacrimation · Urination · Defecation · "
               "GI distress · Emesis\nPlus: PINPOINT PUPILS (miosis) + Bronchospasm/Bronchorrhoea + Bradycardia",
               bg=ROW_LO, border=ORANGE), sp(6)]

# Treatment table
story += [banner("NERVE AGENT TREATMENT — THREE DRUGS", MED_RED), sp(4)]
tx_data = [
    [Paragraph("DRUG", ps("th", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("MECHANISM", ps("th", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("TREATS", ps("th", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("NCLEX TIPS", ps("th", 9, WHITE, bold=True, align=TA_CENTER))],
    [Paragraph("ATROPINE\n(first-line)", ps("td", 9, MED_RED, bold=True)),
     Paragraph("Muscarinic receptor antagonist\n(blocks parasympathetic excess)", body_s),
     Paragraph("DUMBELS symptoms:\ndrying secretions, bronchospasm,\nbradycardia", body_s),
     Paragraph("Titrate to DRYING OF SECRETIONS — NOT pupil size or heart rate!\nLarge doses may be needed (up to 20 mg IV)\nAuto-injector (DuoDote) available", body_s)],
    [Paragraph("PRALIDOXIME\n(2-PAM)", ps("td2", 9, ORANGE, bold=True)),
     Paragraph("Reactivates acetylcholinesterase\nat NMJ (reverses enzyme inhibition)", body_s),
     Paragraph("Nicotinic effects:\nmuscle fasciculations,\nweakness, paralysis", body_s),
     Paragraph("Must give EARLY — loses effectiveness after\n'AGING' of enzyme (irreversible bond forms)\nGive with atropine", body_s)],
    [Paragraph("BENZODIAZEPINES\n(e.g., Diazepam)", ps("td3", 9, PURPLE, bold=True)),
     Paragraph("GABA-A agonist;\nreduces CNS excitability", body_s),
     Paragraph("CNS effects:\nseizures, agitation", body_s),
     Paragraph("Diazepam auto-injector also available\nfor mass casualty scenarios", body_s)],
]
tx_tbl = Table(tx_data, colWidths=[1.4*inch, 2.0*inch, 1.8*inch, 2.2*inch])
tx_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), DARK_MAROON),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("BACKGROUND",    (0,1), (-1,1), ROW_LR),
    ("BACKGROUND",    (0,2), (-1,2), ROW_LO),
    ("BACKGROUND",    (0,3), (-1,3), ROW_LP),
    ("FONTNAME",      (0,0), (-1,-1), "Helvetica"),
    ("FONTSIZE",      (0,0), (-1,-1), 7.5),
    ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.5, GREY_LINE),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story += [tx_tbl, sp(4)]

story += [
    wbox("⚠  DECONTAMINATION before hospital entry: Remove ALL clothing (eliminates ~80% of contamination). "
         "Flush skin with copious water. Protect healthcare workers — do NOT allow contaminated patients to enter facility."),
    sp(4),
    mbox("🔑 NCLEX Keys for Nerve Agents:\n"
         "  (1) PINPOINT PUPILS + DUMBELS = nerve agent / organophosphate poisoning\n"
         "  (2) Atropine titrated to DRYING OF SECRETIONS — not pupil size or heart rate\n"
         "  (3) Pralidoxime must be given EARLY before enzyme 'aging' makes it ineffective\n"
         "  (4) Decontaminate OUTSIDE hospital before patient enters"),
    sp(10),
]

story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# 2. VESICANTS
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("2.  VESICANTS / BLISTER AGENTS  —  Mustard Gas (HD), Lewisite", ORANGE), sp(4)]

story += [dtable(
    headers=["Agent", "Onset", "Smell", "Eyes", "Skin", "Respiratory", "Antidote"],
    col_widths=[0.8*inch, 1.0*inch, 0.9*inch, 1.2*inch, 1.3*inch, 1.2*inch, 1.0*inch],
    rows=[
        ["MUSTARD\nGAS (HD)", "DELAYED\n2–24 hours\n(key!)", "Garlic / mustard",
         "Delayed burning, conjunctivitis, corneal damage; photophobia",
         "Delayed erythema → blisters → necrosis; 2nd/3rd degree-like burns",
         "Airway damage, pseudo-membrane formation; pulmonary oedema",
         "NONE\n(supportive care only)"],
        ["LEWISITE (L)", "IMMEDIATE\npain on contact", "Geraniums",
         "Immediate severe pain, tearing, blepharospasm",
         "Immediate blister formation; severe pain",
         "Immediate airway pain and damage",
         "BAL\n(dimercaprol)\n= antidote"],
    ],
    row_colors=[ROW_LO, ROW_LR],
), sp(4),

wbox("⚠  MUSTARD GAS: Asymptomatic period after exposure → patient FEELS FINE initially → "
     "then dramatic deterioration 2–24 hours later. Do NOT be reassured by absence of symptoms at scene."),
sp(4),

dtable(
    headers=["Treatment Principle", "Details"],
    col_widths=[2.2*inch, 5.2*inch],
    rows=[
        ["Decontamination", "Remove clothing; flush with large amounts of water; soap and water for skin"],
        ["Eye irrigation", "Copious normal saline irrigation; do not patch eye if corneal involvement"],
        ["Wound/blister care", "Treat blisters like partial-thickness burns; do NOT rupture large blisters"],
        ["Respiratory support", "Bronchodilators, O2, mechanical ventilation if needed; airway management is priority"],
        ["BAL/dimercaprol", "Lewisite antidote only; given IM; also useful for arsenic/mercury/gold poisoning"],
        ["Precautions", "Contact precautions; full decontamination; PPE gown + gloves + eye protection"],
    ],
    row_colors=[ROW_LO, ROW_ALT, ROW_LO, ROW_ALT, ROW_LR, ROW_LT],
), sp(4),

mbox("🔑 NCLEX Keys for Vesicants:\n"
     "  (1) Mustard gas = DELAYED onset (2–24 hours) — no immediate symptoms\n"
     "  (2) Lewisite = IMMEDIATE severe pain on contact\n"
     "  (3) Lewisite antidote = BAL (British Anti-Lewisite / dimercaprol)\n"
     "  (4) Mustard gas = NO antidote (supportive care only)\n"
     "  (5) Garlic smell = mustard gas; Geranium smell = lewisite",
     bg=ROW_LO, border=ORANGE),
sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# 3. PULMONARY AGENTS
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("3.  PULMONARY / CHOKING AGENTS  —  Chlorine, Phosgene", YELLOW_DK), sp(4)]

story += [dtable(
    headers=["Agent", "Odour", "Onset", "Mechanism", "Symptoms", "Treatment"],
    col_widths=[0.9*inch, 1.2*inch, 1.0*inch, 1.5*inch, 1.5*inch, 1.3*inch],
    rows=[
        ["CHLORINE\n(Cl₂)", "Pool/bleach\n(greenish-yellow gas)",
         "Rapid", "Hydrolyses to HCl + hypochlorous acid → airway/lung damage",
         "Burning eyes & throat → cough → wheezing → dyspnoea → pulmonary oedema",
         "Fresh air; O2; bronchodilators; treat pulmonary oedema; no antidote"],
        ["PHOSGENE\n(CG)", "Freshly cut hay\nor newly mown grass",
         "DELAYED\n(4–24 hours)", "Carbonylates proteins in lung → destroys alveolar-capillary membrane → massive pulmonary oedema",
         "Initial mild irritation → latent 'well' period → delayed pulmonary oedema & respiratory failure",
         "Fresh air; O2; mechanical ventilation; corticosteroids; no antidote"],
    ],
    row_colors=[ROW_LY, ROW_LO],
), sp(4),

wbox("⚠  PHOSGENE: Patient may appear well after initial brief irritation — then develops DELAYED "
     "massive pulmonary oedema 4–24 hours later. Do NOT discharge or reassure early."),
sp(4),

mbox("🔑 NCLEX Keys for Pulmonary Agents:\n"
     "  (1) 'Pool/bleach' smell = CHLORINE\n"
     "  (2) 'Freshly cut hay/mown grass' smell = PHOSGENE (more dangerous)\n"
     "  (3) Phosgene = DELAYED pulmonary oedema — latent 'well' period is deceptive\n"
     "  (4) Chlorine = rapid onset of respiratory symptoms\n"
     "  (5) Both: Fresh air + high-flow O2 + respiratory support; NO specific antidote",
     bg=ROW_LY, border=YELLOW_DK),
sp(10)]

story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# 4. BLOOD AGENTS / CYANIDE
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("4.  BLOOD AGENTS  —  Hydrogen Cyanide (AC), Cyanogen Chloride (CK)", PURPLE), sp(4)]

story += [dtable(
    headers=["Feature", "Details"],
    col_widths=[1.8*inch, 5.6*inch],
    rows=[
        ["Mechanism", "Binds cytochrome oxidase (complex IV) → blocks aerobic respiration → "
                      "cells CANNOT use oxygen despite adequate delivery = HISTOTOXIC HYPOXIA"],
        ["Classic odour", "Bitter almonds (many people genetically unable to detect this smell — do not rely on it)"],
        ["KEY presentation", "Severe respiratory distress + rapid cardiovascular collapse — BUT NO CYANOSIS\n"
                             "(venous blood stays bright red because cells cannot extract O2)"],
        ["Symptoms onset", "Rapid: headache, dizziness, anxiety, confusion, tachycardia → seizures → cardiac arrest"],
        ["Smoke fires risk", "Burning synthetic materials (plastics, nylon, wool) release HCN → fire victims may have cyanide toxicity"],
        ["Precautions", "Standard precautions; decontaminate if liquid exposure; vapour dissipates quickly outdoors"],
    ],
    row_colors=[ROW_LR, ROW_LA, ROW_LP, ROW_LO, ROW_ALT, ROW_LT],
), sp(4)]

# Antidote table
story += [banner("CYANIDE ANTIDOTE REGIMEN", PURPLE), sp(4)]
antidote_data = [
    [Paragraph("ANTIDOTE", ps("ah", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("MECHANISM", ps("ah", 9, WHITE, bold=True, align=TA_CENTER)),
     Paragraph("NCLEX NOTES", ps("ah", 9, WHITE, bold=True, align=TA_CENTER))],
    [Paragraph("Hydroxocobalamin\n(Cyanokit)\n★ PREFERRED", ps("ap", 9, PURPLE, bold=True)),
     Paragraph("Binds cyanide directly forming cyanocobalamin (Vit B12); renal excretion", body_s),
     Paragraph("Turns urine/skin RED-ORANGE (warn patient/family)\nSafe in smoke inhalation (no methHb risk)\nFirst-line in prehospital and ED settings", body_s)],
    [Paragraph("Sodium Thiosulfate\n+\nSodium Nitrite", ps("as", 9, DARK_BLUE, bold=True)),
     Paragraph("Sodium nitrite → forms methHb which binds cyanide\nSodium thiosulfate → converts thiocyanate for renal excretion", body_s),
     Paragraph("Sodium nitrite causes methHb — AVOID in fire victims with CO poisoning\n(worsens O2 carrying capacity)\nHistorical regimen; now second-line", body_s)],
    [Paragraph("100% Oxygen\n(supportive)", ps("ao", 9, TEAL, bold=True)),
     Paragraph("Displaces CO (if present); supports cellular respiration", body_s),
     Paragraph("Always give high-flow O2 to all suspected cyanide/CO victims", body_s)],
]
ad_tbl = Table(antidote_data, colWidths=[1.6*inch, 2.8*inch, 3.0*inch])
ad_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), PURPLE),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
    ("BACKGROUND",    (0,1), (-1,1), ROW_LP),
    ("BACKGROUND",    (0,2), (-1,2), ROW_LB),
    ("BACKGROUND",    (0,3), (-1,3), ROW_LT),
    ("FONTNAME",      (0,0), (-1,-1), "Helvetica"),
    ("FONTSIZE",      (0,0), (-1,-1), 7.5),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.5, GREY_LINE),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story += [ad_tbl, sp(4),

wbox("⚠  CYANIDE TRAP: Patient has SEVERE RESPIRATORY DISTRESS but NO CYANOSIS "
     "(skin/lips may appear PINK/RED — venous blood is oxygenated but cells can't use it). "
     "Hydroxocobalamin turns urine orange-red — document this and educate family."),
sp(4),

mbox("🔑 NCLEX Keys for Blood Agents:\n"
     "  (1) Dyspnoea + tachycardia + seizures + NO CYANOSIS = cyanide poisoning\n"
     "  (2) 'Bitter almonds' smell (not always detectable)\n"
     "  (3) Preferred antidote = HYDROXOCOBALAMIN (Cyanokit) → red-orange urine\n"
     "  (4) Fire/smoke victims → think cyanide + carbon monoxide together\n"
     "  (5) Avoid sodium nitrite in fire victims with suspected CO poisoning",
     bg=ROW_LP, border=PURPLE),
sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# 5. DECONTAMINATION & PRIORITY NURSING ACTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [agent_header("5.  DECONTAMINATION & PRIORITY NURSING ACTIONS", DARK_BLUE), sp(4)]
story += [dtable(
    headers=["Step", "Action", "Rationale"],
    col_widths=[0.5*inch, 3.2*inch, 3.7*inch],
    rows=[
        ["1", "NURSE SAFETY FIRST — do NOT enter hot zone without appropriate PPE",
         "Healthcare worker protection prevents secondary casualties"],
        ["2", "DECONTAMINATE outside facility — remove ALL clothing, flush with water",
         "Clothing removal eliminates ~80% of contamination; prevents hospital contamination"],
        ["3", "Activate emergency response plan / incident command system",
         "Coordinates resources, isolates patients, protects facility and staff"],
        ["4", "Notify charge nurse, administration, public health, CDC/local health dept",
         "Legal reporting requirement; mobilises public health response"],
        ["5", "Triage using START system (Red/Yellow/Green/Black)",
         "Efficiently allocates limited resources in mass casualty events"],
        ["6", "Identify agent based on signs/symptoms + smell + circumstance",
         "Allows targeted antidote selection — wrong antidote can be fatal"],
        ["7", "Administer antidotes per protocol (atropine, pralidoxime, hydroxocobalamin, BAL)",
         "Early antidote administration dramatically improves outcomes"],
        ["8", "Document all exposures; report to poison control (1-800-222-1222) & state health dept",
         "Epidemiological tracking; medicolegal requirements"],
    ],
    row_colors=[ROW_LR, ROW_LO, ROW_LY, ROW_ALT, ROW_LR, ROW_LO, ROW_LY, ROW_LT],
), sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# MASTER QUICK REFERENCE
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("MASTER QUICK-REFERENCE — Chemical Warfare Agents", DARK_MAROON), sp(4)]
story += [dtable(
    headers=["Agent", "Class", "Key Presentation", "Smell", "Antidote / Treatment"],
    col_widths=[1.1*inch, 1.0*inch, 2.3*inch, 1.2*inch, 2.8*inch],
    rows=[
        ["Sarin / VX",        "Nerve",     "DUMBELS + pinpoint pupils + paralysis",
         "None / fruity", "Atropine + Pralidoxime (2-PAM) + Benzos"],
        ["Mustard Gas",       "Vesicant",  "DELAYED blisters (2–24 hrs); no pain initially",
         "Garlic",        "NONE — supportive care, wound care"],
        ["Lewisite",          "Vesicant",  "IMMEDIATE severe pain + blisters",
         "Geraniums",     "BAL (dimercaprol)"],
        ["Chlorine",          "Pulmonary", "Rapid respiratory irritation; cough, wheeze",
         "Pool/bleach",   "O2, bronchodilators, no antidote"],
        ["Phosgene",          "Pulmonary", "DELAYED pulmonary oedema (4–24 hrs); well initially",
         "Fresh-cut hay", "O2, mechanical ventilation, no antidote"],
        ["Hydrogen Cyanide",  "Blood",     "Dyspnoea + seizures + NO CYANOSIS",
         "Bitter almonds","Hydroxocobalamin (Cyanokit) — first-line"],
    ],
    row_colors=[ROW_LR, ROW_LO, ROW_LA, ROW_LY, ROW_LO, ROW_LP],
), sp(10)]

# ══════════════════════════════════════════════════════════════════════════════
# PRACTICE QUESTIONS
# ══════════════════════════════════════════════════════════════════════════════
story += [banner("NCLEX-STYLE PRACTICE QUESTIONS — Chemical Warfare", MED_RED), sp(6)]

qs = [
    ("Q1.  Multiple casualties arrive after a subway attack. Victims have pinpoint pupils, excessive salivation, "
     "and muscle fasciculations. Which drug should the nurse prepare to administer FIRST?",
     "A. Hydroxocobalamin\nB. Atropine\nC. Sodium thiosulfate\nD. Dimercaprol (BAL)",
     "✔  Answer: B — DUMBELS signs (miosis, salivation, fasciculations) = nerve agent (organophosphate). "
     "Atropine is the first-line muscarinic antidote. Pralidoxime follows."),
    ("Q2.  A nurse is caring for a victim of a chemical attack who is alert and ambulatory. "
     "The patient states he felt a brief eye sting 20 minutes ago at the scene but now feels fine. "
     "The patient's clothing has a garlic-like odour. What is the nurse's priority action?",
     "A. Reassure the patient and discharge home\n"
     "B. Perform complete decontamination, remove clothing, and observe closely for delayed symptoms\n"
     "C. Administer BAL (dimercaprol) immediately\nD. Apply antibiotic eye ointment",
     "✔  Answer: B — Garlic odour = mustard gas, which has DELAYED onset (2–24 hours). "
     "Patient feeling fine initially is characteristic and deceptive. "
     "Decontaminate thoroughly and observe for delayed blistering and systemic effects. "
     "BAL is for Lewisite, not mustard gas."),
    ("Q3.  A victim of a suspected cyanide attack has rapid respiratory distress and seizures, "
     "but the nurse notes the patient's lips and skin appear PINK rather than blue. "
     "Which antidote does the nurse anticipate administering?",
     "A. Atropine\nB. Naloxone\nC. Hydroxocobalamin (Cyanokit)\nD. Flumazenil",
     "✔  Answer: C — Respiratory distress WITHOUT cyanosis = cyanide poisoning (histotoxic hypoxia). "
     "Skin may appear pink/red because cells cannot extract O2 from blood. "
     "Hydroxocobalamin (Cyanokit) is the preferred antidote."),
    ("Q4.  The nurse is teaching staff about the odour associated with phosgene. "
     "Which statement by a staff member indicates correct understanding?",
     "A. 'Phosgene smells like bleach or pool chemicals.'\n"
     "B. 'Phosgene has no odour.'\n"
     "C. 'Phosgene smells like freshly cut hay or newly mown grass.'\n"
     "D. 'Phosgene smells like bitter almonds.'",
     "✔  Answer: C — Phosgene smells like freshly cut hay or mown grass. "
     "Chlorine smells like bleach/pool. Cyanide smells like bitter almonds. "
     "Mustard gas smells like garlic."),
    ("Q5.  A nurse is treating a nerve agent victim and has administered atropine. "
     "Which assessment finding indicates that a sufficient dose of atropine has been given?",
     "A. Pupil dilation to normal size\nB. Heart rate increase to > 100 bpm\n"
     "C. Drying of bronchial secretions\nD. Resolution of muscle fasciculations",
     "✔  Answer: C — Atropine is titrated to DRYING OF SECRETIONS (bronchorrhoea). "
     "Pupil size and heart rate are POOR indicators of adequate atropinisation in "
     "organophosphate/nerve agent exposure per Tintinalli's Emergency Medicine."),
]

for q, choices, ans in qs:
    blk = Table([
        [Paragraph(q, body_s)],
        [Paragraph(choices.replace("\n", "   "), ps("qc", 7.8, italic=True))],
        [Paragraph(ans, ps("qa", 8, DARK_MAROON, bold=True))],
    ], colWidths=[7.4*inch])
    blk.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (0,0), colors.HexColor("#FEF5E7")),
        ("BACKGROUND", (0,1), (0,1), WHITE),
        ("BACKGROUND", (0,2), (0,2), ROW_LO),
        ("BOX",        (0,0), (-1,-1), 0.7, ORANGE),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ]))
    story += [blk, sp(6)]

# FOOTER
story += [
    HRFlowable(width="100%", thickness=0.5, color=GREY_LINE), sp(4),
    Paragraph("Sources: Tintinalli's Emergency Medicine (Nerve Agents chapter) · Ganong's Medical Physiology · "
              "Goldman-Cecil Medicine · CDC Emergency Preparedness · NCSBN NCLEX-RN Test Plan", foot_s),
]

doc.build(story)
print(f"Chemical Warfare PDF: {OUTPUT}")
Running Command

python /home/daytona/workspace/nclex-warfare/bio_warfare.py 2>&1 && python /home…

Editing File

~/nclex-warfare/chem_warfare.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/nclex-warfare/chem_warfare.py'
Running Command

python /home/daytona/workspace/nclex-warfare/chem_warfare.py 2>&1

Running Command

ls -lh /home/daytona/workspace/nclex-warfare/*.pdf

Both PDFs are ready! Download them here:

📄 PDF 1 — Biological Warfare

NCLEX Biological Warfare

PDF Document · PDF

Color theme: Deep green | 4 pages
SectionContent
CDC Category A/B/C overviewColor-coded 3-column category table (red/amber/olive)
Anthrax3 forms (cutaneous/inhalation/GI) with color-coded rows + black eschar key
Smallpox vs. ChickenpoxSide-by-side purple vs. teal comparison table
PlagueBubonic/Pneumonic/Septicaemic table - pneumonic row highlighted red
BotulismDescending paralysis, antitoxin details, "locked-in" patient alert
Tularemia4 forms with transmissions
Viral Hemorrhagic FeversEbola/Marburg/Lassa, full PPE requirements
Master Quick ReferenceColor-coded summary of all 8 agents
4 Practice QuestionsWith full rationales

📄 PDF 2 — Chemical Warfare

NCLEX Chemical Warfare

PDF Document · PDF

Color theme: Dark red/maroon | 4 pages
SectionContent
4-Class overviewRed/orange/amber/purple category boxes with mechanisms
Nerve AgentsDUMBELS letter-by-letter visual box; 3-receptor table
Treatment TableAtropine + Pralidoxime + Benzodiazepines with titration tips
VesicantsMustard gas vs. Lewisite side-by-side; delayed vs. immediate onset
Pulmonary AgentsChlorine vs. Phosgene; odour identification
Blood Agents / Cyanide"No cyanosis" trap; hydroxocobalamin vs. sodium thiosulfate
Decontamination StepsPriority nursing action sequence
Master Quick ReferenceAll 6 agents with smell, key sign, and antidote
5 Practice QuestionsIncluding atropine titration Q and cyanide "pink skin" Q

Wow that so good

Thank you! Glad it's helpful for your NCLEX prep! 😊
Here's a quick recap of everything we've built for you today:
PDFFocus
📋 NCLEX Lab ValuesAll reference intervals, critical values, mnemonics
🏥 Safe EnvironmentPrecautions, restraints, fire safety, triage, PPE
☣️ Biological WarfareAnthrax, Smallpox, Plague, Botulism, VHF + practice Qs
⚗️ Chemical WarfareNerve agents (DUMBELS), Vesicants, Cyanide + practice Qs
A few tips for using these:
  • The quick-reference cheat sheets on the last pages are great for last-minute review the morning of the exam
  • The practice questions at the end of each PDF are written in NCLEX style - try answering before reading the rationale
  • For the warfare agents, focus especially on: which precaution type, what the antidote is, and the one "trap" sign (e.g. no cyanosis with cyanide, delayed onset with mustard gas, antitoxin doesn't REVERSE botulism)
If you want more PDFs on other NCLEX topics - pharmacology, maternal-newborn, pediatrics, psych, cardiac, or any other area - just ask! 📚

But one thing nxt time uh can also use peregraph type also because in table mathode some collomes are mearge

Absolutely understood! Great feedback. 👍
Next time I'll use a mixed layout approach:
  • Paragraphs for detailed explanations, mechanisms, and descriptions where text flows better
  • Tables only for true comparisons (like side-by-side agent comparisons)
  • Bullet-point blocks for lists of signs/symptoms/nursing actions
  • Coloured heading boxes as section dividers
So instead of squeezing everything into narrow table columns (which causes merging/overflow issues), the layout will look more like a real textbook or study guide - with paragraph sections, then a clean table only where it genuinely helps.
For example, instead of:
AgentMechanismSymptomsTreatmentPrecautionsAntidote
(all squished)...............
It would be:
🔴 Anthrax (Bacillus anthracis) Anthrax is caused by... (paragraph) Signs & Symptoms: Cutaneous form presents with a painless papule... Treatment: Ciprofloxacin or doxycycline... (then a small clean 2-col table for quick reference)
This makes the PDF much more readable and printable. I'll apply this style to all future study guides. Just ask for any topic and I'll build it with the improved format! 📄✨

Sure can uh show me a demo pdf on that format

Writing File

~/nclex-warfare/demo_format.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether, ListFlowable, ListItem
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY

OUTPUT = "/home/daytona/workspace/nclex-warfare/DEMO_NewFormat.pdf"

# ── Palette ───────────────────────────────────────────────────────────────────
NAVY        = colors.HexColor("#1B3A6B")
TEAL        = colors.HexColor("#1A7F8E")
RED_DARK    = colors.HexColor("#922B21")
RED_MED     = colors.HexColor("#C0392B")
GREEN_DK    = colors.HexColor("#145A32")
GREEN_MD    = colors.HexColor("#1E8449")
ORANGE      = colors.HexColor("#E67E22")
PURPLE      = colors.HexColor("#6C3483")
GREY_BG     = colors.HexColor("#F4F6F7")
GREY_LINE   = colors.HexColor("#BDC3C7")
GREY_TXT    = colors.HexColor("#555555")
LIGHT_TEAL  = colors.HexColor("#D1F2EB")
LIGHT_RED   = colors.HexColor("#FADBD8")
LIGHT_AMB   = colors.HexColor("#FDEBD0")
LIGHT_BLUE  = colors.HexColor("#D6EAF8")
LIGHT_PUR   = colors.HexColor("#E8DAEF")
LIGHT_GRN   = colors.HexColor("#D5F5E3")
WHITE       = colors.white
BLACK       = colors.black

# ── Styles ────────────────────────────────────────────────────────────────────
def P(name, size=9, color=BLACK, bold=False, italic=False,
      align=TA_LEFT, leading=None, left=0, right=0, space_before=0, space_after=0):
    fn = "Helvetica" + ("-BoldOblique" if bold and italic else
                        "-Bold" if bold else "-Oblique" if italic else "")
    return ParagraphStyle(name, fontSize=size, fontName=fn, textColor=color,
                          alignment=align, leading=leading or size * 1.5,
                          leftIndent=left, rightIndent=right,
                          spaceBefore=space_before, spaceAfter=space_after)

title_s    = P("T",   22, WHITE,    bold=True,  align=TA_CENTER)
subtitle_s = P("St",  10, WHITE,    italic=True, align=TA_CENTER)
sec_s      = P("Se",  13, WHITE,    bold=True,  left=8)
agent_s    = P("Ag",  11, WHITE,    bold=True,  left=8)
h3_s       = P("H3",  10, NAVY,     bold=True,  space_before=4)
body_s     = P("Bo",   9, BLACK,    leading=14, align=TA_JUSTIFY)
body_b     = P("BB",   9, BLACK,    bold=True)
bullet_s   = P("Bu",   9, BLACK,    leading=14, left=14)
label_s    = P("La",   8.5, GREY_TXT, bold=True)
key_s      = P("Ke",   9, RED_DARK, bold=True,  left=6)
mnem_s     = P("Mn",   9, GREEN_DK, bold=True,  left=6)
tip_s      = P("Ti",   8.5, NAVY,   italic=True, left=6)
foot_s     = P("Fo",   6.8, GREY_TXT, align=TA_CENTER)
tag_s      = P("Ta",   8, WHITE,    bold=True,  align=TA_CENTER)

def sp(n=6): return Spacer(1, n)

# ── Layout helpers ─────────────────────────────────────────────────────────────

def full_banner(line1, line2, bg=NAVY):
    """Full-width title banner."""
    t = Table([
        [Paragraph(line1, title_s)],
        [Paragraph(line2, subtitle_s)],
    ], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("TOPPADDING",    (0,0), (-1,-1), 12),
        ("BOTTOMPADDING", (0,0), (-1,-1), 12),
    ]))
    return t

def section_banner(text, bg=TEAL):
    """Section divider banner."""
    t = Table([[Paragraph(text, sec_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ]))
    return t

def agent_banner(number, name, category, bg=RED_DARK):
    """Per-agent coloured header with category tag."""
    inner = Table([[
        Paragraph(f"{number}.  {name}", agent_s),
        Paragraph(category, tag_s),
    ]], colWidths=[5.8*inch, 1.6*inch])
    inner.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BACKGROUND",    (1,0), (1,0), colors.HexColor(
            hex(max(0, int(bg.hexval(), 16) - 0x202020))[2:].zfill(6)
            if False else bg.hexval())),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (0,0), 10),
        ("ALIGN",         (1,0), (1,0), "CENTER"),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]))
    return inner

def info_box(text_para, bg=LIGHT_BLUE, border=TEAL):
    """Coloured paragraph info box."""
    t = Table([[text_para]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.8, border),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
        ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ]))
    return t

def two_box(left_title, left_items, right_title, right_items,
            left_bg=LIGHT_BLUE, right_bg=LIGHT_GRN,
            left_border=TEAL, right_border=GREEN_MD):
    """Two side-by-side coloured boxes."""
    def make_box(title, items, bg, border):
        rows = [[Paragraph(title, h3_s)]]
        for item in items:
            rows.append([Paragraph(f"• {item}", bullet_s)])
        t = Table(rows, colWidths=[3.55*inch])
        t.setStyle(TableStyle([
            ("BACKGROUND",    (0,0), (-1,-1), bg),
            ("BOX",           (0,0), (-1,-1), 0.7, border),
            ("TOPPADDING",    (0,0), (-1,-1), 4),
            ("BOTTOMPADDING", (0,0), (-1,-1), 4),
            ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ]))
        return t
    outer = Table([[
        make_box(left_title,  left_items,  left_bg,  left_border),
        make_box(right_title, right_items, right_bg, right_border),
    ]], colWidths=[3.6*inch, 3.6*inch])
    outer.setStyle(TableStyle([
        ("LEFTPADDING",  (0,0), (-1,-1), 0),
        ("RIGHTPADDING", (0,0), (-1,-1), 2),
        ("TOPPADDING",   (0,0), (-1,-1), 0),
        ("VALIGN",       (0,0), (-1,-1), "TOP"),
    ]))
    return outer

def quick_ref_table(headers, rows, col_widths, header_bg=NAVY):
    """Compact quick-reference table — used sparingly."""
    data = [headers] + rows
    t = Table(data, colWidths=col_widths, repeatRows=1)
    cmds = [
        ("BACKGROUND",    (0,0), (-1,0), header_bg),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 8),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 6),
        ("RIGHTPADDING",  (0,0), (-1,-1), 6),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    for i in range(2, len(rows)+1, 2):
        cmds.append(("BACKGROUND", (0,i), (-1,i), GREY_BG))
    t.setStyle(TableStyle(cmds))
    return t

def label_para(label, text):
    """Inline bold label + paragraph text as a single 2-col row."""
    t = Table([[Paragraph(label, label_s), Paragraph(text, body_s)]],
              colWidths=[1.3*inch, 6.1*inch])
    t.setStyle(TableStyle([
        ("TOPPADDING",    (0,0), (-1,-1), 2),
        ("BOTTOMPADDING", (0,0), (-1,-1), 2),
        ("LEFTPADDING",   (0,0), (-1,-1), 0),
        ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ]))
    return t

# ══════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ══════════════════════════════════════════════════════════════════════════════
story = []
doc = SimpleDocTemplate(OUTPUT, pagesize=letter,
    leftMargin=0.6*inch, rightMargin=0.6*inch,
    topMargin=0.6*inch, bottomMargin=0.6*inch,
    title="NCLEX Demo – New Mixed Format", author="Orris Study Aid")

# ── TITLE ─────────────────────────────────────────────────────────────────────
story += [
    full_banner("☣  Biological Warfare Agents", "NCLEX Demo  ·  New Mixed Format  ·  Paragraph + Bullet + Table"),
    sp(8),
]

# ── INTRO PARAGRAPH ───────────────────────────────────────────────────────────
story += [
    section_banner("Overview", NAVY),
    sp(6),
    Paragraph(
        "Biological warfare agents are living organisms or toxins deliberately released to cause "
        "disease in humans, animals, or plants. The CDC classifies them into three categories "
        "based on their potential for mass casualties and ease of dissemination. Nurses must "
        "recognise these agents quickly, apply the correct isolation precautions, and initiate "
        "appropriate treatment without delay. This guide covers the four highest-yield Category A "
        "agents tested on the NCLEX.",
        body_s),
    sp(6),
    info_box(Paragraph(
        "📌  NGN NCLEX Note: Questions will ask you to RECOGNISE the agent from clinical clues, "
        "SELECT the correct precaution type, and PRIORITISE the nursing action. "
        "Focus on the 'one key differentiator' for each agent.",
        tip_s), bg=LIGHT_BLUE, border=TEAL),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 1 — ANTHRAX
# ══════════════════════════════════════════════════════════════════════════════
story += [
    KeepTogether([
        agent_banner("1", "ANTHRAX  (Bacillus anthracis)", "Category A", RED_DARK),
        sp(6),
    ]),
]

# Mechanism paragraph
story += [
    Paragraph("What is it?", h3_s),
    sp(3),
    Paragraph(
        "Anthrax is caused by <i>Bacillus anthracis</i>, a spore-forming gram-positive bacillus. "
        "It is considered the most likely agent to be used in a bioterrorism attack because "
        "its spores are highly stable, can be aerosolized, and inhalation anthrax carries a "
        "mortality rate greater than 80% if untreated. The 2001 U.S. mail attacks produced "
        "11 inhalation and 11 cutaneous cases, with 5 inhalation deaths.",
        body_s),
    sp(8),
]

# Three forms side-by-side bullets + paragraph
story += [
    Paragraph("Clinical Forms", h3_s),
    sp(4),
    two_box(
        "Cutaneous (95% of natural cases)",
        ["Painless papule at site of skin contact",
         "Progresses to vesicles, then ULCER",
         "BLACK, painless ESCHAR forms (key finding)",
         "Surrounding oedema and lymphadenopathy",
         "Incubation: < 1 week"],
        "Inhalation (Bioweapon form ★)",
        ["Flu-like prodrome (fever, malaise, myalgia)",
         "Brief apparent improvement, then sudden collapse",
         "WIDENED MEDIASTINUM on chest X-ray",
         "Haemorrhagic meningitis, septic shock",
         "Incubation: 2 days – 6 WEEKS (spore dormancy)"],
        left_bg=LIGHT_AMB, right_bg=LIGHT_RED,
        left_border=ORANGE, right_border=RED_MED,
    ),
    sp(6),
    Paragraph(
        "Gastrointestinal anthrax, while rare, can occur after ingesting infected animal tissue. "
        "It presents with ulcers, nausea, bloody diarrhoea, and ascites. All forms can "
        "progress to systemic anthrax with bacteraemia and meningitis if untreated.",
        body_s),
    sp(8),
]

# Precautions & Treatment as labelled paragraphs
story += [
    Paragraph("Precautions & Treatment", h3_s),
    sp(4),
    label_para("Precautions:", "Standard precautions for all forms. Person-to-person transmission is extremely rare. Cutaneous discharge is potentially infectious — wear gloves when handling wounds."),
    sp(3),
    label_para("Treatment:", "Ciprofloxacin or Doxycycline are first-line for all forms. Inhalation anthrax requires IV therapy initially, then oral step-down for a total of 60 days to cover spore dormancy."),
    sp(3),
    label_para("Prophylaxis:", "Post-exposure prophylaxis: Ciprofloxacin or Doxycycline × 60 days. Anthrax vaccine (AVA) is available for high-risk populations (military, laboratory workers)."),
    sp(6),
]

# NCLEX key box
story += [
    info_box(Paragraph(
        "🔑  NCLEX Key Points for Anthrax\n"
        "  •  PAINLESS black eschar = cutaneous anthrax (painless differentiates from other skin infections)\n"
        "  •  WIDENED MEDIASTINUM on CXR = inhalation anthrax until proven otherwise\n"
        "  •  Inhalation incubation can be up to 6 WEEKS — patient may seem well initially\n"
        "  •  Standard precautions only (no airborne, no droplet needed)",
        mnem_s), bg=LIGHT_GRN, border=GREEN_MD),
    sp(12),
]

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 2 — SMALLPOX
# ══════════════════════════════════════════════════════════════════════════════
story += [
    KeepTogether([
        agent_banner("2", "SMALLPOX  (Variola virus)", "Category A", PURPLE),
        sp(6),
    ]),
    Paragraph("What is it?", h3_s),
    sp(3),
    Paragraph(
        "Smallpox is caused by Variola virus and was declared eradicated in 1980. "
        "Because routine vaccination stopped around 1980, most people under 45 have "
        "no immunity, making it an extremely dangerous potential bioweapon. "
        "The only known remaining stocks are held at the CDC (Atlanta, USA) and "
        "VECTOR Institute (Russia). Smallpox is HIGHLY contagious via the airborne route "
        "and close contact.",
        body_s),
    sp(8),
    Paragraph("Key Clinical Features", h3_s),
    sp(4),
    Paragraph(
        "The prodrome begins with high fever, severe headache, and backache "
        "<b>2–4 days BEFORE the rash appears</b>. This is a critical differentiator from chickenpox, "
        "where fever and rash appear together. The rash is centrifugal — it begins on the "
        "face and spreads outward to the palms and soles. All lesions are in the "
        "<b>same stage of development simultaneously</b> (deep, firm, umbilicated pustules). "
        "Contrast this with chickenpox, where lesions in multiple stages (papules, vesicles, "
        "crusts) appear at the same time.",
        body_s),
    sp(6),
]

# Comparison quick-ref table (used because it IS a true comparison)
story += [
    Paragraph("Smallpox vs. Chickenpox — Quick Comparison", h3_s),
    sp(4),
    quick_ref_table(
        headers=["Feature", "SMALLPOX", "CHICKENPOX (Varicella)"],
        col_widths=[2.0*inch, 2.7*inch, 2.7*inch],
        rows=[
            ["Lesion stages",     "ALL same stage (synchronous)",    "Multiple stages simultaneously"],
            ["Rash distribution", "Centrifugal: face → palms/soles", "Centripetal: trunk > extremities"],
            ["Lesion depth",      "Deep, firm, hard, umbilicated",   "Superficial, soft vesicles"],
            ["Prodrome fever",    "2–4 days BEFORE rash",            "Mild fever WITH rash"],
            ["Isolation",         "Airborne + Contact precautions",  "Airborne precautions"],
        ],
        header_bg=PURPLE,
    ),
    sp(6),
]

story += [
    label_para("Precautions:", "AIRBORNE + CONTACT precautions. N-95 respirator, negative-pressure room, gown and gloves. Door must remain closed at all times. Patient wears surgical mask during transport."),
    sp(3),
    label_para("Treatment:", "No proven specific antiviral. Vaccinia vaccine (ACAM2000) given within 4 days of exposure can prevent or significantly reduce disease. Tecovirimat (TPOXX) is approved for treatment of smallpox."),
    sp(6),
    info_box(Paragraph(
        "🔑  NCLEX Key Points for Smallpox\n"
        "  •  ALL lesions = SAME stage (the single most testable fact)\n"
        "  •  Centrifugal rash: face, palms, soles first\n"
        "  •  Requires BOTH Airborne AND Contact precautions (N-95 + negative-pressure room)\n"
        "  •  Vaccinate contacts within 4 DAYS of exposure for prevention",
        mnem_s), bg=LIGHT_PUR, border=PURPLE),
    sp(12),
]

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 3 — BOTULISM (briefer — shows paragraph-only style)
# ══════════════════════════════════════════════════════════════════════════════
story += [
    KeepTogether([
        agent_banner("3", "BOTULISM  (Clostridium botulinum toxin)", "Category A", NAVY),
        sp(6),
    ]),
    Paragraph("Mechanism & Presentation", h3_s),
    sp(3),
    Paragraph(
        "Botulinum toxin blocks acetylcholine release at the neuromuscular junction, "
        "producing a characteristic <b>descending flaccid paralysis</b>. Symptoms begin with the "
        "cranial nerves — diplopia, dysarthria, dysphagia (the '3 Ds') — then descend to the "
        "limbs and finally the respiratory muscles. The patient remains <b>afebrile, alert, "
        "and without sensory loss</b>. This is a critical NCLEX distinguisher: the patient "
        "appears 'locked in' but is fully conscious. All caregivers must be reminded of this — "
        "explain every procedure and avoid insensitive comments at the bedside.",
        body_s),
    sp(6),
    Paragraph("Nursing Priorities", h3_s),
    sp(4),
]

# Bullet list using paragraph style
for bullet in [
    "Monitor respiratory status continuously — rate, O2 saturation, vital capacity. Respiratory failure from descending paralysis is the immediate life threat.",
    "Administer Botulinum Antitoxin (BAT) as early as possible. It stops progression but does NOT reverse paralysis already present.",
    "Mechanical ventilation may be required for weeks to months — prepare patient and family for prolonged ICU stay.",
    "Standard precautions only — no person-to-person transmission.",
    "Infant botulism: HONEY is strictly contraindicated in infants under 12 months. Use BabyBIG (human-derived immunoglobulin) for treatment.",
]:
    story += [Paragraph(f"• {bullet}", bullet_s), sp(2)]

story += [
    sp(4),
    info_box(Paragraph(
        "⚠  ANTITOXIN ALERT: Botulinum antitoxin STOPS progression of paralysis — "
        "it does NOT reverse paralysis that has already occurred. Give as early as possible. "
        "The patient is CONSCIOUS even when completely paralysed — always communicate and explain.",
        key_s), bg=LIGHT_RED, border=RED_MED),
    sp(12),
]

# ══════════════════════════════════════════════════════════════════════════════
# MASTER QUICK REFERENCE (compact table — appropriate use)
# ══════════════════════════════════════════════════════════════════════════════
story += [
    section_banner("Master Quick-Reference — All Agents at a Glance", GREEN_DK),
    sp(6),
    quick_ref_table(
        headers=["Agent", "One Key Sign", "Precaution", "Antidote / Treatment"],
        col_widths=[1.3*inch, 2.4*inch, 1.5*inch, 2.2*inch],
        rows=[
            ["Anthrax\n(cutaneous)",  "Painless BLACK ESCHAR",          "Standard",         "Ciprofloxacin / Doxycycline"],
            ["Anthrax\n(inhalation)", "Widened mediastinum on CXR",     "Standard",         "Ciprofloxacin IV then oral × 60 days"],
            ["Smallpox",             "ALL lesions same stage; centrifugal","Airborne+Contact","Vaccinia vaccine within 4 days; Tecovirimat"],
            ["Plague (pneumonic)",   "Bloody frothy sputum",            "DROPLET",          "Streptomycin / Doxycycline"],
            ["Botulism",             "Descending paralysis; alert/afebrile","Standard",      "BAT antitoxin (stops progression only)"],
            ["Tularemia",            "Ulcer + lymphadenopathy",         "Standard",         "Streptomycin / Gentamicin"],
        ],
        header_bg=GREEN_DK,
    ),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# PRACTICE QUESTION — shows mixed format
# ══════════════════════════════════════════════════════════════════════════════
story += [
    section_banner("Sample NCLEX-Style Question", TEAL),
    sp(6),
]

q_block = Table([
    [Paragraph(
        "Q.  A patient presents with a rash where all skin lesions appear to be in the same stage "
        "of development, are firm and deep, and are concentrated on the face and palms. "
        "The patient reports a high fever that began 3 days before the rash. "
        "Which action should the nurse take FIRST?",
        body_s)],
    [sp(4)],
    [Paragraph(
        "A.  Apply contact precautions and a gown and gloves only\n"
        "B.  Place the patient in a negative-pressure room and apply airborne and contact precautions\n"
        "C.  Administer antiviral medication immediately\n"
        "D.  Place the patient in a standard room and notify the charge nurse",
        P("qc", 8.5, italic=True, left=10))],
    [sp(4)],
    [Paragraph(
        "✔  Answer: B\n"
        "Rationale: The presentation describes smallpox — synchronous same-stage lesions, "
        "centrifugal distribution (face and palms), and prodromal fever 2–4 days before the rash. "
        "Smallpox requires BOTH airborne precautions (N-95, negative-pressure room) AND contact "
        "precautions (gown and gloves). Option A is incomplete. Option C is not the priority "
        "action before isolation is established.",
        P("qa", 9, GREEN_DK, bold=False, left=6))],
], colWidths=[7.4*inch])
q_block.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), LIGHT_BLUE),
    ("BACKGROUND", (0,2), (0,2), WHITE),
    ("BACKGROUND", (0,4), (0,4), LIGHT_GRN),
    ("BOX",        (0,0), (-1,-1), 0.8, TEAL),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
]))
story += [q_block, sp(8)]

# ── FORMAT NOTE BOX ────────────────────────────────────────────────────────────
story += [
    info_box(Paragraph(
        "📐  About This Format\n"
        "This demo uses a MIXED LAYOUT: paragraphs for explanations and mechanisms, "
        "bullet lists for signs/symptoms and nursing actions, labelled paragraph rows for "
        "treatment details, and compact tables ONLY where genuine side-by-side comparison "
        "adds value. This prevents column merging and overflow issues and makes content "
        "more readable when printed.",
        tip_s), bg=LIGHT_BLUE, border=TEAL),
    sp(6),
    HRFlowable(width="100%", thickness=0.5, color=GREY_LINE),
    sp(4),
    Paragraph(
        "Sources: Harrison's Principles of Internal Medicine 22E · Red Book 2021 · "
        "Goldman-Cecil Medicine · CDC Bioterrorism Agent List · NCSBN NCLEX-RN Test Plan",
        foot_s),
]

doc.build(story)
print(f"Demo PDF written to: {OUTPUT}")
Running Command

python /home/daytona/workspace/nclex-warfare/demo_format.py 2>&1

Running Command

ls -lh /home/daytona/workspace/nclex-warfare/DEMO_NewFormat.pdf

Here's the demo PDF:

DEMO NewFormat

PDF Document · PDF

Here's exactly what the new format uses and when:
ElementUsed For
Coloured agent bannerEach agent gets its own header bar with category tag
Paragraph blocksMechanism, background, clinical description — flows naturally, no squishing
Labelled rows (bold label + paragraph)Precautions / Treatment / Prophylaxis — clean, readable
Bullet listsSigns & symptoms, nursing priorities — scannable
Two-box side layoutOnly for true comparisons (e.g. cutaneous vs. inhalation anthrax)
TableONLY the Smallpox vs. Chickenpox comparison and the final quick-reference — where a table genuinely helps
Coloured info boxesNCLEX key points, warnings, mnemonics — stand out visually
The result is much closer to how a real textbook or Saunders NCLEX review guide reads. Let me know if you'd like any tweaks to the style before I use this format for future topics! 📄

Tht was also good but use littel highlighted points and also nclex imp topics and mamory tricks

Writing File

~/nclex-warfare/demo_v2.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY

OUTPUT = "/home/daytona/workspace/nclex-warfare/DEMO_NewFormat_v2.pdf"

# ── Palette ───────────────────────────────────────────────────────────────────
NAVY        = colors.HexColor("#1B3A6B")
TEAL        = colors.HexColor("#1A7F8E")
RED_DARK    = colors.HexColor("#922B21")
RED_MED     = colors.HexColor("#C0392B")
GREEN_DK    = colors.HexColor("#145A32")
GREEN_MD    = colors.HexColor("#1E8449")
ORANGE      = colors.HexColor("#E67E22")
PURPLE      = colors.HexColor("#6C3483")
GOLD        = colors.HexColor("#B7950B")
PINK        = colors.HexColor("#C0185A")
BROWN       = colors.HexColor("#5D4037")
GREY_LINE   = colors.HexColor("#BDC3C7")
GREY_TXT    = colors.HexColor("#666666")
WHITE       = colors.white
BLACK       = colors.black

# Light fills
L_RED   = colors.HexColor("#FADBD8")
L_ORG   = colors.HexColor("#FDEBD0")
L_YEL   = colors.HexColor("#FEF9E7")
L_GRN   = colors.HexColor("#D5F5E3")
L_TEAL  = colors.HexColor("#D1F2EB")
L_BLUE  = colors.HexColor("#D6EAF8")
L_PUR   = colors.HexColor("#E8DAEF")
L_PINK  = colors.HexColor("#FDEDEC")
L_GREY  = colors.HexColor("#F4F6F7")
L_GOLD  = colors.HexColor("#FEF5E7")

# ── Styles ────────────────────────────────────────────────────────────────────
def S(name, size=9, color=BLACK, bold=False, italic=False,
      align=TA_LEFT, leading=None, left=0, right=0, sb=0, sa=0):
    fn = ("Helvetica-BoldOblique" if bold and italic else
          "Helvetica-Bold"        if bold else
          "Helvetica-Oblique"     if italic else "Helvetica")
    return ParagraphStyle(name, fontSize=size, fontName=fn, textColor=color,
                          alignment=align, leading=leading or size*1.55,
                          leftIndent=left, rightIndent=right,
                          spaceBefore=sb, spaceAfter=sa)

title_s  = S("T",  22, WHITE, bold=True,  align=TA_CENTER)
sub_s    = S("Su", 10, WHITE, italic=True, align=TA_CENTER)
sec_s    = S("Se", 13, WHITE, bold=True,  left=8)
agent_s  = S("Ag", 11, WHITE, bold=True,  left=10)
tag_s    = S("Tg",  8, WHITE, bold=True,  align=TA_CENTER)
h3_s     = S("H3", 10, NAVY,  bold=True,  sb=2)
body_s   = S("Bo",  9, BLACK, leading=14, align=TA_JUSTIFY)
bullet_s = S("Bu",  9, BLACK, leading=14, left=14)
label_s  = S("La",  8.5, GREY_TXT, bold=True)

# Highlighted text styles
hl_red   = S("HR",  9, RED_DARK,  bold=True, left=6)
hl_green = S("HG",  9, GREEN_DK,  bold=True, left=6)
hl_navy  = S("HN",  9, NAVY,      bold=True, left=6)
hl_gold  = S("HGo", 9, GOLD,      bold=True, left=6)
hl_tip   = S("HT",  8.5, NAVY, italic=True, left=6)
hl_trick = S("HTr", 9, PURPLE, bold=True, left=6)
foot_s   = S("Fo",  6.8, GREY_TXT, align=TA_CENTER)

def sp(n=6): return Spacer(1, n)

# ══════════════════════════════════════════════════════════════════════════════
# COMPONENT BUILDERS
# ══════════════════════════════════════════════════════════════════════════════

def title_block(line1, line2, bg=NAVY):
    t = Table([[Paragraph(line1, title_s)],
               [Paragraph(line2, sub_s)]],
              colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("TOPPADDING",    (0,0), (-1,-1), 14),
        ("BOTTOMPADDING", (0,0), (-1,-1), 14),
    ]))
    return t

def sec_banner(text, bg=TEAL):
    t = Table([[Paragraph(text, sec_s)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ]))
    return t

def agent_bar(num, name, cat, bg=RED_DARK):
    """Agent header: coloured bar with number, name and category badge."""
    badge_bg = colors.HexColor(
        "%06x" % max(0, int(bg.hexval(), 16) - 0x181818))
    t = Table([[Paragraph(f"{num}.  {name}", agent_s),
                Paragraph(cat, tag_s)]],
              colWidths=[5.9*inch, 1.5*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,0), bg),
        ("BACKGROUND",    (1,0), (1,0), badge_bg),
        ("TOPPADDING",    (0,0), (-1,-1), 8),
        ("BOTTOMPADDING", (0,0), (-1,-1), 8),
        ("LEFTPADDING",   (0,0), (0,0), 10),
        ("ALIGN",         (1,0), (1,0), "CENTER"),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]))
    return t

def lp(label, text, label_w=1.4):
    """Bold label + body paragraph in a tight two-column row."""
    t = Table([[Paragraph(label, label_s),
                Paragraph(text, body_s)]],
              colWidths=[label_w*inch, (7.4 - label_w)*inch])
    t.setStyle(TableStyle([
        ("TOPPADDING",    (0,0), (-1,-1), 2),
        ("BOTTOMPADDING", (0,0), (-1,-1), 2),
        ("LEFTPADDING",   (0,0), (-1,-1), 0),
        ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ]))
    return t

def hbox(para, bg=L_BLUE, border=TEAL):
    """Single coloured paragraph box."""
    t = Table([[para]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.9, border),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
        ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ]))
    return t

def highlight_row(icon, text, style, bg, border):
    """Single inline highlight strip — icon + text on one line."""
    t = Table([[Paragraph(f"{icon}  {text}", style)]],
              colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 0.7, border),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def nclex_imp(*points):
    """Yellow starred NCLEX-important points box."""
    rows = [[Paragraph("⭐  NCLEX IMPORTANT", S("ni", 9, GOLD, bold=True))]]
    for p in points:
        rows.append([Paragraph(f"★  {p}", S("np", 8.5, colors.HexColor("#7D6608")))])
    t = Table(rows, colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,0), L_GOLD),
        ("BACKGROUND",    (0,1), (-1,-1), colors.HexColor("#FFFDE7")),
        ("BOX",           (0,0), (-1,-1), 1.0, GOLD),
        ("LINEBELOW",     (0,0), (-1,0), 0.5, GOLD),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def memory_trick(trick_text, extra=None):
    """Purple memory trick / mnemonic box."""
    rows = [[Paragraph("🧠  MEMORY TRICK", S("mt", 9, PURPLE, bold=True))],
            [Paragraph(trick_text, S("mb", 9, PURPLE, bold=False))]]
    if extra:
        rows.append([Paragraph(extra, S("me", 8.5, colors.HexColor("#4A235A"), italic=True))])
    t = Table(rows, colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,0), L_PUR),
        ("BACKGROUND",    (0,1), (-1,-1), colors.HexColor("#F5EEF8")),
        ("BOX",           (0,0), (-1,-1), 1.0, PURPLE),
        ("LINEBELOW",     (0,0), (-1,0), 0.5, PURPLE),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def warn_box(text):
    """Red warning / critical alert box."""
    t = Table([[Paragraph(f"⚠  {text}", hl_red)]], colWidths=[7.4*inch])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), L_RED),
        ("BOX",           (0,0), (-1,-1), 1.0, RED_MED),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ]))
    return t

def two_col_bullets(left_title, left_bullets, left_bg, left_border,
                    right_title, right_bullets, right_bg, right_border):
    """Two side-by-side bullet boxes."""
    def box(title, bullets, bg, border):
        rows = [[Paragraph(title, S("bh", 9, border, bold=True))]]
        for b in bullets:
            rows.append([Paragraph(f"• {b}", bullet_s)])
        t = Table(rows, colWidths=[3.55*inch])
        t.setStyle(TableStyle([
            ("BACKGROUND",    (0,0), (0,0), bg),
            ("BACKGROUND",    (0,1), (-1,-1), colors.HexColor(
                "FFFEF0" if bg == L_YEL else "F8FFFE" if bg == L_TEAL else "FFF8F8"))),
            ("BOX",           (0,0), (-1,-1), 0.8, border),
            ("LINEBELOW",     (0,0), (0,0), 0.5, border),
            ("TOPPADDING",    (0,0), (-1,-1), 4),
            ("BOTTOMPADDING", (0,0), (-1,-1), 4),
            ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ]))
        return t
    outer = Table([[box(left_title,  left_bullets,  left_bg,  left_border),
                    box(right_title, right_bullets, right_bg, right_border)]],
                  colWidths=[3.65*inch, 3.65*inch])
    outer.setStyle(TableStyle([
        ("LEFTPADDING",  (0,0), (-1,-1), 0),
        ("RIGHTPADDING", (0,0), (-1,-1), 0),
        ("TOPPADDING",   (0,0), (-1,-1), 0),
        ("VALIGN",       (0,0), (-1,-1), "TOP"),
        ("INNERGRID",    (0,0), (-1,-1), 0, WHITE),
    ]))
    return outer

def mini_table(headers, rows, col_widths, hdr_bg=NAVY):
    data = [headers] + rows
    t = Table(data, colWidths=col_widths, repeatRows=1)
    cmds = [
        ("BACKGROUND",    (0,0), (-1,0), hdr_bg),
        ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
        ("FONTNAME",      (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",      (0,0), (-1,0), 8),
        ("ALIGN",         (0,0), (-1,0), "CENTER"),
        ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE",      (0,1), (-1,-1), 8),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 6),
        ("RIGHTPADDING",  (0,0), (-1,-1), 6),
        ("GRID",          (0,0), (-1,-1), 0.4, GREY_LINE),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]
    for i in range(2, len(rows)+1, 2):
        cmds.append(("BACKGROUND", (0,i), (-1,i), L_GREY))
    t.setStyle(TableStyle(cmds))
    return t

def divider(): return HRFlowable(width="100%", thickness=0.6, color=GREY_LINE)

# ══════════════════════════════════════════════════════════════════════════════
# STORY
# ══════════════════════════════════════════════════════════════════════════════
story = []
doc = SimpleDocTemplate(OUTPUT, pagesize=letter,
    leftMargin=0.6*inch, rightMargin=0.6*inch,
    topMargin=0.6*inch, bottomMargin=0.6*inch,
    title="NCLEX Demo v2 – Biological Warfare (Mixed Format)",
    author="Orris Study Aid")

# ── TITLE ──────────────────────────────────────────────────────────────────────
story += [
    title_block("☣  Biological Warfare Agents — NCLEX Study Guide",
                "Mixed Format Demo  ·  Highlights · Memory Tricks · NCLEX Important Points"),
    sp(8),
    hbox(Paragraph(
        "📌  This guide uses paragraphs for explanations, bullet boxes for signs & symptoms, "
        "highlighted strips for key facts, ⭐ NCLEX Important boxes for exam-critical points, "
        "and 🧠 Memory Trick boxes for mnemonics. Tables are used only for true comparisons.",
        hl_tip), bg=L_BLUE, border=TEAL),
    sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 1 — ANTHRAX
# ══════════════════════════════════════════════════════════════════════════════
story += [
    agent_bar("1", "ANTHRAX  (Bacillus anthracis)", "Category A ★ HIGHEST RISK", RED_DARK),
    sp(6),

    Paragraph("Overview", h3_s), sp(3),
    Paragraph(
        "Anthrax is caused by <i>Bacillus anthracis</i>, a spore-forming gram-positive rod. "
        "It is the #1 bioterrorism concern because spores are incredibly stable, survive for decades "
        "in soil, can be aerosolised, and inhalation anthrax kills over 80% of untreated patients. "
        "The 2001 U.S. anthrax mail attacks demonstrated how even small quantities of spores "
        "can produce mass casualties and overwhelm public health systems.",
        body_s), sp(8),

    Paragraph("Three Clinical Forms", h3_s), sp(4),

    two_col_bullets(
        "Cutaneous (95% of natural cases)",
        ["Painless papule at site of spore contact",
         "Becomes a ring of vesicles, then ulcerates",
         "Forms a BLACK, PAINLESS ESCHAR (key sign!)",
         "Surrounding gelatinous oedema",
         "Lymphadenopathy, low-grade fever",
         "Incubation: < 1 week (rarely up to 2 weeks)"],
        L_ORG, ORANGE,
        "Inhalation  ★ Bioweapon Form",
        ["Flu-like prodrome: fever, malaise, myalgia",
         "Brief apparent improvement (1–3 days)",
         "Then SUDDEN COLLAPSE & rapid deterioration",
         "WIDENED MEDIASTINUM on chest X-ray",
         "Haemorrhagic mediastinitis, meningitis",
         "Incubation: 2 days – 6 WEEKS (spore dormancy!)"],
        L_RED, RED_MED,
    ), sp(4),

    Paragraph(
        "The gastrointestinal form is rare and occurs after ingesting contaminated meat. "
        "It produces oral/oropharyngeal ulcers, nausea, bloody diarrhoea, and ascites. "
        "All forms can progress to septicaemia and meningitis if treatment is delayed.",
        body_s), sp(6),

    # highlighted strip
    highlight_row("🔴", "WIDENED MEDIASTINUM on chest X-ray = Inhalation Anthrax until proven otherwise",
                  hl_red, L_RED, RED_MED), sp(3),
    highlight_row("🟠", "BLACK PAINLESS ESCHAR = Cutaneous Anthrax  (painless = key differentiator)",
                  S("hs2", 9, ORANGE, bold=True), L_ORG, ORANGE), sp(6),

    Paragraph("Precautions & Treatment", h3_s), sp(3),
    lp("Precautions:", "Standard precautions for all forms. Person-to-person transmission is extremely rare. Wear gloves when handling cutaneous lesions — discharge is potentially infectious."), sp(3),
    lp("Treatment:", "Ciprofloxacin or Doxycycline are first-line. Inhalation anthrax requires IV therapy initially, then oral step-down. Total course = 60 days to cover prolonged spore germination."), sp(3),
    lp("Prophylaxis:", "Post-exposure: Ciprofloxacin or Doxycycline × 60 days. Anthrax vaccine (AVA) for military and lab workers."), sp(6),

    nclex_imp(
        "Inhalation anthrax incubation = up to 6 WEEKS (spores can remain dormant in lungs)",
        "Painless eschar — the word PAINLESS is what the NCLEX will test",
        "Widened mediastinum on CXR = classic imaging finding of inhalation anthrax",
        "Standard precautions only — no airborne, no droplet required",
        "Ciprofloxacin is the drug most associated with anthrax on NCLEX",
    ), sp(4),

    memory_trick(
        "'ANTH-RAX has a BLACK TACK in its name' — black eschar, painless like a thumbtack",
        "Inhalation anthrax: Think '6-WEEK SLEEPER' — spores sleep in lungs up to 6 weeks before waking up deadly"
    ), sp(12),
]

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 2 — SMALLPOX
# ══════════════════════════════════════════════════════════════════════════════
story += [
    agent_bar("2", "SMALLPOX  (Variola virus)", "Category A — Eradicated 1980", PURPLE),
    sp(6),

    Paragraph("Overview", h3_s), sp(3),
    Paragraph(
        "Smallpox was eradicated globally in 1980. Because routine vaccination stopped at that time, "
        "most people born after ~1972 have NO immunity, making it one of the most feared "
        "potential bioweapons. Only two authorised repositories remain: CDC (Atlanta) and "
        "VECTOR Institute (Russia). Smallpox spreads via the airborne route and direct contact "
        "with lesions — both routes require simultaneous precautions.",
        body_s), sp(8),

    Paragraph("Clinical Features", h3_s), sp(3),
    Paragraph(
        "The prodrome begins with HIGH FEVER, severe headache, and backache "
        "<b>2–4 days BEFORE the rash appears</b>. This pre-rash fever is the most tested "
        "distinguishing feature from chickenpox. The rash is <b>centrifugal</b> — starting on "
        "the face and spreading outward to the palms and soles (the opposite of chickenpox). "
        "Critically, ALL lesions are in the <b>same stage of development simultaneously</b> — "
        "all papules, then all vesicles, then all pustules, then all crusts. "
        "The pustules are deep, firm, and umbilicated (belly-button dimple in centre).",
        body_s), sp(6),

    highlight_row("🟣", "ALL lesions SAME STAGE simultaneously = Smallpox  (chickenpox has multiple stages at once)",
                  S("hsp", 9, PURPLE, bold=True), L_PUR, PURPLE), sp(3),
    highlight_row("🟣", "Centrifugal rash: Face → Arms → Legs → Palms & Soles  (outward from the centre)",
                  S("hsp2", 9, PURPLE, bold=True), L_PUR, PURPLE), sp(3),
    highlight_row("🟣", "Prodrome FEVER 2–4 days BEFORE rash appears  (chickenpox: fever WITH rash)",
                  S("hsp3", 9, PURPLE, bold=True), L_PUR, PURPLE), sp(6),

    Paragraph("Smallpox vs. Chickenpox — Quick Comparison", h3_s), sp(4),
    mini_table(
        headers=["Feature", "SMALLPOX", "CHICKENPOX"],
        col_widths=[2.0*inch, 2.7*inch, 2.7*inch],
        rows=[
            ["Lesion stages",    "ALL same stage",           "Multiple stages at once"],
            ["Rash direction",   "Centrifugal (face first)",  "Centripetal (trunk first)"],
            ["Lesion feel",      "Deep, firm, umbilicated",   "Superficial, soft vesicles"],
            ["Fever timing",     "2–4 days BEFORE rash",      "WITH rash (or mild)"],
            ["Precautions",      "Airborne + Contact",        "Airborne only"],
        ], hdr_bg=PURPLE,
    ), sp(6),

    lp("Precautions:", "AIRBORNE + CONTACT simultaneously. N-95 respirator (fit-tested), negative-pressure room, gown, gloves. Door must remain closed. Patient wears surgical mask during any transport."), sp(3),
    lp("Treatment:", "No proven antiviral cure. Vaccinia vaccine (ACAM2000) given within 4 days of exposure can prevent or reduce disease severity. Tecovirimat (TPOXX) is FDA-approved for treatment."), sp(6),

    nclex_imp(
        "The #1 tested fact: ALL lesions are in the SAME stage — memorise this above all else",
        "Precautions = BOTH Airborne AND Contact (two types at the same time)",
        "Vaccinate contacts within 4 DAYS of exposure — window for prevention",
        "Centrifugal = outward (face, palms, soles) — opposite of chickenpox",
        "Negative-pressure room with door CLOSED at all times",
    ), sp(4),

    memory_trick(
        '"SMALL-pox = SMALL differences that are BIG on exams"',
        "S = Same stage  ·  M = More on face/palms (centrifugal)  ·  A = Airborne+Contact  ·  L = Lesions deep/firm  ·  L = Long prodrome (fever first)"
    ), sp(12),
]

story.append(PageBreak())

# ══════════════════════════════════════════════════════════════════════════════
# AGENT 3 — BOTULISM
# ══════════════════════════════════════════════════════════════════════════════
story += [
    agent_bar("3", "BOTULISM  (Clostridium botulinum toxin)", "Category A", NAVY),
    sp(6),

    Paragraph("Overview", h3_s), sp(3),
    Paragraph(
        "Botulinum toxin is the most acutely lethal toxin known. It blocks the release of "
        "acetylcholine (ACh) at the neuromuscular junction, causing a progressive "
        "<b>descending flaccid paralysis</b>. As a bioweapon it could be aerosolised or introduced "
        "into food/water supplies. There is NO person-to-person transmission, so only standard "
        "precautions are needed.",
        body_s), sp(6),

    Paragraph("Presentation — The 3 Ds then Down", h3_s), sp(3),
    Paragraph(
        "Symptoms begin with the cranial nerves and descend. The classic triad is <b>Diplopia</b> "
        "(double vision), <b>Dysarthria</b> (slurred speech), and <b>Dysphagia</b> (difficulty "
        "swallowing). These then progress downward to the trunk and limbs, and ultimately to "
        "respiratory muscle failure. The patient is <b>afebrile, fully alert, with no sensory "
        "loss</b> — they can hear and understand everything said in the room.",
        body_s), sp(4),

    highlight_row("🔵", "DESCENDING paralysis + AFEBRILE + ALERT + NO sensory loss = Botulism",
                  S("hbot", 9, NAVY, bold=True), L_BLUE, NAVY), sp(3),
    highlight_row("🔵", "Patient is CONSCIOUS even when completely paralysed — always explain procedures aloud",
                  S("hbot2", 9, NAVY, bold=True), L_BLUE, NAVY), sp(3),
    highlight_row("🔴", "RESPIRATORY FAILURE is the #1 priority — monitor rate, O2 sat, vital capacity",
                  hl_red, L_RED, RED_MED), sp(6),

    Paragraph("Nursing Priorities", h3_s), sp(3),
    *[Paragraph(f"• {b}", bullet_s) for b in [
        "Monitor respiratory status continuously — frequent vital capacity checks, early intubation if needed.",
        "Administer Botulinum Antitoxin (BAT) ASAP — it stops progression but does NOT reverse existing paralysis.",
        "Paralysis can last weeks to months — prepare patient and family for prolonged ICU stay.",
        "Post bedside signage: 'Patient is conscious — communicate with them, explain all procedures.'",
        "Infant form: strict honey restriction under 12 months; treat with BabyBIG (human immunoglobulin).",
        "Standard precautions only — no person-to-person transmission of botulism.",
    ]], sp(6),

    warn_box(
        "ANTITOXIN TRAP: BAT stops progression — it does NOT reverse paralysis already present. "
        "The earlier it is given, the better the outcome. Call state health dept to access BAT from federal stockpile."
    ), sp(6),

    nclex_imp(
        "Descending paralysis = always think Botulism (ascending = Guillain-Barré — opposite direction!)",
        "Patient is ALERT and CONSCIOUS despite being paralysed — NCLEX loves this fact",
        "Antitoxin STOPS progression — does NOT REVERSE existing paralysis",
        "Honey is CONTRAINDICATED in infants under 1 year (infant botulism risk)",
        "Respiratory failure = priority nursing concern — monitor every assessment",
    ), sp(4),

    memory_trick(
        '"BOTU-lism goes DOWN like a BOT-tle tipped over"  — descending paralysis',
        "3 Ds before it goes DOWN:  Diplopia  →  Dysarthria  →  Dysphagia  →  Descending paralysis  →  Diaphragm (respiratory failure)"
    ), sp(12),
]

# ══════════════════════════════════════════════════════════════════════════════
# MASTER QUICK REFERENCE
# ══════════════════════════════════════════════════════════════════════════════
story += [
    sec_banner("Master Quick-Reference", GREEN_DK), sp(6),
    mini_table(
        headers=["Agent", "One-Liner Key Sign", "Precaution", "Treatment / Antidote"],
        col_widths=[1.3*inch, 2.5*inch, 1.4*inch, 2.2*inch],
        rows=[
            ["Anthrax (skin)",    "Painless BLACK eschar",              "Standard",          "Cipro / Doxy × 60 days"],
            ["Anthrax (lung)",    "Widened mediastinum on CXR",         "Standard",          "Cipro IV → oral × 60 days"],
            ["Smallpox",          "ALL lesions same stage; centrifugal","Airborne+Contact",  "Vaccinia vaccine ≤ 4 days"],
            ["Plague (pneumonic)","Bloody frothy sputum",               "Droplet",           "Streptomycin / Doxy"],
            ["Botulism",          "Descending paralysis; alert; afebrile","Standard",        "BAT antitoxin (stops progression)"],
            ["Tularemia",         "Ulcer + lymphadenopathy",            "Standard",          "Streptomycin / Gentamicin"],
        ], hdr_bg=GREEN_DK,
    ), sp(10),
]

# ══════════════════════════════════════════════════════════════════════════════
# PRACTICE QUESTION
# ══════════════════════════════════════════════════════════════════════════════
story += [
    sec_banner("NCLEX-Style Practice Question", TEAL), sp(6),
]

q = Table([
    [Paragraph(
        "A nurse receives four patients during a mass casualty event. "
        "Patient A has a painless black lesion on the hand after opening a suspicious envelope. "
        "Patient B has descending limb weakness, double vision, and is afebrile. "
        "Patient C has a rash with ALL lesions appearing as firm pustules concentrated on the face and palms. "
        "Patient D has bloody frothy sputum and severe pneumonia after a possible bioterrorism release.\n\n"
        "Match each patient with the correct priority nursing action:",
        body_s)],
    [sp(3)],
    [Paragraph(
        "Patient A — Cutaneous Anthrax:  Standard precautions; Ciprofloxacin; notify public health.\n"
        "Patient B — Botulism:  Standard precautions; monitor respirations; prepare for BAT antitoxin.\n"
        "Patient C — Smallpox:  Airborne + Contact precautions; N-95; negative-pressure room; notify CDC.\n"
        "Patient D — Pneumonic Plague:  Droplet precautions; Streptomycin or Doxycycline.",
        S("qa", 9, GREEN_DK, bold=False, left=8))],
], colWidths=[7.4*inch])
q.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (0,0), L_BLUE),
    ("BACKGROUND", (0,2), (0,2), L_GRN),
    ("BOX",        (0,0), (-1,-1), 0.8, TEAL),
    ("TOPPADDING",    (0,0), (-1,-1), 7),
    ("BOTTOMPADDING", (0,0), (-1,-1), 7),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
]))
story += [q, sp(10)]

# ── FOOTER ─────────────────────────────────────────────────────────────────────
story += [
    divider(), sp(4),
    Paragraph(
        "Sources: Harrison's Principles of Internal Medicine 22E  ·  Red Book 2021  ·  "
        "Goldman-Cecil Medicine  ·  CDC Bioterrorism Agent List  ·  NCSBN NCLEX-RN Test Plan",
        foot_s),
]

doc.build(story)
print(f"Written: {OUTPUT}")
Your credits are exhausted. You can purchase a top-up or subscribe to a plan to continue using Orris. If you're already subscribed, your daily credits will reset tomorrow.

Generate the demo v2 PDF and download it

Your credits are exhausted. You can purchase a top-up or subscribe to a plan to continue using Orris. If you're already subscribed, your daily credits will reset tomorrow.

Create a full NCLEX study guide using this improved format

Your credits are exhausted. You can purchase a top-up or subscribe to a plan to continue using Orris. If you're already subscribed, your daily credits will reset tomorrow.
This is a shared conversation. Sign in to Orris to start your own chat.