What is the steps in management of pt with low oxygen with tracheostomy tube
management of hypoxia in patient with tracheostomy tube stepwise approach
https://www.aspirus.org/Uploads/Public/Documents/MedEvac/Pro…
tracheostomy tube management algorithm hypoxia

A clinical procedural photograph demonstrating the intraoperative management of a patient with a tracheostomy requiring lung isolation. The image shows a clear, flexible double-lumen endobronchial tube (DLT) inserted directly through an established tracheostomy stoma rather than the oral route. The DLT is secured at the stoma site using traditional white cloth tracheostomy ties knotted around the tube's flange and fastened around the patient's neck. A transparent occlusive dressing is applied over the peristomal skin to maintain sterility. The patient is in a supine position with the head supported by a purple positioning foam and the neck slightly extended. Green surgical drapes partially cover the head and upper torso. Visible monitoring equipment includes an ECG electrode adhered to the left upper chest with an attached white lead wire. White adhesive tape is applied across the maxilla and nose, and the mouth remains open. This image illustrates a specialized anesthetic technique for thoracic surgery in patients with pre-existing tracheostomies, highlighting the stabilization and positioning of airway devices.

A clinical photograph showing a close-up of a patient's anterior neck with a permanent tracheotomy. A white, oval-shaped tracheostomy tube flange is positioned centrally over the trachea, inferior to the laryngeal prominence. The flange is marked with the text "NO CUFF," indicating a cuffless tube design often used for long-term airway management in patients who do not require mechanical ventilation or have low aspiration risk. The tube is secured to the neck using green cloth tracheostomy ties threaded through lateral slots in the flange. The skin surrounding the stoma appears intact, with no visible signs of inflammation, infection, or stomal breakdown. The patient's neck shows visible skin folds and anatomical landmarks, including the contours of the sternocleidomastoid muscles. This image illustrates post-surgical management following extensive tracheal resection and total thyroidectomy for locally advanced papillary thyroid cancer (PTC).

This clinical photograph displays a tracheostomy tube in situ within the anterior neck of an adult male patient. The tracheostomy tube features a light blue rectangular flange (plate) resting against the skin, secured by white gauze padding and a wide, off-white cotton tie that encircles the neck. The surgical site appears to be located just above the manubrium sterni, and there is visible suture material and some minor dried discharge near the flange edges. A translucent ventilator or T-piece tubing is connected to the tracheostomy cannula, extending upwards across the left side of the patient’s face. Additional clinical features include white medical tape and gauze obscuring the patient's nose and upper lip, likely related to prior airway management or nasogastric tube fixation. This procedural image demonstrates the management of a patient on spontaneous ventilation following surgical emphysema and tracheal wound dehiscence repair, highlighting standard tracheostomy fixation techniques in a post-surgical critical care setting.
Critical note: For tracheostomies less than 7 days old - blind reinsertion carries high risk of creating a false passage. Use a bougie or scope-guided reinsertion.
| Cause | Signs | Management |
|---|---|---|
| Mucous plug / secretion obstruction | Suction catheter won't pass, high airway pressure | Suction, saline instillation, inner cannula removal and cleaning |
| Tube dislodgement | No capnography waveform, subcutaneous emphysema, no airflow at stoma | Remove tube, oxygenate via face mask, replace tube with obturator |
| Cuff herniation | Cuff visible at tube end, suction catheter blocked | Deflate cuff, replace tube |
| Tube too long (carina impaction) | Unilateral breath sounds | Replace with shorter/smaller tube |
| Pneumothorax | Decreased unilateral breath sounds, hypotension, tracheal deviation | CXR, needle decompression, chest drain |
| Bleeding | Hemoptysis, blood at stoma | Overinflate cuff to tamponade; emergent surgical consult; ETT through stoma if tracheo-innominate fistula suspected |
| Equipment failure | Ventilator circuit disconnection, kinked tubing | Disconnect from vent, manual BVM ventilation |
| Infection/pneumonia | Fever, purulent sputum, CXR changes | Antibiotics, optimize ventilation |

