PMV (Passy Muir Valve) Introduction + Sensory Priming The FEES shows mobile vocal folds bilaterally and bilateral LAR present — PMV candidacy is favorable. TASK DETAIL PMV trial Cuff must be fully deflated before PMV placement; confirm no air leak, adequate SpO₂ Duration target Begin 15–20 min; increase by 10 min each session toward 4–8 hrs/day Rationale PMV restores subglottic pressure → improves laryngeal sensation → reduces penetration/aspiration (O'Connor et al., Heart Lung 2019, PMID 30573194) explain how can i do this procedure aS AN SLP
"Passy Muir valve" tracheostomy dysphagia speech language pathology
Passy Muir valve PMV trial procedure SLP speech language pathologist step by step placement
"Passy Muir valve" swallowing sensation subglottic pressure
https://www.passy-muir.com/wp-content/uploads/2018/10/placem…
"speaking valve" tracheostomy aspiration laryngeal sensation
PMID: 30573194

| Item | Action |
|---|---|
| Physician order | Ensure order is in for "speaking valve evaluation/trial" |
| Chart review | Trach brand, size, cuff type, vent status, recent secretion frequency |
| Trach tube type | Foam-cuffed trach = absolute contraindication for PMV |
| Glottic patency confirmed | FEES showing mobile VFs + bilateral LAR ✅ (your patient qualifies) |
| Equipment at bedside | PMV, pulse oximeter, suction (oral + tracheal), manual resuscitation bag |
| Pilot balloon label | Attach the PMV warning label to the pilot balloon line before you start |
| STOP Criteria | Action |
|---|---|
| SpO₂ drops below 93% | Remove PMV, suction, consider cuff re-inflation |
| HR increases >10 bpm above baseline | Remove PMV |
| RR increases significantly or WOB increases | Remove PMV |
| Patient reports significant discomfort | Remove PMV |
| Persistent, unrelieved coughing | May indicate upper airway obstruction — remove PMV |
| PMV pops off with cough | Suction, re-assess, replace securely |
| Contraindication | Reason |
|---|---|
| Foam-cuffed trach (e.g., Bivona) | Cuff cannot be fully deflated |
| Inability to tolerate cuff deflation | Exhalation pathway is obstructed |
| Severe upper airway obstruction | PMV would cause air trapping |
| Medical instability | Physiologic reserve too low |
| Severe aspiration risk | Relative — weigh risk/benefit; FEES helps stratify |
| Sleeping patient | Never leave PMV in during sleep |
| Concurrent HME use | Incompatible |
| Active aerosol/nebulizer treatment | Remove PMV first |
| Source | Finding |
|---|---|
| O'Connor et al., Heart & Lung 2019 [PMID 30573194] | Systematic review + meta-analysis: OR 0.12 for aspiration reduction with speaking valve in situ (p=0.003); also improved olfaction, secretion management, ventilator weaning |
| Cummings Otolaryngology | PMV aids swallowing and prevents aspiration by increasing subglottic pressure |
| Tintinalli's Emergency Medicine | PMV opens on inhalation, closes on exhalation; air directed through VCs; never use with cuffed tube |
Create a PMV trial protocol checklist PDF for SLP competency verification
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /home/daytona/workspace/pmv-protocol && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "from reportlab.lib.pagesizes import letter; print('ok')"
~/pmv-protocol/build_pmv_pdf.py
#!/usr/bin/env python3
"""
PMV Trial Protocol Checklist PDF — SLP Competency Verification
Uses ReportLab for precise clinical-document layout.
"""
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
import io, os
# ── Color palette ────────────────────────────────────────────────
DARK_BLUE = HexColor("#1B3A6B")
MID_BLUE = HexColor("#2E6DA4")
LIGHT_BLUE = HexColor("#D6E8F7")
TEAL = HexColor("#1E7B74")
TEAL_LIGHT = HexColor("#D0EFED")
RED = HexColor("#B22222")
RED_LIGHT = HexColor("#FDE8E8")
AMBER = HexColor("#A05C00")
AMBER_LIGHT = HexColor("#FFF3CD")
GRAY_DARK = HexColor("#333333")
GRAY_MED = HexColor("#666666")
GRAY_LIGHT = HexColor("#F2F4F6")
WHITE = colors.white
BLACK = colors.black
PAGE_W, PAGE_H = letter
MARGIN = 0.55 * inch
INNER_W = PAGE_W - 2 * MARGIN
OUTPUT = "/home/daytona/workspace/pmv-protocol/PMV_Trial_Protocol_SLP_Checklist.pdf"
# ── Style helpers ────────────────────────────────────────────────
def build_styles():
base = getSampleStyleSheet()
s = {}
s["title"] = ParagraphStyle("title",
fontName="Helvetica-Bold", fontSize=16,
textColor=WHITE, alignment=TA_CENTER, spaceAfter=2)
s["subtitle"] = ParagraphStyle("subtitle",
fontName="Helvetica", fontSize=10,
textColor=HexColor("#D6E8F7"), alignment=TA_CENTER, spaceAfter=0)
s["section"] = ParagraphStyle("section",
fontName="Helvetica-Bold", fontSize=10.5,
textColor=WHITE, alignment=TA_LEFT,
spaceBefore=4, spaceAfter=4, leftIndent=4)
s["body"] = ParagraphStyle("body",
fontName="Helvetica", fontSize=9,
textColor=GRAY_DARK, leading=13,
spaceBefore=1, spaceAfter=1)
s["body_bold"] = ParagraphStyle("body_bold",
fontName="Helvetica-Bold", fontSize=9,
textColor=GRAY_DARK, leading=13)
s["small"] = ParagraphStyle("small",
fontName="Helvetica", fontSize=7.5,
textColor=GRAY_MED, leading=11)
s["small_bold"] = ParagraphStyle("small_bold",
fontName="Helvetica-Bold", fontSize=7.5,
textColor=GRAY_DARK, leading=11)
s["note"] = ParagraphStyle("note",
fontName="Helvetica-Oblique", fontSize=8,
textColor=AMBER, leading=11)
s["stop"] = ParagraphStyle("stop",
fontName="Helvetica-Bold", fontSize=8,
textColor=RED, leading=11)
s["tbl_hdr"] = ParagraphStyle("tbl_hdr",
fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE, alignment=TA_CENTER)
s["tbl_cell"] = ParagraphStyle("tbl_cell",
fontName="Helvetica", fontSize=8.5,
textColor=GRAY_DARK, leading=12)
s["tbl_cell_b"] = ParagraphStyle("tbl_cell_b",
fontName="Helvetica-Bold", fontSize=8.5,
textColor=GRAY_DARK, leading=12)
s["evidence"] = ParagraphStyle("evidence",
fontName="Helvetica", fontSize=7.5,
textColor=GRAY_MED, leading=11)
s["footer"] = ParagraphStyle("footer",
fontName="Helvetica", fontSize=7,
textColor=GRAY_MED, alignment=TA_CENTER)
return s
# ── Checkbox helper ──────────────────────────────────────────────
CHECKBOX = "☐" # unicode open box
def cb(text, s, indent=0):
"""Return a checkbox paragraph."""
return Paragraph(f" {CHECKBOX} {text}", s)
def cb_crit(text, s):
"""Checkbox for a critical/safety step — bold."""
return Paragraph(f" {CHECKBOX} <b>{text}</b>", s)
# ── Section header banner ────────────────────────────────────────
def section_banner(label, bg_color, styles):
data = [[Paragraph(label, styles["section"])]]
t = Table(data, colWidths=[INNER_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg_color),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [4]),
]))
return t
# ── Two-column checklist table ───────────────────────────────────
def two_col_checks(items_left, items_right, s):
"""Build a 2-column checklist row table."""
rows = []
max_len = max(len(items_left), len(items_right))
for i in range(max_len):
left = cb(items_left[i], s) if i < len(items_left) else Paragraph("", s)
right = cb(items_right[i], s) if i < len(items_right) else Paragraph("", s)
rows.append([left, right])
col_w = INNER_W / 2 - 2
t = Table(rows, colWidths=[col_w, col_w])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
]))
return t
# ── Single-col checklist ─────────────────────────────────────────
def single_col_checks(items, s, critical_indices=None):
critical_indices = critical_indices or []
rows = []
for i, item in enumerate(items):
if i in critical_indices:
rows.append([cb_crit(item, s)])
else:
rows.append([cb(item, s)])
t = Table(rows, colWidths=[INNER_W])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
return t
# ── STOP criteria table ──────────────────────────────────────────
def stop_table(styles):
hdr = [
Paragraph("STOP CRITERION — Remove PMV Immediately", styles["tbl_hdr"]),
Paragraph("SLP Action", styles["tbl_hdr"]),
Paragraph("✓ / N/A", styles["tbl_hdr"]),
]
rows = [
["SpO₂ drops below 93% or >4% from baseline",
"Remove PMV; suction; consider cuff re-inflation; notify team"],
["HR increases >10 bpm above resting baseline",
"Remove PMV; monitor for 2 min; document"],
["RR increases significantly or labored WOB",
"Remove PMV; reposition; reassess upper airway patency"],
["Patient reports pain, pressure, or severe discomfort",
"Remove PMV; do not re-trial same session without MD consult"],
["Persistent forceful coughing — PMV not relieving",
"Remove PMV; consider upper airway obstruction; ENT consult"],
["PMV pops off repeatedly with cough",
"Suction; confirm secure placement; consider trach downsizing"],
["Decreasing level of consciousness or agitation",
"Remove PMV; assess airway; escalate care"],
]
col_w = [INNER_W * 0.40, INNER_W * 0.45, INNER_W * 0.15]
tbl_rows = [hdr]
for r in rows:
tbl_rows.append([
Paragraph(r[0], styles["tbl_cell"]),
Paragraph(r[1], styles["tbl_cell"]),
Paragraph("", styles["tbl_cell"]), # blank for checkmark
])
t = Table(tbl_rows, colWidths=col_w)
stripe = RED_LIGHT
tbl_style = [
("BACKGROUND", (0,0), (-1,0), RED),
("VALIGN", (0,0), (-1,-1), "TOP"),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("ALIGN", (2,0), (2,-1), "CENTER"),
]
for i in range(1, len(tbl_rows), 2):
tbl_style.append(("BACKGROUND", (0,i), (-1,i), RED_LIGHT))
t.setStyle(TableStyle(tbl_style))
return t
# ── Contraindications table ──────────────────────────────────────
def contraindications_table(styles):
hdr = [
Paragraph("Contraindication", styles["tbl_hdr"]),
Paragraph("Rationale", styles["tbl_hdr"]),
Paragraph("Confirmed Absent ✓", styles["tbl_hdr"]),
]
rows = [
["Foam-cuffed tracheostomy tube (e.g., Bivona)",
"Cuff cannot be fully deflated; air trapping risk"],
["Inability to tolerate cuff deflation",
"Exhalation through upper airway impossible"],
["Severe upper airway obstruction",
"PMV prevents exhalation; life-threatening air trapping"],
["Medical instability / hemodynamic compromise",
"Insufficient physiologic reserve for trial"],
["Laryngectomy (total)",
"No upper airway communication"],
["Concurrent HME in circuit",
"Blocks exhalation pathway — incompatible with PMV"],
["Active nebulizer / aerosol treatment",
"Medication deposits on valve membrane — remove PMV first"],
["Patient asleep or unresponsive",
"Cannot signal distress; never leave PMV unattended during sleep"],
]
col_w = [INNER_W * 0.33, INNER_W * 0.44, INNER_W * 0.23]
tbl_rows = [hdr]
for r in rows:
tbl_rows.append([
Paragraph(r[0], styles["tbl_cell_b"]),
Paragraph(r[1], styles["tbl_cell"]),
Paragraph("", styles["tbl_cell"]),
])
t = Table(tbl_rows, colWidths=col_w)
tbl_style = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("VALIGN", (0,0), (-1,-1), "TOP"),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("ALIGN", (2,0), (2,-1), "CENTER"),
]
for i in range(1, len(tbl_rows), 2):
tbl_style.append(("BACKGROUND", (0,i), (-1,i), LIGHT_BLUE))
t.setStyle(TableStyle(tbl_style))
return t
# ── Competency signature table ───────────────────────────────────
def competency_table(styles):
rows = [
[Paragraph("Competency Item", styles["tbl_hdr"]),
Paragraph("Date Observed", styles["tbl_hdr"]),
Paragraph("Meets Standard", styles["tbl_hdr"]),
Paragraph("Supervisor Initials", styles["tbl_hdr"])],
["Verifies contraindications + obtains MD order", "", "Yes / No / N/A", ""],
["Demonstrates complete cuff deflation technique", "", "Yes / No / N/A", ""],
["Confirms upper airway patency (airflow at mouth/nose)", "", "Yes / No / N/A", ""],
["Performs secure PMV placement (¼-turn lock)", "", "Yes / No / N/A", ""],
["Monitors vitals continuously; identifies STOP criteria", "", "Yes / No / N/A", ""],
["Conducts sensory priming / vocalization tasks with PMV", "", "Yes / No / N/A", ""],
["Removes PMV safely (stabilizes neck plate; right twist)", "", "Yes / No / N/A", ""],
["Documents trial: duration, vitals, voice quality, plan", "", "Yes / No / N/A", ""],
["Provides patient + family education", "", "Yes / No / N/A", ""],
["Cleans valve correctly; labels single-patient use", "", "Yes / No / N/A", ""],
]
col_w = [INNER_W * 0.40, INNER_W * 0.17, INNER_W * 0.20, INNER_W * 0.23]
t = Table(rows, colWidths=col_w)
tbl_style = [
("BACKGROUND", (0,0), (-1,0), TEAL),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("ALIGN", (1,0), (-1,-1), "CENTER"),
("FONTNAME", (0,1), (0,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
]
for i in range(1, len(rows), 2):
tbl_style.append(("BACKGROUND", (0,i), (-1,i), TEAL_LIGHT))
t.setStyle(TableStyle(tbl_style))
return t
# ── Duration progression table ───────────────────────────────────
def duration_table(styles):
hdr = [
Paragraph("Session", styles["tbl_hdr"]),
Paragraph("Target Duration", styles["tbl_hdr"]),
Paragraph("Actual Duration", styles["tbl_hdr"]),
Paragraph("SpO₂ Start / End", styles["tbl_hdr"]),
Paragraph("Tolerance", styles["tbl_hdr"]),
Paragraph("SLP Initials", styles["tbl_hdr"]),
]
sessions = [
["1 (Initial)", "15–20 min", "", "", "Good / Fair / Poor", ""],
["2", "+10 min", "", "", "Good / Fair / Poor", ""],
["3", "+10 min", "", "", "Good / Fair / Poor", ""],
["4", "+10 min", "", "", "Good / Fair / Poor", ""],
["5+", "→ 4–8 hrs/day target", "", "", "Good / Fair / Poor", ""],
]
col_w = [INNER_W*0.12, INNER_W*0.20, INNER_W*0.17, INNER_W*0.18, INNER_W*0.18, INNER_W*0.15]
rows = [hdr]
for r in sessions:
rows.append([Paragraph(c, styles["tbl_cell"]) for c in r])
t = Table(rows, colWidths=col_w)
tbl_style = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("ALIGN", (0,0), (-1,-1), "CENTER"),
]
for i in range(1, len(rows), 2):
tbl_style.append(("BACKGROUND", (0,i), (-1,i), LIGHT_BLUE))
t.setStyle(TableStyle(tbl_style))
return t
# ── Page numbering callback ──────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
super().showPage()
super().save()
def draw_page_number(self, page_count):
self.setFont("Helvetica", 7)
self.setFillColor(GRAY_MED)
self.drawCentredString(
PAGE_W / 2, 0.35 * inch,
f"PMV Trial Protocol — SLP Competency Checklist | Page {self._pageNumber} of {page_count} | © {2026} — For clinical use only"
)
# ── Main build ───────────────────────────────────────────────────
def build():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=letter,
topMargin=MARGIN,
bottomMargin=0.65 * inch,
leftMargin=MARGIN,
rightMargin=MARGIN,
title="PMV Trial Protocol – SLP Competency Checklist",
author="Speech-Language Pathology",
subject="Passy Muir Valve Clinical Protocol",
)
S = build_styles()
story = []
# ── ① HEADER BANNER ─────────────────────────────────────────
header_data = [
[Paragraph("PASSY MUIR VALVE (PMV) TRIAL PROTOCOL", S["title"])],
[Paragraph("SLP Competency Verification Checklist | Non-Ventilated & Ventilated Patients", S["subtitle"])],
]
header_tbl = Table(header_data, colWidths=[INNER_W])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
]))
story.append(header_tbl)
story.append(Spacer(1, 6))
# ── ② PATIENT / CLINICIAN INFO BOX ──────────────────────────
info_rows = [
[Paragraph("Patient Name / MRN:", S["small_bold"]),
Paragraph("_" * 38, S["small"]),
Paragraph("Date of Trial:", S["small_bold"]),
Paragraph("_" * 22, S["small"])],
[Paragraph("Trach Tube Brand / Size:", S["small_bold"]),
Paragraph("_" * 38, S["small"]),
Paragraph("Cuff Type (circle):", S["small_bold"]),
Paragraph("Cuffed / Cuffless / Foam", S["small"])],
[Paragraph("Evaluating SLP:", S["small_bold"]),
Paragraph("_" * 38, S["small"]),
Paragraph("Supervising SLP:", S["small_bold"]),
Paragraph("_" * 22, S["small"])],
[Paragraph("Indication:", S["small_bold"]),
Paragraph("Dysphagia / Communication / Vent weaning / Other: ______", S["small"]),
Paragraph("Session #:", S["small_bold"]),
Paragraph("_" * 10, S["small"])],
]
info_col_w = [INNER_W*0.20, INNER_W*0.33, INNER_W*0.20, INNER_W*0.27]
info_tbl = Table(info_rows, colWidths=info_col_w)
info_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GRAY_LIGHT),
("GRID", (0,0), (-1,-1), 0.3, HexColor("#CCCCCC")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
story.append(info_tbl)
story.append(Spacer(1, 6))
# ── ③ SECTION A: PRE-TRIAL CANDIDACY & CONTRAINDICATIONS ────
story.append(section_banner("A. PRE-TRIAL CANDIDACY VERIFICATION", MID_BLUE, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"<b>All ABSOLUTE contraindications must be confirmed absent before proceeding.</b>",
S["body"]))
story.append(Spacer(1, 4))
story.append(contraindications_table(S))
story.append(Spacer(1, 5))
glottic_checks = [
"FEES or nasolaryngoscopy confirms mobile vocal folds bilaterally",
"Bilateral laryngeal adductor reflex (LAR) present",
"Upper airway patent: adequate space between trach OD and tracheal wall for exhalation",
"Physician order obtained for speaking valve evaluation / trial",
"Recent suctioning frequency and secretion quality reviewed with bedside nurse",
]
story.append(Paragraph("<b>Glottic Patency & Candidacy Confirmation:</b>", S["body"]))
story.append(single_col_checks(glottic_checks, S["body"], critical_indices=[0,1,2]))
story.append(Spacer(1, 6))
# ── ④ SECTION B: PRE-PLACEMENT PREPARATION ──────────────────
story.append(section_banner("B. PRE-PLACEMENT PREPARATION", MID_BLUE, S))
story.append(Spacer(1, 3))
prep_items = [
"Equipment verified at bedside: PMV, pulse oximeter, oral suction, tracheal suction, manual resuscitation bag",
"Pilot balloon safety label applied to trach pilot line (from PMV package)",
"Patient positioned UPRIGHT (≥45°; prefer 90°) — optimizes lung expansion and reduces aspiration risk",
"Baseline vitals recorded: SpO₂ _____ HR _____ RR _____",
"Backup AAC / communication method established (letterboard / paper / eye blink / high-tech AAC)",
"Patient and family education provided: valve mechanics, airflow change, expected sensations, STOP signals",
"HME removed from circuit (incompatible with PMV)",
"No active nebulizer or aerosol treatment in progress",
]
story.append(single_col_checks(prep_items, S["body"], critical_indices=[2, 3]))
story.append(Spacer(1, 6))
# ── ⑤ SECTION C: CUFF DEFLATION & AIRWAY PATENCY ────────────
story.append(section_banner("C. CUFF DEFLATION & UPPER AIRWAY PATENCY CHECK ★ SAFETY CRITICAL", RED, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"<b>★ The cuff MUST be FULLY deflated before PMV placement. Partial deflation = air trapping = life threat.</b>",
S["stop"]))
story.append(Spacer(1, 4))
cuff_items = [
"Oral/pharyngeal suction performed (clears pooled secretions ABOVE cuff before deflation)",
"Tracheal suction performed as needed",
"SLOW, COMPLETE cuff deflation via 10 cc syringe — pilot balloon fully collapsed",
"Cuff deflation confirmed: pilot balloon flat, cuff palpation, or manometer reads 0 cm H₂O",
"Post-deflation suctioning performed — secretions that were sitting on cuff now accessible",
"UPPER AIRWAY PATENCY CONFIRMED: exhaled airflow felt at mouth/nose on patient exhalation",
" Alternative confirmation: spontaneous cough, vocalization, throat clearing, or reflexive oral movement observed",
"Finger occlusion trial performed (if patient alert): patient tolerates hub occlusion without distress",
"SpO₂ remains acceptable (≥93%) after cuff deflation alone: SpO₂ post-deflation _____",
]
story.append(single_col_checks(cuff_items, S["body"], critical_indices=[2,3,5]))
story.append(Spacer(1, 6))
# ── ⑥ SECTION D: PMV PLACEMENT ──────────────────────────────
story.append(section_banner("D. PMV PLACEMENT (NON-VENTILATED PATIENT)", MID_BLUE, S))
story.append(Spacer(1, 3))
placement_items = [
"PMV model identified: PMV 2000 (aqua) / PMV 2001 (white) — attaches directly to 15 mm hub",
" OR PMV 007 (aqua/clear) — requires adapter inline between suction catheter and flex tubing",
"Non-dominant hand stabilizes trach flange/neck plate WITHOUT downward pressure on trachea",
"Dominant hand attaches PMV to trach hub — GENTLE placement, no force",
"PMV locked with ¼-turn CLOCKWISE — confirmed seated securely",
"Valve opening orientation checked: bias-closed position confirmed (valve does not flap open at rest)",
"Post-placement SpO₂, HR, RR recorded immediately: SpO₂ _____ HR _____ RR _____",
"Patient able to vocalize or phonate — voice quality noted: ☐ Clear ☐ Wet/gurgly ☐ Hoarse ☐ Absent",
]
story.append(single_col_checks(placement_items, S["body"], critical_indices=[2,3,4]))
story.append(Spacer(1, 4))
story.append(Paragraph(
"<b>Ventilated patients — additional steps (RT co-management required):</b>", S["body_bold"]))
vent_items = [
"Baseline PIP and exhaled tidal volume recorded pre-deflation",
"Post-deflation air leak noted (confirms upper airway patency for ventilated patient)",
"PMV placed in-line: removed 15 mm adapter between inline suction catheter and 6-inch flex tubing → replaced with PMV",
"Ventilator settings adjusted by RT: volume augmentation ≤+200 cc; do NOT exceed baseline PIP",
"Sensitivity adjusted to prevent auto-cycling",
"RT and SLP monitoring simultaneously — clear roles established",
]
story.append(single_col_checks(vent_items, S["body"], critical_indices=[1,2,3]))
story.append(Spacer(1, 6))
# ── ⑦ SECTION E: MONITORING & STOP CRITERIA ─────────────────
story.append(section_banner("E. CONTINUOUS MONITORING & STOP CRITERIA", RED, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"Monitor continuously throughout trial. Remove PMV immediately if ANY criterion below is met.",
S["body"]))
story.append(Spacer(1, 4))
story.append(stop_table(S))
story.append(Spacer(1, 6))
# ── ⑧ SECTION F: SENSORY PRIMING & SWALLOWING ───────────────
story.append(section_banner("F. SENSORY PRIMING & SWALLOWING ASSESSMENT WITH PMV IN SITU", TEAL, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"With PMV restoring subglottic pressure and transglottic airflow, the following priming tasks can be performed. "
"Restored subglottic pressure improves laryngeal chemoreceptor and mechanoreceptor sensitivity, "
"enhancing glottic closure timing during swallowing (O'Connor et al., Heart & Lung 2019 — OR 0.12 for aspiration reduction, 95% CI 0.031–0.479, p=0.003).",
S["body"]))
story.append(Spacer(1, 4))
priming_items = [
"Thermal-tactile stimulation (anterior faucial arches — cold probe/spoon) — heightens swallow trigger",
"Sour bolus stimulation (small sour liquid if deemed safe) — enhances laryngeal chemoreceptor response",
"Volitional cough task — effortful cough now possible with subglottic pressure restored; assess cough strength",
"Throat clear task — volitional glottic closure trial; note success and quality",
"Sustained vocalization /a/ (3–5 sec) — confirms VF adduction; documents voice quality",
"Counting / sustained phonation — sensorimotor integration + respiratory support for speech",
"Trial swallows (if indicated by FEES/clinical findings):",
" Thin liquid — 5 mL teaspoon/syringe × 3 trials — observe for cough, wet voice, SpO₂ change",
" Nectar or pudding bolus — per clinical judgment / FEES findings",
"Penetration-Aspiration Scale (PAS) rating post-swallow trial: PAS _____",
"Patient/family instructed: valve restores taste/smell — olfaction often improved same session",
]
story.append(single_col_checks(priming_items, S["body"], critical_indices=[2,3]))
story.append(Spacer(1, 6))
# ── ⑨ SECTION G: PMV REMOVAL & POST-TRIAL ───────────────────
story.append(section_banner("G. PMV REMOVAL & POST-TRIAL TASKS", MID_BLUE, S))
story.append(Spacer(1, 3))
removal_items = [
"Trial duration achieved or STOP criteria prompted early removal — duration: _____ minutes",
"Stabilize trach neck plate with non-dominant hand",
"Remove PMV with dominant hand: GENTLE pull or twist to the RIGHT (not left — leftward unlocks inner cannula)",
"If poor tolerance / STOP criteria: re-inflate cuff; return ventilator to baseline settings",
"Post-trial vitals recorded: SpO₂ _____ HR _____ RR _____ (confirm return to baseline)",
"Post-trial suction as needed",
"Ventilator settings returned to baseline by RT (if ventilated)",
"PMV cleaning: pure mild soap + tepid water → thorough rinse in COOL water → air dry in open container",
" ★ No hot water / bleach / peroxide / vinegar / alcohol / autoclaving — damages silastic membrane",
"PMV labeled with patient ID + date opened; single-patient use only; replace if noisy/vibrating or after ~2 months",
"Warning signs placed: 'CUFF MUST BE DEFLATED FOR PMV USE' — at head of bed AND on pilot line",
]
story.append(single_col_checks(removal_items, S["body"], critical_indices=[2,8]))
story.append(Spacer(1, 6))
# ── ⑩ SECTION H: DURATION PROGRESSION LOG ───────────────────
story.append(section_banner("H. SESSION DURATION PROGRESSION LOG", MID_BLUE, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"Begin 15–20 min (Session 1). Increase by ~10 min each subsequent session toward goal of 4–8 hrs/day.",
S["body"]))
story.append(Spacer(1, 4))
story.append(duration_table(S))
story.append(Spacer(1, 6))
# ── ⑪ SECTION I: DOCUMENTATION CHECKLIST ────────────────────
story.append(section_banner("I. DOCUMENTATION CHECKLIST (Post-Trial)", TEAL, S))
story.append(Spacer(1, 3))
doc_items_left = [
"Pre/post vitals (SpO₂, HR, RR)",
"Trach tube brand, size, cuff type/status",
"Glottic patency confirmation method",
"PMV model used + placement technique",
"Duration tolerated",
]
doc_items_right = [
"Voice quality (wet/dry/hoarse/clear/absent)",
"Penetration/Aspiration Scale (PAS) rating",
"Any STOP criteria triggered + action taken",
"Patient/family education delivered",
"Plan: next session duration target + goals",
]
story.append(two_col_checks(doc_items_left, doc_items_right, S["body"]))
story.append(Spacer(1, 6))
# ── ⑫ SECTION J: SLP COMPETENCY SIGN-OFF ────────────────────
story.append(section_banner("J. SLP COMPETENCY SIGN-OFF", TEAL, S))
story.append(Spacer(1, 3))
story.append(Paragraph(
"All new SLPs or SLPs new to trach/vent populations must be observed by a senior clinician until competency is established. "
"Mark each item with date observed. Supervisor signs upon satisfactory performance.",
S["body"]))
story.append(Spacer(1, 4))
story.append(competency_table(S))
story.append(Spacer(1, 5))
sig_rows = [
[Paragraph("Clinician Signature:", S["small_bold"]),
Paragraph("_" * 40, S["small"]),
Paragraph("Date Competency Achieved:", S["small_bold"]),
Paragraph("_" * 20, S["small"])],
[Paragraph("Supervisor Signature:", S["small_bold"]),
Paragraph("_" * 40, S["small"]),
Paragraph("Credentials / Role:", S["small_bold"]),
Paragraph("_" * 20, S["small"])],
]
sig_tbl = Table(sig_rows, colWidths=[INNER_W*0.20, INNER_W*0.33, INNER_W*0.25, INNER_W*0.22])
sig_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GRAY_LIGHT),
("GRID", (0,0), (-1,-1), 0.3, HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sig_tbl)
story.append(Spacer(1, 8))
# ── ⑬ EVIDENCE FOOTER ────────────────────────────────────────
story.append(HRFlowable(width=INNER_W, thickness=0.5, color=GRAY_MED))
story.append(Spacer(1, 4))
story.append(Paragraph(
"<b>Evidence Base:</b> O'Connor LR, Morris NR, Paratz J. Physiological and clinical outcomes associated with use of one-way speaking valves "
"on tracheostomised patients: A systematic review. <i>Heart Lung.</i> 2019;48(4):356–364. PMID 30573194 "
"[Systematic Review — OR 0.12 for aspiration reduction with speaking valve in situ, 95% CI 0.031–0.479, p=0.003]. "
"Cummings Otolaryngology (7th ed.): PMV aids swallowing and prevents aspiration by increasing subglottic pressure. "
"Tintinalli's Emergency Medicine (9th ed.): PMV one-way valve physiology and contraindications.",
S["evidence"]))
story.append(Spacer(1, 3))
story.append(Paragraph(
"This checklist is for clinical and educational use. It does not replace institutional policy, physician orders, or direct clinical judgment. "
"Always follow your facility's tracheostomy and ventilator management protocols.",
S["evidence"]))
doc.build(story, canvasmaker=NumberedCanvas)
print(f"PDF written to: {OUTPUT}")
if __name__ == "__main__":
build()
cd /home/daytona/workspace/pmv-protocol && python3 build_pmv_pdf.py 2>&1