~/dental-guide/dental_guide.py
#!/usr/bin/env python3
"""Generate a comprehensive dental ward guide PDF for MBBS 3rd year students."""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, ListFlowable, ListItem, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate, Frame
OUTPUT_PATH = "/home/daytona/workspace/dental-guide/Dental_Ward_Guide_MBBS_3rdYear.pdf"
# ββββββββββββββββββββββββββββββββββββββββββββββ
# COLORS
# ββββββββββββββββββββββββββββββββββββββββββββββ
DARK_BLUE = colors.HexColor("#1B3A6B")
MED_BLUE = colors.HexColor("#2563EB")
LIGHT_BLUE = colors.HexColor("#DBEAFE")
TEAL = colors.HexColor("#0D9488")
LIGHT_TEAL = colors.HexColor("#CCFBF1")
ORANGE = colors.HexColor("#EA580C")
LIGHT_ORANGE= colors.HexColor("#FED7AA")
RED = colors.HexColor("#DC2626")
LIGHT_RED = colors.HexColor("#FEE2E2")
GREEN = colors.HexColor("#16A34A")
LIGHT_GREEN = colors.HexColor("#DCFCE7")
YELLOW = colors.HexColor("#CA8A04")
LIGHT_YELLOW= colors.HexColor("#FEF9C3")
GRAY_BG = colors.HexColor("#F8FAFC")
DARK_GRAY = colors.HexColor("#374151")
MID_GRAY = colors.HexColor("#6B7280")
LIGHT_GRAY = colors.HexColor("#E5E7EB")
WHITE = colors.white
PAGE_W, PAGE_H = A4
MARGIN = 1.8 * cm
# ββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE NUMBERING
# ββββββββββββββββββββββββββββββββββββββββββββββ
def add_page_number(canvas_obj, doc):
canvas_obj.saveState()
canvas_obj.setFont("Helvetica", 8)
canvas_obj.setFillColor(MID_GRAY)
page_num = canvas_obj.getPageNumber()
canvas_obj.drawString(MARGIN, 1.0 * cm, "Dental Ward Guide | MBBS 3rd Year")
canvas_obj.drawRightString(PAGE_W - MARGIN, 1.0 * cm, f"Page {page_num}")
# top line
canvas_obj.setStrokeColor(LIGHT_GRAY)
canvas_obj.setLineWidth(0.5)
canvas_obj.line(MARGIN, PAGE_H - 1.4*cm, PAGE_W - MARGIN, PAGE_H - 1.4*cm)
# bottom line
canvas_obj.line(MARGIN, 1.4*cm, PAGE_W - MARGIN, 1.4*cm)
canvas_obj.restoreState()
# ββββββββββββββββββββββββββββββββββββββββββββββ
# STYLES
# ββββββββββββββββββββββββββββββββββββββββββββββ
def make_styles():
base = getSampleStyleSheet()
styles = {}
styles["cover_title"] = ParagraphStyle(
"cover_title", fontName="Helvetica-Bold", fontSize=28,
textColor=WHITE, alignment=TA_CENTER, spaceAfter=8, leading=34)
styles["cover_sub"] = ParagraphStyle(
"cover_sub", fontName="Helvetica", fontSize=14,
textColor=colors.HexColor("#BFDBFE"), alignment=TA_CENTER, spaceAfter=6)
styles["cover_note"] = ParagraphStyle(
"cover_note", fontName="Helvetica-Oblique", fontSize=11,
textColor=colors.HexColor("#E0F2FE"), alignment=TA_CENTER)
styles["h1"] = ParagraphStyle(
"h1", fontName="Helvetica-Bold", fontSize=16,
textColor=WHITE, spaceBefore=6, spaceAfter=4, leading=20)
styles["h2"] = ParagraphStyle(
"h2", fontName="Helvetica-Bold", fontSize=13,
textColor=DARK_BLUE, spaceBefore=10, spaceAfter=4, leading=17)
styles["h3"] = ParagraphStyle(
"h3", fontName="Helvetica-Bold", fontSize=11,
textColor=TEAL, spaceBefore=8, spaceAfter=3, leading=14)
styles["body"] = ParagraphStyle(
"body", fontName="Helvetica", fontSize=10,
textColor=DARK_GRAY, leading=15, spaceAfter=4, alignment=TA_JUSTIFY)
styles["bullet"] = ParagraphStyle(
"bullet", fontName="Helvetica", fontSize=10,
textColor=DARK_GRAY, leading=14, spaceAfter=2,
leftIndent=12, bulletIndent=0)
styles["bold_bullet"] = ParagraphStyle(
"bold_bullet", fontName="Helvetica-Bold", fontSize=10,
textColor=DARK_BLUE, leading=14, spaceAfter=2, leftIndent=12)
styles["small"] = ParagraphStyle(
"small", fontName="Helvetica", fontSize=8.5,
textColor=MID_GRAY, leading=12, spaceAfter=2)
styles["table_header"] = ParagraphStyle(
"table_header", fontName="Helvetica-Bold", fontSize=9,
textColor=WHITE, alignment=TA_CENTER)
styles["table_cell"] = ParagraphStyle(
"table_cell", fontName="Helvetica", fontSize=9,
textColor=DARK_GRAY, leading=12)
styles["table_cell_bold"] = ParagraphStyle(
"table_cell_bold", fontName="Helvetica-Bold", fontSize=9,
textColor=DARK_BLUE, leading=12)
styles["warning"] = ParagraphStyle(
"warning", fontName="Helvetica-Bold", fontSize=10,
textColor=RED, leading=14)
styles["tip"] = ParagraphStyle(
"tip", fontName="Helvetica-Oblique", fontSize=9.5,
textColor=TEAL, leading=13)
styles["toc_entry"] = ParagraphStyle(
"toc_entry", fontName="Helvetica", fontSize=10,
textColor=DARK_GRAY, leading=16, leftIndent=10)
styles["toc_section"] = ParagraphStyle(
"toc_section", fontName="Helvetica-Bold", fontSize=11,
textColor=DARK_BLUE, leading=18, spaceBefore=4)
return styles
# ββββββββββββββββββββββββββββββββββββββββββββββ
# HELPER: colored box
# ββββββββββββββββββββββββββββββββββββββββββββββ
def section_header_table(title_para, bg_color=DARK_BLUE, w=None):
"""Full-width colored header bar."""
usable = PAGE_W - 2 * MARGIN
t = Table([[title_para]], colWidths=[w or usable])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg_color),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
]))
return t
def callout_box(content_rows, bg=LIGHT_BLUE, border=MED_BLUE, w=None):
"""Colored callout box."""
usable = PAGE_W - 2 * MARGIN
t = Table(content_rows, colWidths=[w or usable])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1.5, border),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
("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_col_table(rows, col_headers, col_widths, hdr_bg=DARK_BLUE):
"""Standard two-or-more-column table."""
header = [Paragraph(h, ParagraphStyle("th", fontName="Helvetica-Bold",
fontSize=9, textColor=WHITE, alignment=TA_CENTER)) for h in col_headers]
data = [header]
for row in rows:
data.append([Paragraph(str(c), ParagraphStyle("td", fontName="Helvetica",
fontSize=9, textColor=DARK_GRAY, leading=12)) for c in row])
t = Table(data, colWidths=col_widths, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), hdr_bg),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GRAY_BG]),
("GRID", (0,0), (-1,-1), 0.5, LIGHT_GRAY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
return t
# ββββββββββββββββββββββββββββββββββββββββββββββ
# DOCUMENT BUILDER
# ββββββββββββββββββββββββββββββββββββββββββββββ
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=2.0*cm, bottomMargin=2.0*cm,
title="Dental Ward Guide - MBBS 3rd Year",
author="Orris Medical AI",
subject="Dental History Taking & Ward Basics"
)
S = make_styles()
story = []
usable = PAGE_W - 2 * MARGIN
# ββββββββββββββββββββββββββββββββββββββββββββ
# COVER PAGE
# ββββββββββββββββββββββββββββββββββββββββββββ
# Blue gradient-like cover using a table
cover_data = [
[Paragraph("π¦·", ParagraphStyle("emoji", fontSize=48, alignment=TA_CENTER, spaceAfter=4))],
[Spacer(1, 0.3*cm)],
[Paragraph("DENTAL WARD", S["cover_title"])],
[Paragraph("SURVIVAL GUIDE", ParagraphStyle("ct2", fontName="Helvetica-Bold",
fontSize=26, textColor=colors.HexColor("#93C5FD"), alignment=TA_CENTER))],
[Spacer(1, 0.5*cm)],
[Paragraph("For MBBS 3rd Year Students", S["cover_sub"])],
[Paragraph("First Day Posting Edition", S["cover_sub"])],
[Spacer(1, 1.2*cm)],
[Paragraph("History Taking | Examination | Common Conditions | Drug Chart Tips",
S["cover_note"])],
[Spacer(1, 1.5*cm)],
[Paragraph("Prepared with content from: Rosen's Emergency Medicine, Scott-Brown's ORL,",
ParagraphStyle("src", fontName="Helvetica", fontSize=8.5,
textColor=colors.HexColor("#93C5FD"), alignment=TA_CENTER))],
[Paragraph("Cummings Otolaryngology, Robbins Pathology & Harrison's Medicine",
ParagraphStyle("src2", fontName="Helvetica", fontSize=8.5,
textColor=colors.HexColor("#93C5FD"), alignment=TA_CENTER))],
[Spacer(1, 0.8*cm)],
[Paragraph("July 2026", ParagraphStyle("date", fontName="Helvetica-Oblique",
fontSize=10, textColor=colors.HexColor("#BFDBFE"), alignment=TA_CENTER))],
]
cover = Table(cover_data, colWidths=[usable])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 20),
("RIGHTPADDING", (0,0), (-1,-1), 20),
("ROUNDEDCORNERS", [8,8,8,8]),
]))
story.append(Spacer(1, 2*cm))
story.append(cover)
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# TABLE OF CONTENTS
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(Paragraph("TABLE OF CONTENTS", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.4*cm))
toc_items = [
("1", "Dental History Taking (Step-by-Step)", "3"),
("2", "Clinical Examination of the Oral Cavity", "5"),
("3", "Dental Pain - Differential Diagnosis", "7"),
("4", "Common Dental Conditions You Will See", "8"),
("5", "Dental Charting & Tooth Numbering", "11"),
("6", "Ward Essentials - Prescriptions & Investigations", "12"),
("7", "Key Drug Doses & Analgesic Ladder", "13"),
("8", "Quick Reference Cheat Sheet", "14"),
]
toc_data = [
[Paragraph(f"Section {n}", S["toc_section"]),
Paragraph(title, S["toc_entry"]),
Paragraph(f"Pg {pg}", ParagraphStyle("pg", fontName="Helvetica-Bold",
fontSize=10, textColor=MED_BLUE, alignment=TA_CENTER))]
for n, title, pg in toc_items
]
toc_table = Table(toc_data, colWidths=[2.8*cm, 11.5*cm, 1.5*cm])
toc_table.setStyle(TableStyle([
("ROWBACKGROUNDS", (0,0), (-1,-1), [WHITE, GRAY_BG]),
("LINEBELOW", (0,0), (-1,-1), 0.5, LIGHT_GRAY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(toc_table)
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 1: DENTAL HISTORY TAKING
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 1: DENTAL HISTORY TAKING", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Dental history taking follows the same SOAP framework as general medicine, "
"but with specialized questions targeting the teeth, gums, jaw, and related "
"structures. Always take a full history before examining the patient.",
S["body"]))
story.append(Spacer(1, 0.3*cm))
# 1.1 Chief Complaint
story.append(section_header_table(
Paragraph("1.1 Chief Complaint (CC)", S["h1"]), TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(callout_box([[Paragraph(
"<b>Ask:</b> \"What brings you to the dental clinic today?\"<br/>"
"Record in the patient's own words. Common dental complaints include:<br/>"
"Toothache / Pain | Swelling of face or gums | Bleeding gums | "
"Broken/loose tooth | Bad breath (halitosis) | Difficulty chewing or opening mouth | "
"Sores or ulcers in the mouth | Sensitivity to hot/cold",
S["body"])]],
bg=LIGHT_TEAL, border=TEAL))
story.append(Spacer(1, 0.3*cm))
# 1.2 History of Presenting Illness
story.append(Paragraph("1.2 History of Presenting Illness (HPI) - Use SOCRATES", S["h2"]))
story.append(Paragraph(
"For any dental pain, use the SOCRATES mnemonic:", S["body"]))
socrates_data = [
["S", "Site", "Which tooth/area? Upper or lower jaw? Right or left side?"],
["O", "Onset", "When did it start? Sudden or gradual? After dental procedure?"],
["C", "Character", "Sharp? Dull? Throbbing? Burning? Constant or intermittent?"],
["R", "Radiation", "Does pain spread to ear, jaw, temple, neck, or opposite side?"],
["A", "Associations", "Swelling? Fever? Pus? Bad taste in mouth? Difficulty swallowing?"],
["T", "Time course", "How long does the pain last? Getting better or worse?"],
["E", "Exacerbating / Relieving",
"Worse with: hot/cold food? Biting? Sweet food? Better with analgesics?"],
["S", "Severity", "Pain score 0-10. Does it wake you from sleep?"],
]
t = Table(socrates_data, colWidths=[0.8*cm, 3.5*cm, usable-0.8*cm-3.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), DARK_BLUE),
("TEXTCOLOR", (0,0), (0,-1), WHITE),
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (1,0), (-1,-1), [LIGHT_BLUE, WHITE]),
("FONTNAME", (1,0), (1,-1), "Helvetica-Bold"),
("TEXTCOLOR", (1,0), (1,-1), DARK_BLUE),
("GRID", (0,0), (-1,-1), 0.5, LIGHT_GRAY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(t)
story.append(Spacer(1, 0.3*cm))
# Important Clinical Distinctions box
story.append(callout_box([[Paragraph(
"<b>KEY CLINICAL DISTINCTION (from Rosen's Emergency Medicine):</b><br/>"
"β’ <b>Reversible pulpitis:</b> Pain triggered by hot/cold; stops when stimulus removed<br/>"
"β’ <b>Irreversible pulpitis:</b> Spontaneous throbbing pain; lingers after stimulus; "
"may radiate<br/>"
"β’ <b>Periapical abscess:</b> Exquisite tenderness to percussion of the tooth",
S["body"])]],
bg=LIGHT_YELLOW, border=YELLOW))
story.append(Spacer(1, 0.3*cm))
# 1.3 Past Dental History
story.append(Paragraph("1.3 Past Dental History (PDH)", S["h2"]))
past_dental = [
("Previous dental treatment", "Fillings, root canals, extractions, orthodontics, dentures"),
("Last dental visit", "When was it? Why?"),
("Dental trauma", "Any previous injury to teeth or jaw?"),
("Anaesthetic reactions", "Any problems with local anaesthesia (LA) before?"),
("Oral hygiene habits", "How often do they brush? Floss? Mouthwash?"),
("Fluoride exposure", "Fluoride toothpaste? Fluoride supplements in childhood?"),
]
pdh_table = two_col_table(
past_dental,
["Question Area", "What to Ask"],
[5.5*cm, usable - 5.5*cm]
)
story.append(pdh_table)
story.append(Spacer(1, 0.3*cm))
# 1.4 Past Medical History
story.append(Paragraph("1.4 Past Medical History (PMH) - Dental Relevance", S["h2"]))
story.append(Paragraph(
"Several systemic conditions directly affect dental management and examination findings:",
S["body"]))
pmh_data = [
["Diabetes mellitus", "Impaired wound healing, increased susceptibility to infection, "
"poor periodontal health"],
["Hypertension", "Many antihypertensives cause gingival hyperplasia (e.g. amlodipine, nifedipine)"],
["Cardiac disease / valve replacement",
"Infective endocarditis prophylaxis - ask about rheumatic heart disease, "
"prosthetic valves (amoxicillin 2g before procedure)"],
["Bleeding disorders / anticoagulants",
"Warfarin, heparin, aspirin, clopidogrel - risk of post-extraction bleeding"],
["Bisphosphonate therapy",
"Osteonecrosis of the jaw (ONJ) risk after dental extractions"],
["Immunosuppression (HIV, steroids, chemotherapy)",
"Oral candidiasis, ANUG, delayed healing"],
["Epilepsy",
"Phenytoin causes gingival hyperplasia"],
["SjΓΆgren's syndrome",
"Xerostomia (dry mouth) leading to rampant dental caries"],
["Pregnancy",
"Pregnancy gingivitis; avoid elective procedures in first trimester"],
]
pmh_table = two_col_table(pmh_data,
["Medical Condition", "Dental Relevance"],
[4.5*cm, usable - 4.5*cm], hdr_bg=ORANGE)
story.append(pmh_table)
story.append(Spacer(1, 0.3*cm))
# 1.5 Drug History
story.append(Paragraph("1.5 Drug History", S["h2"]))
story.append(callout_box([[Paragraph(
"<b>Always ask about:</b> Current medications (especially anticoagulants, "
"antihypertensives, bisphosphonates, steroids, immunosuppressants) | "
"<b>Allergies</b> (especially penicillin/amoxicillin - very common in dental practice) | "
"<b>OTC drugs</b> (aspirin, NSAIDs - bleeding risk) | "
"<b>Herbal supplements</b> (garlic, ginkgo - antiplatelet effects)",
S["body"])]],
bg=LIGHT_RED, border=RED))
story.append(Spacer(1, 0.3*cm))
# 1.6 Family, Social, Systemic Review
story.append(Paragraph("1.6 Family History, Social History & Systemic Review", S["h2"]))
fss_data = [
["Family History",
"Dental malocclusion, cleft palate, hereditary gingival fibromatosis, "
"amelogenesis/dentinogenesis imperfecta"],
["Social History",
"Smoking (periodontal disease, oral cancer risk) | Alcohol (oral cancer, "
"xerostomia) | Diet (frequency of sugar intake - caries risk) | Occupation | "
"Stress (bruxism/TMJ)"],
["Systemic Review",
"Dysphagia | Trismus (difficulty opening mouth) | Neck swelling / lymphadenopathy | "
"Fever/chills | Unintentional weight loss | Otalgia (referred pain from teeth) | "
"Paraesthesia of lip/chin (sign of serious pathology)"],
]
fss_table = two_col_table(fss_data,
["Category", "Key Points"],
[3.5*cm, usable - 3.5*cm], hdr_bg=TEAL)
story.append(fss_table)
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 2: CLINICAL EXAMINATION
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 2: CLINICAL EXAMINATION OF THE ORAL CAVITY", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"A dental examination follows a systematic sequence: EXTRA-ORAL first, "
"then INTRA-ORAL. Always ensure good lighting. Ideally, seat the patient "
"semi-reclined at 45 degrees.",
S["body"]))
story.append(Spacer(1, 0.2*cm))
# 2.1 Extra-Oral
story.append(section_header_table(
Paragraph("2.1 Extra-Oral Examination", S["h1"]), TEAL))
story.append(Spacer(1, 0.2*cm))
extra_oral = [
["General", "General appearance, nutritional status, pallor, jaundice, cyanosis"],
["Face", "Symmetry? Swelling? Sinus tracts? Scars? Bruising? Erythema?"],
["Skin", "Rashes, pigmentation (Addison's), vesicles (herpes zoster)"],
["Lymph nodes",
"Palpate: submental, submandibular, pre- & post-auricular, cervical chain, "
"parotid. Note: size, consistency, tenderness, fixity"],
["Lips",
"Colour, angular cheilitis, herpes labialis (cold sores), cleft lip, "
"dryness (Sjogren's)"],
["TMJ",
"Palpate joint with patient opening and closing. Note clicks, crepitus, "
"pain, restricted range (normally 35-50mm between incisors)"],
["Parotid & submandibular glands",
"Enlargement? Tenderness? Note parotitis, Sjogren's, stone obstruction"],
["Muscles of mastication",
"Palpate masseter & temporalis for tenderness (bruxism, TMJ disorder)"],
]
t = two_col_table(extra_oral,
["Examination Component", "What to Look For"],
[4.5*cm, usable - 4.5*cm], hdr_bg=TEAL)
story.append(t)
story.append(Spacer(1, 0.3*cm))
# 2.2 Intra-Oral
story.append(section_header_table(
Paragraph("2.2 Intra-Oral Examination (SALTTS Mnemonic)", S["h1"]), MED_BLUE))
story.append(Spacer(1, 0.2*cm))
story.append(callout_box([[Paragraph(
"<b>SALTTS:</b> Soft tissues | Alveolar bone | Lips (inside) | Tongue | Teeth | Saliva",
S["body"])]],
bg=LIGHT_BLUE, border=MED_BLUE))
story.append(Spacer(1, 0.2*cm))
saltts_data = [
["S", "Soft tissues",
"Inspect all mucosa: buccal, labial, floor of mouth, hard & soft palate. "
"Look for ulcers, white/red patches, swellings, pigmentation"],
["A", "Alveolar bone/Gingiva",
"Colour (healthy = coral pink), consistency, contour, calculus. "
"Bleeding on probing? Recession? Pocket depth? Swelling?"],
["L", "Lips (inner aspect)",
"Mucoceles, fibromata, Fordyce spots, labial frenum attachment"],
["T", "Tongue",
"Dorsum: coating, fissures, geographic tongue, ulcers, macroglossia. "
"Lateral borders: red/white lesion = rule out malignancy. "
"Ventral surface & floor of mouth: Wharton's duct orifices"],
["T", "Teeth",
"Count teeth present. Note: caries (look for dark/cavitated areas), "
"mobility (Grade I/II/III), fractures, discolouration, "
"attrition/erosion/abrasion, crowding/spacing"],
["S", "Saliva",
"Is saliva present and of normal consistency? Xerostomia? "
"Pooling suggesting poor swallowing?"],
]
t2 = Table(saltts_data, colWidths=[0.8*cm, 3.0*cm, usable - 3.8*cm])
t2.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), MED_BLUE),
("TEXTCOLOR", (0,0), (0,-1), WHITE),
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTNAME", (1,0), (1,-1), "Helvetica-Bold"),
("TEXTCOLOR", (1,0), (1,-1), DARK_BLUE),
("ROWBACKGROUNDS", (1,0), (-1,-1), [LIGHT_BLUE, WHITE]),
("GRID", (0,0), (-1,-1), 0.5, LIGHT_GRAY),
("FONTSIZE", (0,0), (-1,-1), 9),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(t2)
story.append(Spacer(1, 0.3*cm))
# 2.3 Specific Dental Tests
story.append(Paragraph("2.3 Specific Dental Tests", S["h2"]))
tests_data = [
["Percussion test", "Tap tooth with handle of dental mirror. "
"Pain = periapical periodontitis/abscess (indicates infection has spread beyond root apex)"],
["Bite test", "Patient bites on tongue blade; if pain = cracked tooth syndrome or "
"periapical pathology"],
["Thermal sensitivity test",
"Cold spray or warm water. Response to cold: if brief = reversible pulpitis; "
"if prolonged/lingering = irreversible pulpitis. No response = necrotic pulp"],
["Palpation of the alveolus",
"Fluctuance = abscess ready for drainage. Hard swelling = cellulitis. "
"Check buccal sulcus opposite root apex"],
["Probing", "Periodontal probe measures pocket depth around each tooth. "
"Normal < 3mm. Deep pocket > 4mm suggests periodontitis"],
["Mobility test",
"Grade I: slight mobility. Grade II: > 1mm bucco-lingual. "
"Grade III: mobile in all directions including vertical. "
"Causes: periodontitis, periapical infection, trauma, tumour"],
]
tests_table = two_col_table(tests_data,
["Test", "Interpretation"],
[4.0*cm, usable - 4.0*cm], hdr_bg=ORANGE)
story.append(tests_table)
story.append(Spacer(1, 0.3*cm))
# Red flag lesions box
story.append(callout_box([[Paragraph(
"<b>RED FLAG ORAL LESIONS - Report to Senior Immediately:</b><br/>"
"β’ Any white patch (leukoplakia) or red patch (erythroplakia) that does NOT rub off<br/>"
"β’ Ulcer present > 3 weeks that has NOT healed<br/>"
"β’ Unexplained paraesthesia/numbness of lip or chin<br/>"
"β’ Unexplained tooth mobility without periodontal disease<br/>"
"β’ Hard, fixed, non-tender lymphadenopathy<br/>"
"β’ Any suspicious mass: refer for biopsy",
S["body"])]],
bg=LIGHT_RED, border=RED))
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 3: DIFFERENTIAL DIAGNOSIS OF DENTAL PAIN
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 3: DENTAL PAIN - DIFFERENTIAL DIAGNOSIS", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Dental pain is most commonly odontogenic (arising from the tooth or its supporting "
"structures). However, non-odontogenic causes must always be excluded, especially "
"when multiple teeth are involved or the pain does not respond to dental treatment.",
S["body"]))
story.append(Spacer(1, 0.2*cm))
dd_data = [
["Reversible Pulpitis",
"Brief pain to cold/sweet; stops when stimulus removed; no spontaneous pain",
"Filling/restoration"],
["Irreversible Pulpitis",
"Spontaneous, throbbing, lingering pain; worse at night; hard to localize",
"Root canal or extraction"],
["Periapical Abscess",
"Severe constant pain; exquisitely tender to percussion; possible swelling",
"Drainage + root canal or extraction"],
["Periodontal Abscess",
"Pain in gum, not the tooth; swelling near gum line; pus from pocket; "
"tooth tender to lateral pressure",
"Drainage, scaling"],
["Pericoronitis",
"Pain around partially erupted wisdom tooth (3rd molar); "
"swollen operculum; trismus; halitosis",
"Irrigation, metronidazole; consider extraction"],
["Cracked Tooth",
"Sharp pain on biting/releasing; thermal sensitivity; hard to localize",
"Crown, root canal or extraction"],
["Dry Socket (Alveolar Osteitis)",
"Severe throbbing pain 2-4 days after extraction; empty socket; foul smell",
"Alvogyl dressing; analgesics"],
["TMJ Disorder",
"Pre-auricular pain; jaw clicking; pain on chewing; tender masseter/temporalis",
"Soft diet, splint, physio"],
["Maxillary Sinusitis",
"Upper posterior tooth pain, bilateral, worse on bending forward; "
"associated with nasal congestion",
"Treat sinusitis"],
["Trigeminal Neuralgia",
"Electric shock, lancinating pain; triggered by touch/eating; "
"no dental cause found",
"Carbamazepine"],
["Salivary Gland Stone (Sialolithiasis)",
"Swelling under jaw at mealtimes; colicky pain; tender gland",
"Hydration, massage; surgery if persistent"],
]
dd_table = two_col_table(dd_data,
["Diagnosis", "Key Features", "Management"],
[3.8*cm, 8.0*cm, usable - 11.8*cm], hdr_bg=RED)
story.append(dd_table)
story.append(Spacer(1, 0.2*cm))
story.append(callout_box([[Paragraph(
"<b>From Harrison's Principles:</b> Dental pathology can cause pain radiating to the ear "
"(referred otalgia). Bruxism, malocclusion, and temporomandibular disorder may present "
"with tenderness over the muscular attachments of the jaw muscles.",
S["tip"])]],
bg=LIGHT_TEAL, border=TEAL))
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 4: COMMON DENTAL CONDITIONS
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 4: COMMON DENTAL CONDITIONS YOU WILL SEE", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
# 4.1 Dental Caries
story.append(section_header_table(
Paragraph("4.1 Dental Caries (Tooth Decay)", S["h1"]), MED_BLUE))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"Dental caries is the most common cause of odontogenic pain. It results from the "
"dissolution of tooth mineral by acids produced by bacterial fermentation of dietary "
"sugars. Streptococcus mutans is the key pathogen.",
S["body"]))
caries_data = [
["Stage", "Findings", "Symptoms"],
["Initial (White Spot)", "Chalky white spot; no cavitation", "None"],
["Enamel caries", "Cavitation confined to enamel", "None or mild sensitivity"],
["Dentine caries", "Cavity into dentine", "Sensitivity to sweet/hot/cold"],
["Pulpitis", "Caries into pulp", "Spontaneous throbbing pain"],
["Necrotic pulp", "Pulp dead", "Pain may subside then return with abscess"],
["Periapical abscess", "Infection at root apex", "Severe pain, swelling, fever"],
]
stages_table = Table(
[[Paragraph(c, ParagraphStyle("th", fontName="Helvetica-Bold",
fontSize=9, textColor=WHITE if i==0 else DARK_GRAY,
alignment=TA_CENTER if i==0 else TA_LEFT))
for c in row]
for i, row in enumerate(caries_data)],
colWidths=[3.5*cm, 6.0*cm, usable - 9.5*cm]
)
stages_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MED_BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.5, LIGHT_GRAY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(stages_table)
story.append(Spacer(1, 0.2*cm))
# 4.2 Periodontal Disease
story.append(section_header_table(
Paragraph("4.2 Periodontal Disease (Gingivitis & Periodontitis)", S["h1"]), ORANGE))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"<b>Gingivitis</b> = Inflammation confined to the gingiva (reversible). "
"<b>Periodontitis</b> = Inflammation that destroys the periodontal ligament and alveolar "
"bone (irreversible - leads to tooth loss).",
S["body"]))
story.append(Spacer(1, 0.1*cm))
perio_data = [
["Gingivitis",
"Bleeding on brushing; red, swollen gums; bad breath",
"Plaque/calculus accumulation; smoking; hormonal (pregnancy)",
"Scaling & oral hygiene instruction"],
["Chronic Periodontitis",
"Deep pockets > 4mm; bone loss on X-ray; tooth mobility; recession",
"Chronic bacterial biofilm; smoking; diabetes; genetics",
"Deep scaling (root planing), antibiotics if severe"],
["Acute Necrotizing Ulcerative Gingivitis (ANUG/'Trench Mouth')",
"Punched-out ulcers of interdental papillae; intense pain; bleeding; "
"characteristic foul halitosis",
"Fusobacterium & spirochetes; stress; smoking; immunosuppression (HIV)",
"Metronidazole 200mg TDS, chlorhexidine rinse, debridement"],
["Pericoronitis",
"Pain & swelling around partially erupted tooth; operculum inflamed; trismus",
"Impacted/partially erupted third molar (wisdom tooth)",
"Irrigation under operculum with chlorhexidine; metronidazole if spreading"],
]
perio_table = two_col_table(perio_data,
["Condition", "Clinical Features", "Cause", "Treatment"],
[3.0*cm, 4.5*cm, 3.5*cm, usable-11.0*cm], hdr_bg=ORANGE)
story.append(perio_table)
story.append(Spacer(1, 0.3*cm))
# 4.3 Oral Ulcers
story.append(section_header_table(
Paragraph("4.3 Oral Ulcers", S["h1"]), TEAL))
story.append(Spacer(1, 0.2*cm))
ulcer_data = [
["Aphthous Ulcer\n(most common)",
"Shallow, round/oval; yellow/grey base with red halo; very painful; "
"non-infective; heals in 7-14 days",
"Spontaneous remission; chlorhexidine mouthwash; topical steroids (triamcinolone)"],
["Herpetic Stomatitis (HSV-1)",
"Multiple vesicles that rupture into ulcers; fever; "
"lymphadenopathy; affects gingiva; children > adults",
"Aciclovir (if early/severe); analgesics; topical lignocaine for pain"],
["Traumatic Ulcer",
"Single ulcer at site of trauma (denture, sharp tooth, biting cheek); "
"irregular edge; heals when cause removed",
"Remove cause; reassess at 2 weeks if not healed"],
["Oral Candidiasis\n(Thrush)",
"White curdy plaques on erythematous base; rub off leaving bleeding surface; "
"immunosuppressed, infants, denture wearers",
"Nystatin oral drops or lozenges; fluconazole if severe"],
["Malignant Ulcer",
"Indurated (hard) rolled edges; painless initially; does NOT heal; "
"> 3 weeks; lateral tongue or floor of mouth most common",
"URGENT referral for biopsy; surgical excision; radiotherapy"],
]
ulcer_table = two_col_table(ulcer_data,
["Type", "Clinical Features", "Management"],
[3.5*cm, 6.5*cm, usable - 10.0*cm], hdr_bg=TEAL)
story.append(ulcer_table)
story.append(Spacer(1, 0.3*cm))
# 4.4 Swellings
story.append(section_header_table(
Paragraph("4.4 Oro-Facial Swellings", S["h1"]), RED))
story.append(Spacer(1, 0.2*cm))
swelling_data = [
["Dental Abscess", "Tender, fluctuant swelling in buccal sulcus opposite apex; "
"fistula (sinus tract) may be present",
"Drain abscess; extract/RCT tooth; antibiotics if systemic features"],
["Pericoronitis", "Swelling behind last visible molar; trismus",
"Irrigation; metronidazole; operculum removal or extraction"],
["Cellulitis (spreading infection)",
"Brawny, non-fluctuant, diffuse; erythema; fever; danger of Ludwig's angina "
"(bilateral submandibular space - AIRWAY EMERGENCY)",
"IV antibiotics; hospital admission; surgical drainage; "
"CALL SENIOR if floor of mouth raised"],
["Gingival Hyperplasia",
"Painless overgrowth of gingiva; drug-induced (phenytoin, nifedipine, ciclosporin) "
"or idiopathic",
"Remove causative drug if possible; gingivectomy"],
["Ranula",
"Translucent bluish swelling in floor of mouth; "
"pseudocyst of sublingual gland",
"Surgical excision or marsupialisation"],
["Epulis",
"Pedunculated growth from gingival margin; fibrous / vascular / giant cell types",
"Excision biopsy to rule out malignancy"],
]
swelling_table = two_col_table(swelling_data,
["Type", "Features", "Management"],
[3.5*cm, 6.5*cm, usable - 10.0*cm], hdr_bg=RED)
story.append(swelling_table)
story.append(Spacer(1, 0.2*cm))
story.append(callout_box([[Paragraph(
"<b>EMERGENCY ALERT - Ludwig's Angina:</b> Bilateral submandibular cellulitis causing "
"floor of mouth elevation, drooling, inability to swallow, stridor. "
"This is a life-threatening airway emergency. Call senior IMMEDIATELY. "
"IV penicillin + metronidazole; prepare for emergency airway management.",
S["warning"])]],
bg=LIGHT_RED, border=RED))
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 5: TOOTH NUMBERING
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 5: DENTAL CHARTING & TOOTH NUMBERING", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Two main systems are used in dental practice. The FDI (Two-digit) system is "
"internationally standard; the Universal numbering system is used in North America.",
S["body"]))
story.append(Spacer(1, 0.2*cm))
# FDI System
story.append(Paragraph("FDI Two-Digit System (Most Commonly Used in India):", S["h3"]))
story.append(callout_box([[Paragraph(
"<b>How it works:</b> First digit = Quadrant (1=Upper Right, 2=Upper Left, 3=Lower Left, 4=Lower Right)<br/>"
"Second digit = Tooth position (1=central incisor to 8=3rd molar)<br/><br/>"
"<b>UPPER JAW:</b> 18 17 16 15 14 13 12 11 | 21 22 23 24 25 26 27 28<br/>"
"<b>LOWER JAW:</b> 48 47 46 45 44 43 42 41 | 31 32 33 34 35 36 37 38<br/><br/>"
"Example: Tooth 36 = Lower left first molar | Tooth 11 = Upper right central incisor<br/>"
"Primary teeth: 51-55 (upper right), 61-65 (upper left), 71-75 (lower left), 81-85 (lower right)",
S["body"])]],
bg=LIGHT_BLUE, border=MED_BLUE))
story.append(Spacer(1, 0.2*cm))
# Tooth Types
teeth_types_data = [
["Incisors (1,2)", "Cutting; single rooted; 4 upper, 4 lower"],
["Canines (3)", "Tearing; single root, longest root; 2 upper, 2 lower"],
["Premolars (4,5)", "Crushing; 1-2 roots; 4 upper, 4 lower (no premolars in primary)"],
["Molars (6,7,8)", "Grinding; multi-rooted; 6 upper (3 roots), 6 lower (2 roots) + 4 wisdom teeth"],
]
teeth_table = two_col_table(teeth_types_data,
["Tooth Type", "Function & Anatomy"],
[4.0*cm, usable - 4.0*cm], hdr_bg=MED_BLUE)
story.append(teeth_table)
story.append(Spacer(1, 0.2*cm))
# Mobility grading
story.append(Paragraph("Tooth Mobility Grading:", S["h3"]))
mob_data = [
["Grade I", "Slightly more than normal mobility; up to 1mm bucco-lingually"],
["Grade II", "Moderately more mobility; > 1mm bucco-lingually; no vertical displacement"],
["Grade III", "Severe mobility; moves in all directions including vertically/axially"],
]
mob_table = two_col_table(mob_data,
["Grade", "Description"],
[2.5*cm, usable - 2.5*cm])
story.append(mob_table)
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 6: WARD ESSENTIALS
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 6: WARD ESSENTIALS - PRESCRIPTIONS & INVESTIGATIONS", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
# Investigations
story.append(Paragraph("6.1 Common Investigations in the Dental Ward", S["h2"]))
inv_data = [
["IOPA X-ray (Intra-Oral Periapical)",
"Single tooth and periapical region; most common dental X-ray; "
"shows caries, periapical pathology, bone levels, root morphology"],
["OPG (Orthopantomogram / Panorex)",
"Full mouth X-ray; shows all teeth, TMJ, sinuses, mandible; "
"screening, wisdom teeth, fractures, cysts, TMJ"],
["Bitewing X-ray",
"Interproximal surfaces of posterior teeth; detects interproximal caries and bone levels"],
["CBCT (Cone Beam CT)",
"3D imaging for implant planning, complex pathology, surgical planning"],
["Blood Tests",
"Pre-op: CBC, BT/CT, blood glucose, HIV, Hepatitis B/C serology for surgical cases<br/>"
"INR (if on warfarin - target < 3.5 for minor oral surgery)<br/>"
"HbA1c for diabetics"],
["Biopsy",
"Excision or incision biopsy for any suspicious lesion; send in 10% formalin"],
["Pus Swab",
"For culture & sensitivity from abscess before antibiotics if available"],
]
inv_table = two_col_table(inv_data,
["Investigation", "Purpose & Notes"],
[4.5*cm, usable - 4.5*cm], hdr_bg=MED_BLUE)
story.append(inv_table)
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("6.2 Pre-Operative Considerations for Dental Procedures", S["h2"]))
preop_data = [
["Anticoagulants",
"Warfarin: check INR (< 3.5 for simple extraction); "
"do NOT routinely stop warfarin for minor oral surgery<br/>"
"Aspirin: no need to stop for most minor procedures<br/>"
"DOAC (rivaroxaban, apixaban): consult haematology for complex surgery"],
["Diabetes",
"Schedule procedure early in the morning; check blood glucose; "
"ensure patient has eaten appropriately; have glucose available post-op"],
["Penicillin Allergy",
"Document clearly; use clindamycin or metronidazole as alternatives for dental infections"],
["Infective Endocarditis (IE) Prophylaxis",
"Only for high-risk patients (prosthetic valves, prior IE, congenital heart disease):<br/>"
"Amoxicillin 2g orally 30-60 mins before procedure<br/>"
"If penicillin allergic: clindamycin 600mg"],
["Bisphosphonates/Denosumab",
"Risk of medication-related osteonecrosis of the jaw (MRONJ); "
"assess risk vs benefit; consult before any dental extraction"],
]
preop_table = two_col_table(preop_data,
["Consideration", "Key Points"],
[4.0*cm, usable - 4.0*cm], hdr_bg=ORANGE)
story.append(preop_table)
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 7: DRUG DOSES
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 7: KEY DRUG DOSES & ANALGESIC LADDER", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
# Analgesic Ladder
story.append(Paragraph("7.1 Dental Pain - Analgesic Ladder", S["h2"]))
story.append(callout_box([[Paragraph(
"<b>STEP 1 (Mild pain):</b> Ibuprofen 400-600mg every 6-8 hrs (with food) OR "
"Paracetamol 1g every 6 hrs<br/>"
"<b>STEP 2 (Moderate pain):</b> Ibuprofen 400mg + Paracetamol 1g (combination at scheduled times)<br/>"
"<b>STEP 3 (Severe pain):</b> Add Diclofenac 50mg TDS or short course opioid (tramadol 50mg TDS)<br/>"
"<b>Note:</b> NSAIDs given at SCHEDULED intervals are MORE effective than opioids for dental pain. "
"Do not prescribe opioids for chronic dental problems. "
"(Source: Rosen's Emergency Medicine)",
S["body"])]],
bg=LIGHT_GREEN, border=GREEN))
story.append(Spacer(1, 0.2*cm))
# Common drug table
story.append(Paragraph("7.2 Common Drugs in Dental Practice", S["h2"]))
drugs_data = [
["Amoxicillin", "500mg TDS x 5 days",
"First-line for dental infections; NOT for simple pulpitis"],
["Amoxicillin + Clavulanate\n(Co-Amoxiclav / Augmentin)", "625mg TDS x 5 days",
"More severe or resistant infections"],
["Metronidazole (Flagyl)", "200-400mg TDS x 5 days",
"Anaerobic infections; ANUG; pericoronitis; avoid alcohol during course"],
["Clindamycin", "300mg TDS x 5 days",
"Penicillin allergy alternative; bone infections; risk of C.diff"],
["Chlorhexidine 0.2% mouthwash", "10ml twice daily",
"Antiseptic rinse; ANUG; post-extraction; gingivitis"],
["Benzydamine (Difflam)", "Rinse/gargle 15ml every 3hrs",
"Anti-inflammatory mouthwash for post-op pain"],
["Nystatin suspension", "100,000 units/ml, 1ml QID",
"Oral candidiasis; hold in mouth then swallow"],
["Fluconazole", "150mg single dose or 50mg daily x 7 days",
"Moderate-severe oral candidiasis"],
["Aciclovir", "200mg 5x/day x 5 days (start within 72 hrs)",
"Primary herpetic gingivostomatitis"],
["Lignocaine 2% + Adrenaline 1:80,000", "Local infiltration",
"Most common dental LA; max 4.4mg/kg lignocaine; avoid adrenaline in CV disease"],
["Topical benzocaine 20%", "Apply to gingiva before LA injection",
"Surface anaesthesia to reduce injection discomfort"],
]
drugs_table = two_col_table(drugs_data,
["Drug", "Dose/Regimen", "Notes"],
[4.5*cm, 4.0*cm, usable - 8.5*cm], hdr_bg=TEAL)
story.append(drugs_table)
story.append(Spacer(1, 0.2*cm))
story.append(callout_box([[Paragraph(
"<b>Antibiotics in Dentistry (Important!):</b> Antibiotics are NOT indicated for "
"reversible or irreversible pulpitis (dental pain without systemic signs). "
"They are indicated only when there is spreading infection, systemic signs "
"(fever, lymphadenopathy, trismus), or immunocompromised patients. "
"Definitive treatment is always surgical (drainage/extraction/root canal).",
S["tip"])]],
bg=LIGHT_YELLOW, border=YELLOW))
story.append(PageBreak())
# ββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 8: QUICK REFERENCE CHEAT SHEET
# ββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header_table(
Paragraph("SECTION 8: QUICK REFERENCE CHEAT SHEET", S["h1"]), DARK_BLUE))
story.append(Spacer(1, 0.3*cm))
# History checklist
story.append(Paragraph("HISTORY TAKING CHECKLIST", S["h2"]))
checklist_items = [
"[ ] Chief complaint in patient's own words",
"[ ] SOCRATES for pain: Site / Onset / Character / Radiation / Associations / Time / Exacerbating-Relieving / Severity",
"[ ] Duration & progression of complaint",
"[ ] Previous dental treatment for this problem",
"[ ] Past dental history: extractions, fillings, root canals, dentures",
"[ ] Past medical history (diabetes, cardiac disease, bleeding disorders, Sjogren's)",
"[ ] Drug history: anticoagulants, bisphosphonates, amlodipine, phenytoin, steroids",
"[ ] Allergies (especially penicillin)",
"[ ] Social: smoking, alcohol, diet/sugar intake, stress (bruxism)",
"[ ] Family: inherited dental conditions, malocclusion",
"[ ] Systemic review: dysphagia, trismus, lymphadenopathy, fever, weight loss",
]
for item in checklist_items:
story.append(Paragraph(item, S["bullet"]))
story.append(Spacer(1, 0.3*cm))
# Examination checklist
story.append(Paragraph("EXAMINATION CHECKLIST", S["h2"]))
exam_checklist = [
"[ ] EXTRA-ORAL: General appearance, facial symmetry, swelling, lymph nodes (submental/submandibular/cervical)",
"[ ] TMJ palpation (clicks, crepitus, pain, restricted opening)",
"[ ] Parotid & submandibular gland palpation",
"[ ] Lips: colour, angular cheilitis, herpes labialis, dryness",
"[ ] INTRA-ORAL (use SALTTS): Soft tissues / Alveolar (gingiva) / Lips (inner) / Tongue / Teeth / Saliva",
"[ ] Teeth: count, caries, fracture, mobility, discolouration",
"[ ] Gingiva: colour, contour, bleeding on probing, swelling, recession",
"[ ] Tongue: dorsum, lateral borders (any white/red patches = biopsy), ventral surface",
"[ ] Floor of mouth: Wharton's ducts, any swelling/masses",
"[ ] Hard & soft palate: ulcers, torus palatinus, perforation",
"[ ] SPECIAL TESTS: Percussion, bite test, thermal, palpation of alveolus",
]
for item in exam_checklist:
story.append(Paragraph(item, S["bullet"]))
story.append(Spacer(1, 0.3*cm))
# Mnemonics box
story.append(callout_box([[Paragraph(
"<b>KEY MNEMONICS:</b><br/>"
"β’ <b>SOCRATES</b> = Site, Onset, Character, Radiation, Associations, Time course, "
"Exacerbating/relieving, Severity<br/>"
"β’ <b>SALTTS</b> = Soft tissues, Alveolar bone/gingiva, Lips, Tongue, Teeth, Saliva<br/>"
"β’ <b>3 P's of Pulpitis</b> = Pain triggered by temperature (Pulpitis); "
"Pain on Percussion (Periapical); Pus = Abscess<br/>"
"β’ <b>Recall RED FLAGS</b> = Ulcer > 3 weeks | White/Red patch | Hard lymph node | "
"Paraesthesia | Unexplained mobility",
S["body"])]],
bg=LIGHT_YELLOW, border=YELLOW))
story.append(Spacer(1, 0.2*cm))
# Common presentations summary
story.append(Paragraph("COMMON PRESENTATIONS AT A GLANCE", S["h2"]))
pres_data = [
["Toothache + cold sensitivity (brief)", "Reversible pulpitis", "Filling"],
["Toothache + spontaneous at night", "Irreversible pulpitis", "Root canal / Extraction"],
["Tender on tapping tooth", "Periapical periodontitis", "Root canal / Extraction"],
["Swelling in gum + fever", "Dental abscess", "Drain + Antibiotics + RCT/EXT"],
["Swollen jaw + can't open mouth", "Pericoronitis / Spreading cellulitis",
"Metronidazole; check for Ludwig's"],
["Multiple oral ulcers + fever (child)", "Primary herpetic stomatitis",
"Aciclovir + supportive"],
["White non-scrapable oral patch", "Leukoplakia (potentially malignant)",
"Urgent biopsy"],
["Gum bleeding on brushing", "Gingivitis", "Scaling + OHI"],
["Dry socket 2-4 days post-extraction", "Alveolar osteitis",
"Alvogyl dressing + analgesia"],
["Floor of mouth raised + drooling", "Ludwig's Angina - EMERGENCY",
"IV Antibiotics + Airway management"],
]
pres_table = two_col_table(pres_data,
["Presentation", "Likely Diagnosis", "Initial Action"],
[5.0*cm, 5.0*cm, usable - 10.0*cm], hdr_bg=DARK_BLUE)
story.append(pres_table)
story.append(Spacer(1, 0.3*cm))
# Final note
story.append(callout_box([[Paragraph(
"<b>Remember for your first posting:</b> Always wash hands before and after "
"examining a patient. Use gloves, mask, and protective eyewear. Never attempt "
"a procedure without supervision. If in doubt - ask your senior. Good luck!",
ParagraphStyle("final", fontName="Helvetica-Bold", fontSize=11,
textColor=DARK_BLUE, alignment=TA_CENTER, leading=16))]],
bg=LIGHT_BLUE, border=MED_BLUE))
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width=usable, color=LIGHT_GRAY))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"Content compiled from: Rosen's Emergency Medicine (9th Ed) | Scott-Brown's ORL Head & Neck Surgery | "
"Cummings Otolaryngology | Robbins & Kumar Basic Pathology | Harrison's Principles of Internal Medicine 22E | "
"K.J. Lee's Essential Otolaryngology",
S["small"]))
# ββββ BUILD ββββ
doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
print(f"PDF created: {OUTPUT_PATH}")
if __name__ == "__main__":
build_pdf()