Create a quick reference PDF checklist for tracheostomy emergencies
ls -la /tmp/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /tmp/workspace/tracheostomy-checklist && echo "done"
~/tracheostomy-checklist/build_checklist.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.platypus import KeepTogether
OUTPUT = "/tmp/workspace/tracheostomy-checklist/Tracheostomy_Emergency_Checklist.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=14*mm,
leftMargin=14*mm,
topMargin=12*mm,
bottomMargin=12*mm,
)
W, H = A4
CONTENT_W = W - 28*mm
# ── Color palette ──────────────────────────────────────────────────────────────
RED = colors.HexColor("#C0392B")
DARK_RED = colors.HexColor("#922B21")
ORANGE = colors.HexColor("#E67E22")
TEAL = colors.HexColor("#1A7A6E")
DARK_TEAL = colors.HexColor("#145A52")
BLUE = colors.HexColor("#2471A3")
DARK_BLUE = colors.HexColor("#1A5276")
PURPLE = colors.HexColor("#7D3C98")
YELLOW = colors.HexColor("#F9E79F")
PALE_RED = colors.HexColor("#FADBD8")
PALE_GRN = colors.HexColor("#D5F5E3")
PALE_BLU = colors.HexColor("#D6EAF8")
PALE_ORG = colors.HexColor("#FDEBD0")
PALE_PUR = colors.HexColor("#E8DAEF")
LIGHT_GREY= colors.HexColor("#F2F3F4")
MID_GREY = colors.HexColor("#BDC3C7")
DARK_GREY = colors.HexColor("#566573")
WHITE = colors.white
BLACK = colors.black
styles = getSampleStyleSheet()
def style(name="Normal", size=9, bold=False, color=BLACK, align=TA_LEFT,
space_before=0, space_after=2, leading=None):
return ParagraphStyle(
name,
parent=styles["Normal"],
fontSize=size,
fontName="Helvetica-Bold" if bold else "Helvetica",
textColor=color,
alignment=align,
spaceBefore=space_before,
spaceAfter=space_after,
leading=leading or (size * 1.3),
)
def para(text, **kw):
return Paragraph(text, style(**kw))
def hr(color=MID_GREY, thickness=0.5):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=2, spaceBefore=2)
def checkbox(text, indent=4):
"""A single checklist row with a square checkbox."""
return Paragraph(
f'<font size="10">□</font> {text}',
ParagraphStyle(
"cb",
parent=styles["Normal"],
fontSize=9,
fontName="Helvetica",
leftIndent=indent * mm,
spaceBefore=1,
spaceAfter=1,
leading=12,
)
)
def section_header(title, bg=TEAL, fg=WHITE, size=10):
data = [[Paragraph(f'<b>{title}</b>',
ParagraphStyle("sh", parent=styles["Normal"],
fontSize=size, fontName="Helvetica-Bold",
textColor=fg, alignment=TA_LEFT, leading=size*1.3))]]
t = Table(data, colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [3]),
]))
return t
def note_box(lines, bg=PALE_BLU, border=BLUE):
paras = [Paragraph(l, ParagraphStyle("nb", parent=styles["Normal"],
fontSize=8.5, fontName="Helvetica",
leading=12, spaceBefore=1, spaceAfter=1))
for l in lines]
t = Table([[p] for p in paras], colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1, border),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
return t
def two_col_checks(left_items, right_items, bg=LIGHT_GREY):
HW = CONTENT_W / 2 - 1*mm
def col(items):
return [checkbox(i) for i in items]
rows = []
for i in range(max(len(left_items), len(right_items))):
l = col([left_items[i]])[0] if i < len(left_items) else Paragraph("")
r = col([right_items[i]])[0] if i < len(right_items) else Paragraph("")
rows.append([l, r])
t = Table(rows, colWidths=[HW, HW])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
return t
def dope_table():
headers = ["D", "O", "P", "E"]
full = ["Dislodgement / Displacement",
"Obstruction (secretions, plug)",
"Pneumothorax",
"Equipment failure"]
signs = ["No airflow at stoma, subcutaneous emphysema, agitation",
"Suction catheter won't pass, high airway pressure",
"Unilateral breath sounds, hypotension, tracheal deviation",
"Vent disconnect, kinked/cracked tubing, cuff leak"]
bgs = [PALE_RED, PALE_ORG, PALE_BLU, PALE_PUR]
bords = [RED, ORANGE, BLUE, PURPLE]
col_w = [CONTENT_W * 0.06, CONTENT_W * 0.28, CONTENT_W * 0.66]
rows = []
for i in range(4):
rows.append([
Paragraph(f'<b>{headers[i]}</b>',
ParagraphStyle("dh", parent=styles["Normal"],
fontSize=14, fontName="Helvetica-Bold",
textColor=bords[i], alignment=TA_CENTER)),
Paragraph(f'<b>{full[i]}</b>',
ParagraphStyle("df", parent=styles["Normal"],
fontSize=8.5, fontName="Helvetica-Bold",
textColor=BLACK, leading=11)),
Paragraph(signs[i],
ParagraphStyle("ds", parent=styles["Normal"],
fontSize=8, fontName="Helvetica",
textColor=DARK_GREY, leading=11)),
])
t = Table(rows, colWidths=col_w, rowHeights=None)
ts_cmds = [
("GRID", (0,0), (-1,-1), 0.5, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
for i, bg in enumerate(bgs):
ts_cmds.append(("BACKGROUND", (0, i), (-1, i), bg))
t.setStyle(TableStyle(ts_cmds))
return t
def cause_table():
headers = [
Paragraph('<b>Cause</b>', style(bold=True, size=8.5, color=WHITE)),
Paragraph('<b>Key Signs</b>', style(bold=True, size=8.5, color=WHITE)),
Paragraph('<b>Action</b>', style(bold=True, size=8.5, color=WHITE)),
]
rows_data = [
["Mucous plug", "Suction catheter blocked, wheeze",
"Remove inner cannula, suction, saline 1-3 mL"],
["Tube dislodged", "No capnography, subcut emphysema",
"Remove tube, O2 via face mask, replace with obturator"],
["Cuff herniation", "Cuff visible at tube end",
"Deflate cuff, replace tube"],
["Tube too long", "Unilateral breath sounds",
"Replace with shorter/smaller tube"],
["Pneumothorax", "Decreased BS, hypotension",
"CXR, needle decompression, chest drain"],
["Tracheo-innominate bleed", "Massive hemoptysis",
"Overinflate cuff, emergent surgical consult, ETT tamponade"],
["Vent disconnect", "Alarm, no ETCO2",
"Check all circuit connections, switch to BVM"],
["Infection/Pneumonia", "Fever, purulent sputum",
"Antibiotics, optimize vent settings, cultures"],
]
col_w = [CONTENT_W*0.22, CONTENT_W*0.30, CONTENT_W*0.48]
table_rows = [[headers[0], headers[1], headers[2]]]
for i, r in enumerate(rows_data):
table_rows.append([
Paragraph(r[0], style(size=8.5, bold=(i % 2 == 0))),
Paragraph(r[1], style(size=8)),
Paragraph(r[2], style(size=8)),
])
t = Table(table_rows, colWidths=col_w)
ts = [
("BACKGROUND", (0,0), (-1,0), DARK_TEAL),
("GRID", (0,0), (-1,-1), 0.5, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_GREY]),
]
t.setStyle(TableStyle(ts))
return t
# ── BUILD STORY ────────────────────────────────────────────────────────────────
story = []
# ── HEADER BANNER ─────────────────────────────────────────────────────────────
banner_data = [[
Paragraph(
'<b><font size="16" color="white">TRACHEOSTOMY EMERGENCY</font></b>'
'<br/><font size="10" color="#F9E79F">Quick Reference Checklist — Hypoxia & Respiratory Distress</font>',
ParagraphStyle("banner", parent=styles["Normal"], alignment=TA_LEFT,
leading=20, spaceBefore=0, spaceAfter=0)
),
Paragraph(
'<font size="8" color="#AED6F1">Based on: Tintinalli\'s Emergency Medicine |<br/>'
'Roberts & Hedges\' Clinical Procedures | NTSP Green Algorithm</font>',
ParagraphStyle("bsrc", parent=styles["Normal"], alignment=TA_RIGHT,
fontSize=8, leading=11, spaceBefore=0, spaceAfter=0)
),
]]
banner = Table(banner_data, colWidths=[CONTENT_W*0.65, CONTENT_W*0.35])
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_RED),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(banner)
story.append(Spacer(1, 4*mm))
# ── DOPE MNEMONIC ─────────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("⚡ DOPE — First think of these 4 causes", bg=RED),
Spacer(1, 2*mm),
dope_table(),
Spacer(1, 3*mm),
]))
# ── IMMEDIATE ACTIONS ─────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("1. IMMEDIATE ACTIONS (ALL Patients)", bg=DARK_RED),
Spacer(1, 2*mm),
two_col_checks(
[
"CALL FOR HELP — alert senior/airway clinician",
"Apply 100% O₂ via tracheostomy AND face mask",
"Attach pulse oximeter + cardiac monitor + capnography",
"Gather bedside equipment (see box below)",
],
[
"Note SpO₂, respiratory rate, chest rise, skin color",
"Look, listen, feel — airflow at stoma AND mouth",
"Auscultate bilaterally + over stoma",
"Note: time/reason tracheostomy was placed",
],
bg=PALE_RED,
),
Spacer(1, 2*mm),
note_box([
"<b>Bedside equipment required:</b> Spare tracheostomy tubes (same size + 1 smaller) • ETT (size 6.0) • "
"BVM • Sterile suction catheters (multiple sizes) • Yankauer • Saline ampoules • Tracheal dilator • "
"Obturator for tube • 10 mL syringe (cuff deflation) • Stethoscope • Waveform capnography"
], bg=PALE_RED, border=RED),
Spacer(1, 3*mm),
]))
# ── STEP-BY-STEP ALGORITHM ────────────────────────────────────────────────────
steps = [
(TEAL, "STEP 1 — Remove External Accessories",
["Remove speaking valve (e.g. Passy-Muir), cap, HME filter, or humidifier from tracheostomy hub",
"These can obstruct airflow and are frequently missed",
"Reassess: if improved → maintain O₂, full ABCDE assessment"]),
(TEAL, "STEP 2 — Pass Suction Catheter (Test Patency)",
["Attempt to pass suction catheter through inner cannula to beyond tube tip",
"If passes → tube is patent. Suction (max 10-15 sec, ≤3 passes). Preoxygenate before each pass",
"If does NOT pass → tube is OBSTRUCTED → proceed to Step 3",
"Apply suction while slowly withdrawing catheter with rotating motion"]),
(TEAL, "STEP 3 — Remove and Clean Inner Cannula",
["Remove inner cannula — inspect for mucous plug or debris",
"Clean/replace inner cannula immediately",
"If thick secretions: instill 1-3 mL sterile saline then suction",
"If no inner cannula present: suction outer cannula directly",
"If no relief → proceed to Step 4"]),
(DARK_TEAL, "STEP 4 — Deflate the Cuff",
["Deflate tracheostomy cuff using 10 mL syringe",
"Provide O₂ via face mask while cuff is deflated (upper airway now patent)",
"Rules out cuff herniation blocking tube end",
"If no relief → proceed to Step 5"]),
(DARK_TEAL, "STEP 5 — Remove and Replace Tracheostomy Tube",
["Remove entire outer tracheostomy tube",
"Suction stoma opening directly",
"Insert replacement tube with obturator IN PLACE, then remove obturator",
"⚠ Tube < 7 days old: high false passage risk — use bougie/scope guidance",
"Confirm: pass suction catheter + ETCO₂ waveform + bilateral breath sounds"]),
(DARK_BLUE, "STEP 6 — Primary Emergency Oxygenation (tube cannot be replaced)",
["VIA UPPER AIRWAY: BVM over nose/mouth + occlude stoma with gauze (prevent air leak)",
" → Use OPA/NPA adjuncts as needed; consider supraglottic airway (LMA)",
"VIA STOMA: Paediatric BVM mask directly over stoma opening",
" → Or place LMA over stoma",
"⚠ Laryngectomy patients: upper airway is disconnected — ONLY ventilate via stoma"]),
(DARK_BLUE, "STEP 7 — Secondary Emergency / Scope the Stoma",
["Pass flexible nasopharyngoscope or bronchoscope through stoma",
"Look for: granulation tissue, foreign body, blood clot, thick impacted secretions",
"Remove granulation tissue / crusting with hemostat through stoma",
"Consider oral intubation if upper airway is patent (not after laryngectomy)",
"If all else fails: emergent surgical airway / surgical consult"]),
]
for (bg, title, items) in steps:
block = [section_header(title, bg=bg), Spacer(1, 1*mm)]
for item in items:
block.append(checkbox(item))
block.append(Spacer(1, 2.5*mm))
story.append(KeepTogether(block))
# ── CAUSE TABLE ───────────────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("CAUSE-SPECIFIC MANAGEMENT", bg=DARK_GREY),
Spacer(1, 2*mm),
cause_table(),
Spacer(1, 3*mm),
]))
# ── POST-STABILISATION ABCDE ──────────────────────────────────────────────────
abcde_items = [
("<b>A — Airway</b>", "Confirm tube position: clinical + capnography + CXR"),
("<b>B — Breathing</b>", "Bilateral breath sounds, check ventilator settings / SpO₂"),
("<b>C — Circulation</b>", "HR, BP, perfusion — rule out tension pneumothorax / tamponade"),
("<b>D — Disability</b>", "GCS, pupil response, agitation level (agitation → tube displacement risk)"),
("<b>E — Exposure</b>", "Tracheostomy ties (1-2 finger breadths), stomal skin, tube position on CXR"),
]
col_w_ab = [CONTENT_W*0.14, CONTENT_W*0.86]
ab_rows = [[Paragraph(a, style(size=8.5, bold=False)), Paragraph(b, style(size=8.5))]
for a, b in abcde_items]
abcde_table = Table(ab_rows, colWidths=col_w_ab)
abcde_table.setStyle(TableStyle([
("GRID", (0,0), (-1,-1), 0.4, MID_GREY),
("BACKGROUND", (0,0), (-1,-1), PALE_GRN),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(KeepTogether([
section_header("POST-STABILISATION — ABCDE", bg=TEAL),
Spacer(1, 2*mm),
abcde_table,
Spacer(1, 3*mm),
]))
# ── SUCTIONING SAFETY REMINDERS ───────────────────────────────────────────────
story.append(KeepTogether([
section_header("SUCTIONING SAFETY REMINDERS", bg=ORANGE),
Spacer(1, 1*mm),
two_col_checks(
[
"Preoxygenate with 100% O₂ before EVERY pass",
"Limit each suction pass to ≤ 10-15 seconds",
"Maximum 3 passes in succession then rest",
"Use in-line suction for ventilator-dependent patients",
],
[
"Select correct catheter size (< ½ tube internal diameter)",
"STOP immediately if dysrhythmia develops",
"Atropine (nebulised or IV) for vagal bradycardia",
"Suspect raised ICP? Minimise stimulation, pre-treat accordingly",
],
bg=PALE_ORG,
),
Spacer(1, 3*mm),
]))
# ── SPECIAL POPULATIONS ───────────────────────────────────────────────────────
story.append(KeepTogether([
section_header("SPECIAL SITUATIONS", bg=PURPLE),
Spacer(1, 1*mm),
note_box([
"<b>Laryngectomy patient:</b> No connection between upper airway and trachea — NEVER attempt oral ventilation as primary strategy. ALL oxygenation must be via stoma.",
"<b>Tracheostomy < 7 days old:</b> Stoma tract immature — risk of false passage with blind reinsertion. Use bougie or scope guidance. Default to oral intubation if skilled operator present.",
"<b>Tracheo-innominate artery fistula (TIF):</b> Rare (0.7%) but 90% mortality. Presents with sentinel bleed then massive hemorrhage. Overinflate cuff as temporising measure. Emergent surgical consult immediately.",
"<b>Paediatric patients:</b> Smaller tubes, higher vagal tone — atropine pre-treatment for bradycardia. Use age-appropriate suction catheter sizes.",
], bg=PALE_PUR, border=PURPLE),
Spacer(1, 3*mm),
]))
# ── FOOTER ────────────────────────────────────────────────────────────────────
footer_data = [[
Paragraph(
'Ref: Tintinalli\'s Emergency Medicine 9e | Roberts & Hedges\' Clinical Procedures 7e | '
'NTSP Green Algorithm (National Tracheostomy Safety Project)',
ParagraphStyle("ft", parent=styles["Normal"], fontSize=7,
textColor=WHITE, alignment=TA_LEFT, leading=10)
),
Paragraph(
'For clinical use — verify against local protocols',
ParagraphStyle("ft2", parent=styles["Normal"], fontSize=7,
textColor=YELLOW, alignment=TA_RIGHT, leading=10)
),
]]
footer = Table(footer_data, colWidths=[CONTENT_W*0.72, CONTENT_W*0.28])
footer.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(footer)
# ── BUILD ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
cd /tmp/workspace/tracheostomy-checklist && python build_checklist.py 2>&1
ls -lh /tmp/workspace/tracheostomy-checklist/Tracheostomy_Emergency_Checklist.pdf
Tracheostomy Emergency Checklist
PDF Document · PDF