~/ephedrine-notes/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
OUTPUT = "/home/daytona/workspace/ephedrine-notes/Ephedrine_Drug_Note.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Ephedrine Drug Note",
author="Orris Medical Assistant"
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
"TitleStyle", parent=styles["Title"],
fontSize=22, textColor=colors.HexColor("#1a3a5c"),
spaceAfter=6, spaceBefore=0, leading=28
)
subtitle_style = ParagraphStyle(
"SubTitle", parent=styles["Normal"],
fontSize=11, textColor=colors.HexColor("#4a6fa5"),
spaceAfter=12, alignment=TA_CENTER
)
h1_style = ParagraphStyle(
"H1", parent=styles["Heading1"],
fontSize=14, textColor=colors.white,
backColor=colors.HexColor("#1a3a5c"),
spaceBefore=14, spaceAfter=6, leading=20,
leftIndent=-0.3*cm, rightIndent=-0.3*cm,
borderPadding=(4, 6, 4, 6)
)
h2_style = ParagraphStyle(
"H2", parent=styles["Heading2"],
fontSize=12, textColor=colors.HexColor("#1a3a5c"),
spaceBefore=10, spaceAfter=4, leading=16,
borderPadding=(2, 4, 2, 4)
)
h3_style = ParagraphStyle(
"H3", parent=styles["Heading3"],
fontSize=11, textColor=colors.HexColor("#c0392b"),
spaceBefore=8, spaceAfter=3, leading=14
)
body_style = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=9.5, leading=14, spaceAfter=4,
textColor=colors.HexColor("#222222"), alignment=TA_JUSTIFY
)
quote_style = ParagraphStyle(
"Quote", parent=styles["Normal"],
fontSize=9, leading=13, spaceAfter=4,
textColor=colors.HexColor("#555555"),
leftIndent=1*cm, rightIndent=0.5*cm,
borderColor=colors.HexColor("#4a6fa5"),
borderWidth=2, borderPadding=(4, 8, 4, 8),
backColor=colors.HexColor("#f0f4fa"),
fontName="Helvetica-Oblique"
)
source_style = ParagraphStyle(
"Source", parent=styles["Normal"],
fontSize=8.5, leading=12, spaceAfter=3,
textColor=colors.HexColor("#27ae60"),
fontName="Helvetica-BoldOblique"
)
bullet_style = ParagraphStyle(
"Bullet", parent=styles["Normal"],
fontSize=9.5, leading=13, spaceAfter=3,
leftIndent=0.6*cm, bulletIndent=0.2*cm,
textColor=colors.HexColor("#222222")
)
footer_style = ParagraphStyle(
"Footer", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#888888"),
alignment=TA_CENTER
)
def section_header(text):
return Paragraph(f" {text}", h1_style)
def sub_header(text):
return Paragraph(text, h2_style)
def adv_header(text):
return Paragraph(text, h3_style)
def body(text):
return Paragraph(text, body_style)
def quote(text):
return Paragraph(text, quote_style)
def source(text):
return Paragraph(f"๐ Source: {text}", source_style)
def bullet(text):
return Paragraph(f"โข {text}", bullet_style)
def spacer(h=0.2):
return Spacer(1, h*cm)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#cccccc"), spaceAfter=4, spaceBefore=4)
story = []
# โโ TITLE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(Paragraph("๐ Ephedrine โ Drug Note", title_style))
story.append(Paragraph("Mechanism of Action ยท Adverse Effects (with Sources & Reasons) ยท MOA Clarification", subtitle_style))
story.append(hr())
story.append(spacer(0.3))
# โโ SECTION 1: Basic Classification โโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("1. Basic Classification"))
story.append(spacer(0.2))
basic_data = [
["Property", "Detail"],
["Drug Class", "Mixed-acting sympathomimetic (adrenergic agonist)"],
["Chemical Type", "Noncatechol phenylisopropylamine"],
["Mechanism", "Direct ฮฑ & ฮฒ receptor agonist + indirect (NE release & reuptake inhibition)"],
["Origin", "Ephedra sinica (Ma huang); used in China >2000 years; Western medicine since 1924"],
]
basic_table = Table(basic_data, colWidths=[4*cm, 12.5*cm])
basic_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1a3a5c")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#e8eef7")),
("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f5f8ff")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
]))
story.append(basic_table)
story.append(spacer(0.3))
# โโ SECTION 2: MOA โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("2. Mechanism of Action (MOA) โ Detailed"))
story.append(spacer(0.2))
story.append(sub_header("2a. Indirect Action (Predominant)"))
story.append(quote(
'"Endocytosis of ephedrine into alpha- and beta-adrenoceptor presynaptic postganglionic nerve terminals '
'displaces norepinephrine from the synaptic vesicles. The displaced norepinephrine is then released to '
'activate the corresponding postsynaptic receptors to cause arterial and venous vasoconstriction and '
'increased myocardial contractility."'
))
story.append(source("Barash, Cullen & Stoelting's Clinical Anesthesia, 9e"))
story.append(spacer(0.2))
story.append(sub_header("2b. Direct Action"))
story.append(quote(
'"Ephedrine is an agonist at both ฮฑ and ฮฒ receptors; in addition, it enhances release of NE from '
'sympathetic neurons and thus is a mixed-acting sympathomimetic."'
))
story.append(source("Goodman & Gilman's Pharmacological Basis of Therapeutics"))
story.append(spacer(0.2))
story.append(sub_header("2c. Not Metabolized by COMT/MAO"))
story.append(quote(
'"Ephedrine and pseudoephedrine are not catecholamines and are poor substrates for COMT and MAO. '
'Therefore, these drugs have a long duration of action."'
))
story.append(source("Lippincott Illustrated Reviews: Pharmacology"))
story.append(spacer(0.2))
story.append(sub_header("2d. Receptor Action Summary"))
moa_data = [
["Receptor", "Location", "Effect"],
["ฮฑโ (direct)", "Vascular smooth muscle, bladder neck", "Vasoconstriction, โ BP, urinary resistance"],
["ฮฒโ (direct)", "Heart", "โ HR, โ contractility, โ cardiac output"],
["ฮฒโ (direct)", "Bronchial smooth muscle", "Bronchodilation"],
["CNS", "Brain", "Stimulation, โ alertness, โ MAC"],
["Indirect (all)", "Presynaptic nerve terminals", "NE displacement โ floods synapse"],
]
moa_table = Table(moa_data, colWidths=[3.5*cm, 6.5*cm, 6.5*cm])
moa_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#4a6fa5")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f0f4fa")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
story.append(moa_table)
story.append(spacer(0.4))
# โโ SECTION 3: Adverse Effects โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("3. Adverse Effects โ Source + Mechanistic Reason"))
story.append(spacer(0.2))
# --- 3.1 Hypertension ---
story.append(adv_header("๐ด 3.1 Hypertension"))
story.append(quote(
'"Untoward effects of ephedrine include hypertension and insomnia."'
))
story.append(source("Goodman & Gilman's Pharmacological Basis of Therapeutics"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("ฮฑโ agonism โ arterial and venous vasoconstriction โ โ peripheral vascular resistance"))
story.append(bullet("ฮฒโ agonism โ โ heart rate + โ cardiac output"))
story.append(bullet("Noncatechol structure โ not degraded by COMT/MAO โ sustained pressor effect"))
story.append(spacer(0.3))
# --- 3.2 Tachyphylaxis ---
story.append(adv_header("๐ก 3.2 Tachyphylaxis (Rapid Tolerance with Repeat Dosing)"))
story.append(quote(
'"Tachyphylaxis to ephedrine\'s hemodynamic effects occurs with repetitive administration of the drug '
'because presynaptic norepinephrine stores are rapidly depleted and ephedrine is then released from '
'synaptic vesicles as a false neurotransmitter. In contrast, tachyphylaxis does not occur with epinephrine '
'because it directly stimulates alpha- and beta-adrenoceptors independent of a displacement and release mechanism."'
))
story.append(source("Barash, Cullen & Stoelting's Clinical Anesthesia, 9e"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("Indirect mechanism depends entirely on NE vesicular stores"))
story.append(bullet("Repeated dosing exhausts these NE stores"))
story.append(bullet("Ephedrine itself enters vesicles โ released as a 'false neurotransmitter' with minimal receptor activity"))
story.append(bullet("Unlike ephedrine, epinephrine directly binds receptors โ no tachyphylaxis"))
story.append(spacer(0.3))
# --- 3.3 Insomnia / CNS stimulation ---
story.append(adv_header("๐ก 3.3 Insomnia / CNS Stimulation"))
story.append(quote(
'"Ephedrine produces a mild stimulation of the CNS. This increases alertness, decreases fatigue, '
'and prevents sleep."'
))
story.append(source("Lippincott Illustrated Reviews: Pharmacology"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("Noncatecholamine structure โ crosses blood-brain barrier (lipophilic)"))
story.append(bullet("Promotes NE and dopamine release in CNS arousal pathways โ insomnia, hyperactivity, headache"))
story.append(bullet("Also raises minimum alveolar concentration (MAC) of anaesthetic agents"))
story.append(spacer(0.3))
# --- 3.4 Urinary Retention ---
story.append(adv_header("๐ก 3.4 Urinary Retention (especially in BPH)"))
story.append(quote(
'"Stimulation of the ฮฑ adrenergic receptors of smooth muscle cells in the bladder base may increase '
'the resistance to the outflow of urine... the drug may cause urinary retention, particularly in men '
'with benign prostatic hyperplasia (BPH)."'
))
story.append(source("Goodman & Gilman's Pharmacological Basis of Therapeutics"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("ฮฑโ activation in bladder neck / internal urethral sphincter โ smooth muscle contraction โ โ urethral resistance"))
story.append(bullet("In BPH, prostate already compresses urethra โ added ฮฑโ stimulation causes acute retention"))
story.append(spacer(0.3))
# --- 3.5 Cardiovascular Toxicity ---
story.append(adv_header("๐ด 3.5 Cardiovascular Toxicity / Stroke / Death"))
story.append(quote(
'"Usual or higher-than-recommended doses may cause important adverse effects in susceptible individuals, '
'especially in patients with underlying cardiovascular disease... There can be considerable variability '
'in the content of ephedrine in these preparations... leading to significant toxicity and death."'
))
story.append(source("Goodman & Gilman's Pharmacological Basis of Therapeutics"))
story.append(quote('"Ephedra has been associated with strokes and deaths in adolescent users."'))
story.append(source("Rosen's Emergency Medicine"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("Massive sympathomimetic surge โ severe hypertension โ haemorrhagic stroke"))
story.append(bullet("Intense ฮฒโ stimulation โ arrhythmias, myocardial infarction"))
story.append(bullet("Patients with unrecognised hypertension or CAD are at highest risk"))
story.append(bullet("Variable herbal content โ inadvertent overdose"))
story.append(spacer(0.3))
# --- 3.6 Arrhythmias ---
story.append(adv_header("๐ 3.6 Arrhythmias"))
story.append(source("Lippincott Illustrated Reviews: Pharmacology (illustrated as key adverse effect)"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("ฮฒโ overstimulation โ โ automaticity in ectopic cardiac foci โ ventricular tachycardia, atrial arrhythmias"))
story.append(bullet("Hypertension-induced โ cardiac wall stress further elevates arrhythmia risk"))
story.append(spacer(0.3))
# --- 3.7 Drug Interactions ---
story.append(adv_header("๐ 3.7 Drug Interactions (Cocaine, Reserpine)"))
story.append(quote(
'"Predictably, drugs that block the ephedrine uptake into adrenergic nerves (e.g., cocaine) and those '
'that deplete norepinephrine reserves (e.g., reserpine) attenuate its cardiovascular effects."'
))
story.append(source("Barash, Cullen & Stoelting's Clinical Anesthesia, 9e"))
story.append(body("<b>Mechanistic Reason:</b>"))
story.append(bullet("Cocaine blocks NET (norepinephrine transporter) โ ephedrine cannot enter nerve terminal โ indirect action abolished"))
story.append(bullet("Reserpine depletes NE vesicular stores โ nothing left to displace โ indirect action abolished"))
story.append(spacer(0.4))
# โโ SECTION 4: MOA Clarification Summary Table โโโโโโโโโโโโโโโ
story.append(section_header("4. MOA Clarification Summary Table"))
story.append(spacer(0.2))
moa_summary = [
["MOA Component", "Mechanism", "Consequence"],
["NE displacement\n(indirect, predominant)", "Enters vesicles, displaces NE โ NE floods synapse", "โ BP, โ HR, vasoconstriction"],
["Direct ฮฑโ agonism", "Binds ฮฑโ on vascular smooth muscle", "Vasoconstriction โ โ BP"],
["Direct ฮฒโ agonism", "Binds ฮฒโ on myocardium", "โ HR, โ contractility"],
["Direct ฮฒโ agonism", "Binds ฮฒโ on bronchi", "Bronchodilation"],
["CNS penetration", "Crosses BBB (noncatecholamine, lipophilic)", "Stimulation, insomnia, โ MAC"],
["Not COMT/MAO substrate", "Lacks catechol ring structure", "Prolonged duration of action"],
["Tachyphylaxis", "NE store depletion + false neurotransmitter\nupon repeat dosing", "Loss of effect with repeat doses"],
]
moa_sum_table = Table(moa_summary, colWidths=[4*cm, 6.5*cm, 6*cm])
moa_sum_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1a3a5c")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#e8eef7")),
("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f5f8ff")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("WORDWRAP", (0, 0), (-1, -1), True),
]))
story.append(moa_sum_table)
story.append(spacer(0.5))
# โโ SECTION 5: Pharmacokinetics โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("5. Pharmacokinetics"))
story.append(spacer(0.2))
pk_data = [
["Parameter", "Detail"],
["Route", "Oral or IV (bolus in anaesthesia)"],
["Bioavailability", "Excellent oral absorption"],
["Half-life (tยฝ)", "3โ6 hours"],
["Duration", "Hours (much longer than catecholamines)"],
["Metabolism", "Poor COMT/MAO substrate; largely excreted unchanged in urine"],
["CNS penetration", "Yes โ crosses blood-brain barrier"],
]
pk_table = Table(pk_data, colWidths=[4*cm, 12.5*cm])
pk_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1a3a5c")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#e8eef7")),
("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f5f8ff")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
story.append(pk_table)
story.append(spacer(0.4))
# โโ SECTION 6: Clinical Uses โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("6. Clinical Uses"))
story.append(spacer(0.2))
clinical_data = [
["Indication", "Notes"],
["Anaesthesia-induced hypotension", "Most common intraoperative use; temporising measure"],
["Spinal/neuraxial anaesthesia hypotension", "Replaced by phenylephrine in obstetrics (better fetal pH profile)"],
["Bronchodilation (historical)", "Replaced by ฮฒโ-selective agonists in asthma"],
["Urinary continence", "ฮฑโ tightens bladder neck; also causes retention in BPH"],
["Nasal decongestant (historical)", "Now replaced by safer alternatives"],
]
clinical_table = Table(clinical_data, colWidths=[5.5*cm, 11*cm])
clinical_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#4a6fa5")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f0f4fa")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
story.append(clinical_table)
story.append(spacer(0.3))
story.append(sub_header("Dosing (Anaesthesia)"))
story.append(bullet("Adults: IV bolus 2.5โ10 mg"))
story.append(bullet("Children: IV bolus 0.1 mg/kg"))
story.append(bullet("Available as 1 mL ampules containing 25 or 50 mg"))
story.append(bullet("Subsequent doses may need to be increased to overcome tachyphylaxis"))
story.append(spacer(0.4))
# โโ SECTION 7: Ephedrine vs Epinephrine โโโโโโโโโโโโโโโโโโโโโโ
story.append(section_header("7. Ephedrine vs. Epinephrine โ Comparison"))
story.append(spacer(0.2))
compare_data = [
["Feature", "Ephedrine", "Epinephrine"],
["Potency", "Much less potent", "Highly potent"],
["Duration", "Hours", "Minutes"],
["Action", "Mixed (direct + indirect)", "Direct only"],
["CNS stimulation", "Yes (crosses BBB)", "Minimal"],
["Tachyphylaxis", "Yes (NE store depletion)", "No (direct action)"],
["Route", "Oral or IV", "IV / IM / SC"],
["COMT/MAO metabolism", "Poor substrate โ longer tยฝ", "Rapidly degraded"],
]
compare_table = Table(compare_data, colWidths=[4.5*cm, 6*cm, 6*cm])
compare_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1a3a5c")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#e8eef7")),
("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f5f8ff")]),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("PADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
story.append(compare_table)
story.append(spacer(0.5))
# โโ SOURCES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
story.append(hr())
story.append(sub_header("Primary Sources"))
story.append(bullet("Goodman & Gilman's The Pharmacological Basis of Therapeutics โ MOA, adverse effects, ADME"))
story.append(bullet("Barash, Cullen & Stoelting's Clinical Anesthesia, 9e โ Tachyphylaxis mechanism, drug interactions, anaesthesia use"))
story.append(bullet("Lippincott Illustrated Reviews: Pharmacology โ Mixed-action classification, CNS effects, arrhythmias"))
story.append(bullet("Morgan & Mikhail's Clinical Anesthesiology, 7e โ Cardiovascular effects, obstetric anaesthesia"))
story.append(bullet("Katzung's Basic and Clinical Pharmacology, 16e โ Herbal use, historical context"))
story.append(bullet("Rosen's Emergency Medicine โ Ephedra toxicity, stroke, herbal dangers"))
story.append(spacer(0.3))
story.append(hr())
story.append(Paragraph("Generated by Orris Medical Assistant ยท April 2026", footer_style))
# Build
doc.build(story)
print(f"PDF saved to: {OUTPUT}")