History taking formats
| Particular | Clinical Relevance |
|---|---|
| Name | Personalizes care, psychological benefit |
| Age | Congenital anomalies (infancy/childhood), sarcomas (teens), carcinomas (>40 yrs), BPH and OA (old age) |
| Sex | Thyroid diseases, cystitis more common in females; carcinomas of stomach/lung more common in males; haemophilia only in males |
| Religion | Carcinoma of penis is rare in Jews/Muslims (circumcision); intussusception can follow prolonged fasting |
| Social status | Appendicitis (higher social class); tuberculosis (lower social class/malnutrition) |
| Occupation | Varicose veins (bus conductors), bladder neoplasms (aniline dye workers), scrotal carcinoma (chimney sweepers), tennis elbow (tennis players) |
| Residence | Geographical disease distribution, e.g. filariasis in Orissa |
Create a printable history-taking template for clinical use
mkdir -p /home/daytona/workspace/history-taking-template
~/history-taking-template/generate_template.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.pdfgen import canvas
OUTPUT = "/home/daytona/workspace/history-taking-template/History_Taking_Template.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MID_BLUE = colors.HexColor("#2e6da4")
LIGHT_BLUE = colors.HexColor("#ddeeff")
PALE_GREY = colors.HexColor("#f5f5f5")
LINE_GREY = colors.HexColor("#cccccc")
BLACK = colors.black
WHITE = colors.white
# ── Page size ─────────────────────────────────────────────────────────────────
PAGE_W, PAGE_H = A4
L_MARGIN = R_MARGIN = 18*mm
T_MARGIN = B_MARGIN = 15*mm
CONTENT_W = PAGE_W - L_MARGIN - R_MARGIN
# ── Styles ────────────────────────────────────────────────────────────────────
base_styles = getSampleStyleSheet()
def style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=base_styles[parent], **kw)
return s
S_TITLE = style("S_TITLE", "Normal", fontSize=16, leading=20,
textColor=WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER)
S_SUBTITLE = style("S_SUBTITLE", "Normal", fontSize=8, leading=10,
textColor=colors.HexColor("#ccddee"), fontName="Helvetica", alignment=TA_CENTER)
S_SEC = style("S_SEC", "Normal", fontSize=9, leading=11,
textColor=WHITE, fontName="Helvetica-Bold")
S_LABEL = style("S_LABEL", "Normal", fontSize=7.5, leading=10,
textColor=DARK_BLUE, fontName="Helvetica-Bold")
S_SMALL = style("S_SMALL", "Normal", fontSize=7, leading=9,
textColor=colors.HexColor("#555555"), fontName="Helvetica")
S_HINT = style("S_HINT", "Normal", fontSize=6.5, leading=8,
textColor=colors.HexColor("#888888"), fontName="Helvetica-Oblique")
S_BODY = style("S_BODY", "Normal", fontSize=7.5, leading=10,
textColor=BLACK, fontName="Helvetica")
S_FOOTER = style("S_FOOTER", "Normal", fontSize=6.5, leading=8,
textColor=colors.HexColor("#999999"), fontName="Helvetica", alignment=TA_CENTER)
# ── Helpers ───────────────────────────────────────────────────────────────────
def section_header(text, icon=""):
full = f"{icon} {text}" if icon else text
tbl = Table([[Paragraph(full, S_SEC)]], colWidths=[CONTENT_W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("ROUNDEDCORNERS", (0,0), (-1,-1), [3,3,3,3]),
]))
return tbl
def write_lines(n, label="", hint="", col_width=None):
"""Return a table row with a label and n blank writing lines."""
cw = col_width or CONTENT_W
rows = []
if label:
rows.append([Paragraph(label, S_LABEL)])
if hint:
rows.append([Paragraph(hint, S_HINT)])
for _ in range(n):
rows.append([""]) # blank line row
tbl = Table(rows, colWidths=[cw])
style_cmds = [
("TOPPADDING", (0,0), (-1,-1), 1),
("BOTTOMPADDING", (0,0), (-1,-1), 1),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
]
# underline each blank row
start = 1 if label else 0
start += 1 if hint else 0
for i in range(start, len(rows)):
style_cmds.append(("LINEBELOW", (0,i), (-1,i), 0.4, LINE_GREY))
tbl.setStyle(TableStyle(style_cmds))
return tbl
def labelled_field(label, hint="", width=None):
return write_lines(1, label, hint, width)
def two_col(left_items, right_items, split=0.5):
"""Lay two lists of flowables side by side in a table."""
lw = CONTENT_W * split - 3*mm
rw = CONTENT_W * (1-split) - 3*mm
rows = []
max_len = max(len(left_items), len(right_items))
for i in range(max_len):
l = left_items[i] if i < len(left_items) else ""
r = right_items[i] if i < len(right_items) else ""
rows.append([l, r])
tbl = Table(rows, colWidths=[lw, rw])
tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 2),
]))
return tbl
def checkbox_row(items):
"""Row of small checkboxes with labels."""
cells = []
for item in items:
cells.append(Paragraph(f"[ ] {item}", S_SMALL))
tbl = Table([cells], colWidths=[CONTENT_W / len(items)] * len(items))
tbl.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING",(0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
]))
return tbl
def sp(h=3):
return Spacer(1, h*mm)
def hr():
return HRFlowable(width="100%", thickness=0.4, color=LINE_GREY, spaceAfter=2, spaceBefore=2)
# ── Header banner (drawn on canvas) ──────────────────────────────────────────
def on_first_page(c: canvas.Canvas, doc):
c.saveState()
# Dark blue banner
banner_h = 28*mm
c.setFillColor(DARK_BLUE)
c.rect(0, PAGE_H - banner_h, PAGE_W, banner_h, fill=1, stroke=0)
# Accent stripe
c.setFillColor(MID_BLUE)
c.rect(0, PAGE_H - banner_h - 2*mm, PAGE_W, 2*mm, fill=1, stroke=0)
# Title text
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 17)
c.drawCentredString(PAGE_W/2, PAGE_H - 15*mm, "CLINICAL HISTORY TAKING FORM")
c.setFont("Helvetica", 8)
c.setFillColor(colors.HexColor("#aaccee"))
c.drawCentredString(PAGE_W/2, PAGE_H - 22*mm,
"Complete all sections | Mark N/A where not applicable | Use black or blue ink")
# Footer
c.setFont("Helvetica", 6.5)
c.setFillColor(colors.HexColor("#aaaaaa"))
c.drawCentredString(PAGE_W/2, 8*mm, "Page 1 | Confidential Medical Record")
c.restoreState()
def on_later_pages(c: canvas.Canvas, doc):
c.saveState()
c.setFillColor(DARK_BLUE)
c.rect(0, PAGE_H - 10*mm, PAGE_W, 10*mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 8)
c.drawString(L_MARGIN, PAGE_H - 6.5*mm, "CLINICAL HISTORY TAKING FORM (continued)")
c.setFont("Helvetica", 7)
c.drawRightString(PAGE_W - R_MARGIN, PAGE_H - 6.5*mm,
f"Patient: ________________________ Date: ______________")
c.setFont("Helvetica", 6.5)
c.setFillColor(colors.HexColor("#aaaaaa"))
c.drawCentredString(PAGE_W/2, 8*mm,
f"Page {doc.page} | Confidential Medical Record")
c.restoreState()
# ── Build story ───────────────────────────────────────────────────────────────
story = []
GAP = sp(2)
# ── 1. PATIENT PARTICULARS ────────────────────────────────────────────────────
story.append(sp(32)) # space for banner
story.append(section_header("1. PATIENT PARTICULARS"))
story.append(sp(2))
bio_left = [
labelled_field("Patient Name", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Age", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Sex [ ] M [ ] F [ ] Other", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Religion / Ethnicity", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Occupation", width=CONTENT_W*0.5 - 3*mm),
]
bio_right = [
labelled_field("Hospital / OPD No.", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Date of Admission", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Ward / Bed No.", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Referred by", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Address / Contact", width=CONTENT_W*0.5 - 3*mm),
]
story.append(two_col(bio_left, bio_right))
story.append(GAP)
# ── 2. CHIEF COMPLAINTS ───────────────────────────────────────────────────────
story.append(section_header("2. CHIEF COMPLAINTS"))
story.append(sp(2))
story.append(Paragraph("List complaints in chronological order of onset (earliest first)", S_HINT))
story.append(sp(1))
cc_data = [
[Paragraph("#", S_LABEL), Paragraph("Complaint", S_LABEL), Paragraph("Duration", S_LABEL)],
]
for i in range(1, 6):
cc_data.append([Paragraph(str(i), S_SMALL), "", ""])
cc_tbl = Table(cc_data, colWidths=[10*mm, CONTENT_W - 10*mm - 35*mm, 35*mm])
cc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("TEXTCOLOR", (0,0), (-1,0), DARK_BLUE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("LINEBELOW", (0,0), (-1,0), 0.6, MID_BLUE),
("LINEBELOW", (0,1), (-1,-1), 0.4, LINE_GREY),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(cc_tbl)
story.append(GAP)
# ── 3. HISTORY OF PRESENT ILLNESS ────────────────────────────────────────────
story.append(section_header("3. HISTORY OF PRESENT ILLNESS (HPI)"))
story.append(sp(2))
hpi_secs = [
("Mode of Onset", "[ ] Sudden [ ] Gradual Precipitating cause (if any): _______________________________________________"),
("Progress / Evolution", "Describe sequence of events from first symptom to present:"),
("Treatment Already Received", "Drugs / procedures tried, treating doctor, response:"),
]
for lbl, hint in hpi_secs:
story.append(Paragraph(lbl, S_LABEL))
story.append(Paragraph(hint, S_HINT))
story.append(write_lines(3))
story.append(sp(2))
# PAIN sub-section (SOCRATES)
story.append(Paragraph("Pain Analysis (SOCRATES) -- complete if pain is a complaint", S_LABEL))
story.append(sp(1))
soc_left = [
labelled_field("Site", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Onset [ ] Sudden [ ] Gradual", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Character (burning/colicky/dull/sharp)", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Radiation", width=CONTENT_W*0.5 - 3*mm),
]
soc_right = [
labelled_field("Associated symptoms", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Timing / Duration", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Exacerbating / Relieving factors", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Severity (0-10 scale)", width=CONTENT_W*0.5 - 3*mm),
]
story.append(two_col(soc_left, soc_right))
story.append(GAP)
# ── 4. PAST MEDICAL HISTORY ───────────────────────────────────────────────────
story.append(section_header("4. PAST MEDICAL HISTORY (PMH)"))
story.append(sp(2))
story.append(Paragraph("Tick all that apply and provide details below", S_HINT))
story.append(sp(1))
story.append(checkbox_row(["Diabetes", "Hypertension", "Cardiac disease", "Respiratory disease"]))
story.append(checkbox_row(["Tuberculosis", "Peptic ulcer", "Renal disease", "Thyroid disorder"]))
story.append(checkbox_row(["Epilepsy / Fits", "Stroke / TIA", "Malignancy", "Other (specify below)"]))
story.append(sp(1))
story.append(write_lines(2, "Details of above + Previous similar episodes:"))
story.append(GAP)
# Past Surgical History
story.append(Paragraph("Previous Surgical / Operative History", S_LABEL))
story.append(sp(1))
surg_data = [
[Paragraph("Operation / Procedure", S_LABEL), Paragraph("Year", S_LABEL), Paragraph("Hospital", S_LABEL), Paragraph("Complications / Notes", S_LABEL)],
]
for _ in range(3):
surg_data.append(["", "", "", ""])
surg_tbl = Table(surg_data, colWidths=[CONTENT_W*0.38, CONTENT_W*0.12, CONTENT_W*0.22, CONTENT_W*0.28])
surg_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("LINEBELOW", (0,0), (-1,0), 0.6, MID_BLUE),
("LINEBELOW", (0,1), (-1,-1), 0.4, LINE_GREY),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
story.append(surg_tbl)
story.append(GAP)
# ── 5. DRUG HISTORY ───────────────────────────────────────────────────────────
story.append(section_header("5. DRUG HISTORY & ALLERGIES"))
story.append(sp(2))
drug_data = [
[Paragraph("Drug Name", S_LABEL), Paragraph("Dose", S_LABEL), Paragraph("Frequency", S_LABEL), Paragraph("Duration", S_LABEL), Paragraph("Indication", S_LABEL)],
]
for _ in range(5):
drug_data.append(["", "", "", "", ""])
drug_tbl = Table(drug_data, colWidths=[
CONTENT_W*0.30, CONTENT_W*0.15, CONTENT_W*0.15, CONTENT_W*0.15, CONTENT_W*0.25
])
drug_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("LINEBELOW", (0,0), (-1,0), 0.6, MID_BLUE),
("LINEBELOW", (0,1), (-1,-1), 0.4, LINE_GREY),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 3),
("FONTSIZE", (0,1), (-1,-1), 7),
]))
story.append(drug_tbl)
story.append(sp(2))
# Allergy box
allergy_box = Table(
[[Paragraph("! ALLERGIES", S_SEC), ""]],
colWidths=[30*mm, CONTENT_W - 30*mm]
)
allergy_box.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), colors.HexColor("#c0392b")),
("BACKGROUND", (1,0), (1,0), colors.HexColor("#fdecea")),
("LINEBELOW", (1,0), (1,0), 0.4, LINE_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 5),
("FONTSIZE", (0,0), (-1,-1), 8),
]))
story.append(allergy_box)
story.append(GAP)
# ── 6. PERSONAL / SOCIAL HISTORY ─────────────────────────────────────────────
story.append(section_header("6. PERSONAL & SOCIAL HISTORY"))
story.append(sp(2))
ps_left = [
Paragraph("Smoking", S_LABEL),
checkbox_row(["Never", "Ex-smoker", "Current"]),
labelled_field("Type / Frequency (packs/day, years)", width=CONTENT_W*0.5 - 3*mm),
sp(1),
Paragraph("Alcohol", S_LABEL),
checkbox_row(["None", "Occasional", "Moderate", "Heavy"]),
labelled_field("Units/week", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Recreational Drugs (specify)", width=CONTENT_W*0.5 - 3*mm),
]
ps_right = [
Paragraph("Diet", S_LABEL),
checkbox_row(["Vegetarian", "Non-veg", "Mixed"]),
labelled_field("Dietary restrictions / notes", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Marital Status [ ] Single [ ] Married [ ] Widowed", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Occupation (exposure risks)", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Domestic support / mobility", width=CONTENT_W*0.5 - 3*mm),
]
story.append(two_col(ps_left, ps_right))
story.append(sp(2))
# Menstrual (females)
men_box = Table([[
Paragraph("Menstrual / Obstetric History (Females)", S_LABEL),
Paragraph("LMP: _______________ Cycle: Regular [ ] Irregular [ ] "
"G ___ P ___ A ___ Last delivery: _______________ "
"[ ] Normal [ ] LSCS Vaginal discharge: [ ] Yes [ ] No", S_SMALL)
]], colWidths=[55*mm, CONTENT_W - 55*mm])
men_box.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(men_box)
story.append(GAP)
# ── 7. FAMILY HISTORY ────────────────────────────────────────────────────────
story.append(section_header("7. FAMILY HISTORY"))
story.append(sp(2))
story.append(Paragraph("Tick all that apply in first-degree relatives:", S_HINT))
story.append(sp(1))
story.append(checkbox_row(["Diabetes", "Hypertension", "Heart disease", "Stroke"]))
story.append(checkbox_row(["Cancer (specify below)", "Tuberculosis", "Haemophilia", "Thyroid disorder"]))
story.append(checkbox_row(["Mental illness", "IBD", "Epilepsy", "Adverse anaesthetic reaction"]))
story.append(sp(1))
story.append(write_lines(2, "Details (affected member, age of onset, cause of death if deceased):"))
story.append(GAP)
# ── 8. IMMUNIZATION HISTORY ──────────────────────────────────────────────────
story.append(section_header("8. IMMUNIZATION HISTORY"))
story.append(sp(2))
imm_left = [
checkbox_row(["BCG (TB)", "DPT", "Polio", "Measles/MMR"]),
sp(1),
checkbox_row(["Hepatitis B", "Typhoid", "Rabies", "COVID-19"]),
]
imm_right = [
labelled_field("Tetanus status / last booster", width=CONTENT_W*0.5 - 3*mm),
sp(1),
labelled_field("Other vaccinations", width=CONTENT_W*0.5 - 3*mm),
]
story.append(two_col(imm_left, imm_right))
story.append(GAP)
# ── 9. SYSTEMS REVIEW ────────────────────────────────────────────────────────
story.append(section_header("9. SYSTEMS REVIEW (tick positives; cross negatives; circle N/A)"))
story.append(sp(2))
systems = [
("Cardiovascular", ["Chest pain", "Palpitations", "Breathlessness", "Orthopnoea", "Ankle swelling", "Syncope"]),
("Respiratory", ["Cough", "Sputum", "Haemoptysis", "Wheeze", "Stridor", "Dyspnoea at rest"]),
("GI / Abdomen", ["Nausea/Vomiting", "Dysphagia", "Heartburn", "Abdominal pain", "Altered bowel habit", "Rectal bleeding", "Jaundice", "Weight loss"]),
("Genitourinary", ["Dysuria", "Frequency", "Haematuria", "Nocturia", "Discharge", "Loin pain"]),
("Neurological", ["Headache", "Fits/Seizures", "Dizziness", "Weakness", "Sensory changes", "Vision changes", "Speech difficulty"]),
("MSK / Skin", ["Joint pain", "Swelling", "Rash", "Skin lesion", "Lymphadenopathy"]),
("Endocrine", ["Polyuria", "Polydipsia", "Heat intolerance", "Cold intolerance", "Weight gain", "Fatigue"]),
]
sr_rows = []
for sys_name, items in systems:
cells = [Paragraph(sys_name, S_LABEL)]
for item in items:
cells.append(Paragraph(f"[ ] {item}", S_SMALL))
# pad to 8 cells
while len(cells) < 9:
cells.append("")
sr_rows.append(cells[:9])
# col widths: system name wide, 8 items equal
sys_col = 28*mm
item_cols = [(CONTENT_W - sys_col) / 8] * 8
sr_tbl = Table(sr_rows, colWidths=[sys_col] + item_cols)
sr_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LIGHT_BLUE),
("LINEBELOW", (0,0), (-1,-1), 0.3, LINE_GREY),
("LINEAFTER", (0,0), (0,-1), 0.5, MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 2),
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (0,-1), 7),
("FONTSIZE", (1,0), (-1,-1), 6.5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sr_tbl)
story.append(GAP)
# ── 10. PROVISIONAL DIAGNOSIS & PLAN ────────────────────────────────────────
story.append(section_header("10. PROVISIONAL DIAGNOSIS & MANAGEMENT PLAN"))
story.append(sp(2))
story.append(write_lines(2, "Provisional Diagnosis:"))
story.append(sp(2))
story.append(write_lines(2, "Differential Diagnoses:"))
story.append(sp(2))
story.append(write_lines(2, "Investigations Ordered:"))
story.append(sp(2))
story.append(write_lines(2, "Initial Management Plan:"))
story.append(GAP)
# ── SIGNATURE BLOCK ───────────────────────────────────────────────────────────
sig_data = [[
Paragraph("Clinician Name: ________________________________", S_SMALL),
Paragraph("Signature: ____________________________", S_SMALL),
Paragraph("Date: ____________________ Time: ____________", S_SMALL),
]]
sig_tbl = Table(sig_data, colWidths=[CONTENT_W*0.40, CONTENT_W*0.30, CONTENT_W*0.30])
sig_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_GREY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 5),
("LINEABOVE", (0,0), (-1,0), 0.8, MID_BLUE),
]))
story.append(sig_tbl)
# ── Build ─────────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=L_MARGIN,
rightMargin=R_MARGIN,
topMargin=T_MARGIN + 28*mm, # space for banner on page 1
bottomMargin=B_MARGIN + 8*mm,
)
def build_pages(canvas_obj, doc_obj):
if doc_obj.page == 1:
on_first_page(canvas_obj, doc_obj)
else:
on_later_pages(canvas_obj, doc_obj)
doc.build(
story,
onFirstPage=on_first_page,
onLaterPages=on_later_pages,
)
print("PDF created:", OUTPUT)
cd /home/daytona/workspace/history-taking-template && python generate_template.p…
ls -lh /home/daytona/workspace/history-taking-template/History_Taking_Template.p…
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/…
History Taking Template
PDF Document · PDF
| Section | Contents |
|---|---|
| 1. Patient Particulars | Name, Age, Sex, Religion, Occupation, OPD No., Ward, Referral, Address |
| 2. Chief Complaints | 5-row table with complaint + duration, chronological order |
| 3. HPI | Mode of onset, Progress/evolution, Prior treatment, full SOCRATES pain analysis grid |
| 4. Past Medical History | Checkbox grid (12 conditions) + 3-row surgical history table |
| 5. Drug History & Allergies | 5-drug table (name/dose/frequency/duration/indication) + prominent red allergy box |
| 6. Personal & Social History | Smoking, alcohol, recreational drugs, diet, marital status, occupation; menstrual/obstetric block for females |
| 7. Family History | Checkbox grid (12 conditions) + free-text details |
| 8. Immunization History | Checkbox grid + tetanus status |
| 9. Systems Review | 7 systems (CVS, Respiratory, GI, GU, Neuro, MSK/Skin, Endocrine) in a compact tick-box table |
| 10. Provisional Diagnosis & Plan | Diagnosis, differentials, investigations, management |
| Signature block | Clinician name, signature, date, time |
For mbbs students symptoms wise hoping in surgery
~/history-taking-template/symptom_surgical_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.pdfgen import canvas
OUTPUT = "/home/daytona/workspace/history-taking-template/Surgical_Symptom_Guide_MBBS.pdf"
# ── Palette ───────────────────────────────────────────────────────────────────
DARK = colors.HexColor("#1a2e4a")
MID = colors.HexColor("#2563a8")
ACCENT = colors.HexColor("#e67e22")
LIGHT_HDR = colors.HexColor("#dbeafe")
PALE = colors.HexColor("#f8faff")
PALE2 = colors.HexColor("#fff8f0")
LINE = colors.HexColor("#cbd5e1")
RED = colors.HexColor("#c0392b")
GREEN = colors.HexColor("#1a7a4a")
ORANGE = colors.HexColor("#e67e22")
WHITE = colors.white
BLACK = colors.black
PAGE_W, PAGE_H = A4
L_MARGIN = R_MARGIN = 16*mm
T_MARGIN = B_MARGIN = 14*mm
CW = PAGE_W - L_MARGIN - R_MARGIN # usable content width
# ── Styles ─────────────────────────────────────────────────────────────────────
bs = getSampleStyleSheet()
def S(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=bs[parent], **kw)
ST_PAGE_TITLE = S("PT", fontSize=18, leading=22, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
ST_PAGE_SUB = S("PS", fontSize=8, leading=10, textColor=colors.HexColor("#b8d4f8"),
fontName="Helvetica", alignment=TA_CENTER)
ST_SYM_TITLE = S("SYM", fontSize=11, leading=13, textColor=WHITE,
fontName="Helvetica-Bold")
ST_SYM_ICON = S("SYMI", fontSize=11, leading=13, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
ST_COL_HEAD = S("CH", fontSize=7.5, leading=9, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
ST_CELL = S("CE", fontSize=7, leading=9, textColor=BLACK,
fontName="Helvetica")
ST_CELL_B = S("CEB", fontSize=7, leading=9, textColor=DARK,
fontName="Helvetica-Bold")
ST_HINT = S("HN", fontSize=6, leading=7.5, textColor=colors.HexColor("#64748b"),
fontName="Helvetica-Oblique")
ST_LABEL = S("LBL", fontSize=7.5, leading=9, textColor=DARK,
fontName="Helvetica-Bold")
ST_ALERT_RED = S("AR", fontSize=7, leading=8.5, textColor=RED,
fontName="Helvetica-Bold")
ST_ALERT_GRN = S("AG", fontSize=7, leading=8.5, textColor=GREEN,
fontName="Helvetica-Bold")
ST_FOOTER = S("FT", fontSize=6, leading=7, textColor=colors.HexColor("#94a3b8"),
fontName="Helvetica", alignment=TA_CENTER)
ST_BODY = S("BD", fontSize=7, leading=9, textColor=BLACK,
fontName="Helvetica")
# ── Page decorators ────────────────────────────────────────────────────────────
def banner(c, doc, title_line2=""):
h = 26*mm
c.saveState()
c.setFillColor(DARK)
c.rect(0, PAGE_H - h, PAGE_W, h, fill=1, stroke=0)
c.setFillColor(ACCENT)
c.rect(0, PAGE_H - h - 1.5*mm, PAGE_W, 1.5*mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 15)
c.drawCentredString(PAGE_W/2, PAGE_H - 12*mm, "SYMPTOM-WISE SURGICAL DIAGNOSIS GUIDE")
c.setFont("Helvetica", 7.5)
c.setFillColor(colors.HexColor("#8ab4d8"))
c.drawCentredString(PAGE_W/2, PAGE_H - 19*mm,
"MBBS Surgery Quick Reference | Symptom -> Possible Diagnoses -> Key Differentiating Features")
# footer
c.setFont("Helvetica", 6)
c.setFillColor(colors.HexColor("#94a3b8"))
c.drawCentredString(PAGE_W/2, 7*mm, f"Page {doc.page} | For Educational Use")
c.restoreState()
def banner_later(c, doc):
c.saveState()
c.setFillColor(DARK)
c.rect(0, PAGE_H - 9*mm, PAGE_W, 9*mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 7.5)
c.drawString(L_MARGIN, PAGE_H - 6*mm, "SYMPTOM-WISE SURGICAL DIAGNOSIS GUIDE (MBBS)")
c.setFont("Helvetica", 7)
c.drawRightString(PAGE_W - R_MARGIN, PAGE_H - 6*mm, f"Page {doc.page}")
c.setFont("Helvetica", 6)
c.setFillColor(colors.HexColor("#94a3b8"))
c.drawCentredString(PAGE_W/2, 7*mm, "For Educational Use")
c.restoreState()
# ── Helper: symptom section header ─────────────────────────────────────────────
def sym_header(emoji, title, color=MID):
row = Table([[Paragraph(emoji, ST_SYM_ICON),
Paragraph(title, ST_SYM_TITLE)]],
colWidths=[12*mm, CW - 12*mm])
row.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
return row
def sp(h=3): return Spacer(1, h*mm)
def hr(): return HRFlowable(width="100%", thickness=0.3, color=LINE, spaceAfter=1, spaceBefore=1)
# ── Helper: build diagnosis table ─────────────────────────────────────────────
def diag_table(col_headers, rows, col_widths, alt_color=PALE):
data = [[Paragraph(h, ST_COL_HEAD) for h in col_headers]]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), ST_CELL_B if j == 0 else ST_CELL)
for j, c in enumerate(row)])
t = Table(data, colWidths=col_widths)
style_cmds = [
("BACKGROUND", (0,0), (-1,0), MID),
("LINEBELOW", (0,0), (-1,0), 0.6, DARK),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEBELOW", (0,1), (-1,-1), 0.3, LINE),
("GRID", (0,0), (-1,-1), 0.2, LINE),
]
for i in range(1, len(data)):
if i % 2 == 0:
style_cmds.append(("BACKGROUND", (0,i), (-1,i), alt_color))
t.setStyle(TableStyle(style_cmds))
return t
def red_flags_box(flags):
items = " | ".join(f"! {f}" for f in flags)
box = Table([[Paragraph("RED FLAGS:", ST_ALERT_RED),
Paragraph(items, ST_ALERT_RED)]],
colWidths=[22*mm, CW - 22*mm])
box.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#fef2f2")),
("LINEABOVE", (0,0), (-1,-1), 0.6, RED),
("LINEBELOW", (0,0), (-1,-1), 0.6, RED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
return box
def key_box(text):
box = Table([[Paragraph("KEY:", ST_ALERT_GRN),
Paragraph(text, ST_BODY)]],
colWidths=[14*mm, CW - 14*mm])
box.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#f0fdf4")),
("LINEABOVE", (0,0), (-1,-1), 0.6, GREEN),
("LINEBELOW", (0,0), (-1,-1), 0.6, GREEN),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
return box
# ═══════════════════════════════════════════════════════════════════════════════
# CONTENT DATA
# ═══════════════════════════════════════════════════════════════════════════════
CONTENT = []
# ── INTRO ─────────────────────────────────────────────────────────────────────
CONTENT.append(sp(30)) # banner clearance
intro_tbl = Table([[
Paragraph("HOW TO USE THIS GUIDE", ST_LABEL),
Paragraph(
"Step 1: Identify the patient's chief symptom. "
"Step 2: Find the symptom section below. "
"Step 3: Use the region/character column to narrow your differential. "
"Step 4: Apply red-flag features to rule out dangerous diagnoses first. "
"Step 5: Confirm with clinical examination + investigations.",
ST_BODY)
]], colWidths=[38*mm, CW - 38*mm])
intro_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#eff6ff")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LINEALL", (0,0), (-1,-1), 0.4, MID),
]))
CONTENT.append(intro_tbl)
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 1. ABDOMINAL PAIN
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(1)", "ABDOMINAL PAIN", DARK),
sp(2),
Paragraph("Differential by Region of Maximal Pain", ST_LABEL),
sp(1),
]))
W = CW
w = [38*mm, 38*mm, W - 76*mm]
pain_region_rows = [
("RUQ", "Liver / GB / duodenum / right lower lobe",
"Cholecystitis, Cholangitis, Hepatitis, Peptic ulcer (duodenal), Right pneumonia, Subphrenic abscess"),
("Epigastric", "Stomach / pancreas / duodenum / aorta",
"Peptic ulcer, Gastritis, Pancreatitis, GORD, Ruptured AAA, MI (referred)"),
("LUQ", "Spleen / stomach / left lower lobe",
"Splenic infarct/rupture, Gastric volvulus, Left pneumonia, Pancreatitis (tail)"),
("Periumbilical", "Small bowel / appendix (early) / aorta",
"Early appendicitis (visceral), SBO, Mesenteric ischaemia, Aortic dissection"),
("RIF", "Appendix / caecum / right ureter / right ovary",
"Appendicitis, Ileocaecal TB, Crohn's, Ovarian cyst/torsion (F), Ectopic (F), Ureteric colic"),
("LIF", "Sigmoid / descending colon / left ureter / left ovary",
"Diverticulitis, Sigmoid volvulus, Colorectal Ca, Ovarian pathology (F), Ureteric colic"),
("Suprapubic / Pelvic", "Bladder / uterus / rectum",
"UTI, Bladder retention, PID (F), Ectopic pregnancy (F), Cystitis, Prostatitis"),
("Loin / Flank", "Kidney / ureter / retroperitoneum",
"Ureteric/renal colic, Pyelonephritis, Perinephric abscess, Retroperitoneal haematoma"),
("Diffuse", "Peritoneum / entire bowel / metabolic",
"Peritonitis (perforated viscus), Generalised SBO, DKA, Sickle cell crisis, Mesenteric ischaemia"),
]
CONTENT.append(diag_table(
["Region", "Underlying Structures", "Key Differentials"],
pain_region_rows, w))
CONTENT.append(sp(3))
CONTENT.append(Paragraph("Character of Pain -> Diagnosis Clues", ST_LABEL))
CONTENT.append(sp(1))
w2 = [30*mm, 30*mm, CW - 60*mm]
pain_char_rows = [
("Colicky", "Hollow viscus obstruction/spasm", "Ureteric colic, SBO, Biliary colic, Early appendicitis"),
("Constant burning", "Mucosal / chemical", "Peptic ulcer, Gastritis, Pancreatitis, Ischaemia"),
("Severe tearing", "Vascular catastrophe", "Ruptured AAA, Aortic dissection"),
("Sudden onset ('thunderclap')", "Perforation / vascular rupture", "Perforated duodenal ulcer, Ruptured spleen, Ruptured ectopic"),
("Gradual onset", "Inflammatory / infective", "Appendicitis (early), Cholecystitis, Diverticulitis"),
("Referred to shoulder", "Diaphragmatic irritation", "Subphrenic collection, Ruptured spleen, Ectopic"),
("Relieved by food", "Duodenal pattern", "Duodenal ulcer"),
("Worse with food", "Gastric / ischaemic pattern", "Gastric ulcer, Mesenteric ischaemia"),
]
CONTENT.append(diag_table(
["Character", "Mechanism", "Key Diagnoses"],
pain_char_rows, w2))
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Peritonism (rigid abdomen, guarding, rebound)",
"Haemodynamic instability",
"Suspected vascular catastrophe (AAA, ectopic)",
"Obstruction with strangulation",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 2. VOMITING
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(2)", "VOMITING", colors.HexColor("#7c3aed")),
sp(2),
]))
w3 = [38*mm, CW - 38*mm]
vom_rows = [
("Projectile, non-bilious\n(infant)", "Pyloric stenosis (male infant 2-8 wks), Pyloric stenosis in adults"),
("Bilious vomiting\n(infant/child)", "Duodenal atresia, Malrotation + volvulus, Jejuno-ileal atresia (neonate)"),
("Large volume, faeculent", "Distal SBO or LBO (late), Gastrocolic fistula"),
("Bile-stained, colicky pain", "Small bowel obstruction (SBO): adhesions, hernia, intussusception (child)"),
("Haematemesis", "See BLEEDING section below -- GI bleed"),
("Vomiting + absolute constipation + distension", "Bowel obstruction (SBO or LBO) -- emergency"),
("Post-prandial (30-60 min)", "Peptic ulcer, Gastroparesis, Gastric outflow obstruction"),
("Vomiting + severe epigastric pain radiating to back", "Acute pancreatitis"),
("Vomiting + jaundice + RUQ pain (Charcot's triad)", "Acute cholangitis -- emergency"),
("Vomiting + headache (no abdominal signs)", "Raised ICP, Meningitis -- surgical if hydrocephalus"),
]
CONTENT.append(diag_table(
["Vomiting Character / Context", "Key Surgical Diagnoses"],
vom_rows, w3, alt_color=colors.HexColor("#f5f0ff")))
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Absolute constipation + distension = obstruction until proven otherwise",
"Bilious vomiting in neonate = surgical emergency",
"Haematemesis = upper GI bleed (see below)",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 3. RECTAL BLEEDING / PR BLEED
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(3)", "RECTAL / PER-RECTAL (PR) BLEEDING", RED),
sp(2),
]))
w4 = [38*mm, 30*mm, CW - 68*mm]
pr_rows = [
("Fresh bright red blood on paper only", "Perianal", "Fissure-in-ano, Haemorrhoids (1st/2nd degree)"),
("Bright red blood dripping after defaecation", "Anal canal / rectum", "Haemorrhoids (3rd/4th degree), Fissure, Rectal polyp"),
("Blood mixed with stool", "Colon / rectum", "Colorectal carcinoma, Diverticular disease, IBD (UC)"),
("Blood + mucus (\"slime\")", "Rectum / sigmoid", "Rectal carcinoma, UC, Villous adenoma"),
("Melaena (black tarry stool)", "Upper GI (>~Treitz lig.)", "Peptic ulcer bleed, Varices, Mallory-Weiss, Gastric Ca"),
("Melaena + haematemesis", "Upper GI bleed", "Duodenal ulcer (most common), Oesophageal varices"),
("Massive fresh PR bleed (older patient)", "Right colon / angiodysplasia", "Diverticular bleed, Angiodysplasia, Ischaemic colitis"),
("PR bleed + pain + change in bowel habit (>40 yrs)", "Colon / rectum", "Colorectal carcinoma -- refer urgently"),
("PR bleed + mucus + tenesmus (young patient)", "Rectum", "Ulcerative proctitis, Rectal polyp, Low rectal Ca"),
("PR bleed in child (< 2 yrs)", "Ileum", "Meckel's diverticulum (classically painless, massive)"),
("Blood per rectum + abdominal pain + sausage-shaped mass (child)", "Ileocaecal", "Intussusception"),
]
CONTENT.append(diag_table(
["Appearance / Context", "Level", "Key Diagnoses"],
pr_rows, w4))
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Melaena or haematemesis = upper GI bleed (resuscitate first)",
"Massive bright red PR = lower GI bleed (may need urgent scope/surgery)",
"Change in bowel habit + bleeding + weight loss (>40 yrs) = colorectal Ca until proven otherwise",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 4. HAEMATEMESIS (Upper GI Bleed)
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(4)", "HAEMATEMESIS (UPPER GI BLEED)", RED),
sp(2),
]))
w5 = [40*mm, CW - 40*mm]
haem_rows = [
("Fresh red blood / clots", "Active arterial bleed -- peptic ulcer (most common), varices"),
("Coffee-ground vomit", "Slower bleed: peptic ulcer, gastric erosions, gastric Ca"),
("History of NSAIDs/aspirin", "Peptic ulcer (duodenal > gastric), gastric erosions"),
("History of alcohol / liver disease / splenomegaly", "Oesophageal or gastric varices -- EMERGENCY"),
("History of forceful vomiting then haematemesis", "Mallory-Weiss tear (mucosal tear at GOJ)"),
("Dysphagia + weight loss + haematemesis (older)", "Oesophageal carcinoma"),
("Anorexia + weight loss + epigastric mass", "Gastric carcinoma"),
("Swallowed blood (nosebleed, haemoptysis)", "Not true haematemesis -- take full history"),
]
CONTENT.append(diag_table(
["Context / Character", "Key Diagnoses"],
haem_rows, w5, alt_color=colors.HexColor("#fff5f5")))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 5. SWELLING / LUMP
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(5)", "SWELLING / LUMP", colors.HexColor("#0f766e")),
sp(2),
]))
CONTENT.append(Paragraph("Swelling by Site", ST_LABEL))
CONTENT.append(sp(1))
w6 = [32*mm, 32*mm, CW - 64*mm]
lump_rows = [
("Neck -- midline", "Thyroid / thyroglossal / lymph node", "Thyroid goitre, Thyroglossal cyst (moves with tongue protrusion), Midline lymph node, Dermoid cyst"),
("Neck -- lateral", "Lymph nodes / branchial / carotid", "Lymphadenopathy (reactive/TB/Ca), Branchial cyst (young adult), Carotid body tumour, Sternomastoid tumour (neonate), Cystic hygroma (child)"),
("Breast", "Glandular / connective tissue", "Fibroadenoma (young F, rubbery), Fibrocystic change, Breast abscess (lactating), Breast carcinoma (hard, irregular, tethered)"),
("Groin", "Hernia / lymph node / femoral", "Inguinal hernia (above & medial to pubic tubercle), Femoral hernia (below & lateral -- strangulates more), Lymph node (reactive/TB/Ca), Saphena varix, Femoral artery aneurysm"),
("Scrotum", "Testis / epididymis / cord", "Hydrocoele (transilluminates), Epididymal cyst (above and behind testis, transilluminates), Varicocoele (bag of worms, left side), Testicular tumour (HARD, does NOT transilluminate), Epididymo-orchitis (tender, warm)"),
("Abdominal wall", "Hernia / lipoma / DSTR", "Umbilical hernia, Paraumbilical hernia, Incisional hernia, Lipoma, Desmoid tumour"),
("Abdominal mass", "Visceral / retroperitoneal", "See intra-abdominal section"),
("Perianal / perineal", "Anal canal / skin", "External haemorrhoids, Perianal abscess, Anal fistula opening, Pilonidal sinus, Anal carcinoma"),
("Limb", "Soft tissue / bone", "Lipoma (soft, lobulated, fluctuant), Sebaceous cyst (punctum present), Ganglion (wrist, transilluminates), Lymph node, Bone tumour"),
]
CONTENT.append(diag_table(
["Site", "Structures", "Key Diagnoses (differentials)"],
lump_rows, w6))
CONTENT.append(sp(3))
CONTENT.append(Paragraph("Features of a Lump -- Assess Every Swelling Systematically", ST_LABEL))
CONTENT.append(sp(1))
feat_data = [
[Paragraph("Site", ST_CELL_B), Paragraph("Size", ST_CELL_B), Paragraph("Shape", ST_CELL_B),
Paragraph("Surface", ST_CELL_B), Paragraph("Consistency", ST_CELL_B),
Paragraph("Mobility", ST_CELL_B), Paragraph("Transilluminates?", ST_CELL_B),
Paragraph("Reducible?", ST_CELL_B), Paragraph("Pulsatile?", ST_CELL_B)],
[Paragraph("Document precisely", ST_CELL),
Paragraph("Measure (cm)", ST_CELL),
Paragraph("Round / oval / irregular", ST_CELL),
Paragraph("Smooth / nodular", ST_CELL),
Paragraph("Soft / firm / hard / fluctuant / rubbery", ST_CELL),
Paragraph("Free / tethered to skin / deep", ST_CELL),
Paragraph("Fluid/cyst = yes", ST_CELL),
Paragraph("Hernia / varix = yes", ST_CELL),
Paragraph("Vascular tumour = yes", ST_CELL)],
]
feat_tbl = Table(feat_data, colWidths=[CW/9]*9)
feat_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_HDR),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 6.5),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 2),
("GRID", (0,0), (-1,-1), 0.3, LINE),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
CONTENT.append(feat_tbl)
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Hard, irregular, fixed, tethered lump = malignancy until proven otherwise",
"Rapidly enlarging lump",
"Testicular lump = tumour until proven otherwise (do NOT transilluminate and diagnose)",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 6. DYSPHAGIA
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(6)", "DYSPHAGIA (DIFFICULTY SWALLOWING)", colors.HexColor("#b45309")),
sp(2),
]))
w7 = [42*mm, CW - 42*mm]
dys_rows = [
("Solids only -> progressing to liquids\nOlder patient + weight loss", "Carcinoma of oesophagus (most common cause of progressive dysphagia in elderly)"),
("Solids and liquids (intermittent)\nYoung patient, worse with stress", "Achalasia cardia (failure of LOS relaxation)"),
("Solids and liquids (progressive)\nMidline chest pain", "Achalasia, Pharyngeal pouch, Diffuse oesophageal spasm"),
("Preceded by heartburn for years\nOlder patient, smoker", "Carcinoma in Barrett's oesophagus, Peptic stricture"),
("Foreign body sensation\nPulsatile mass in neck", "Pharyngeal pouch (Zenker's diverticulum -- regurgitation of undigested food)"),
("Difficulty initiating swallow, nasal regurgitation", "Neurological cause (not surgical primary) -- but consider pharyngeal pouch"),
("Plummer-Vinson syndrome\n(iron deficiency + glossitis + koilonychia)", "Post-cricoid web -- increased risk of post-cricoid Ca"),
("History of caustic ingestion", "Oesophageal stricture"),
("Dysphagia + mediastinal widening on CXR", "External compression: lymphoma, aortic aneurysm, goitre, Ca lung"),
]
CONTENT.append(diag_table(
["Clinical Context", "Key Diagnoses"],
dys_rows, w7, alt_color=colors.HexColor("#fffbeb")))
CONTENT.append(sp(2))
CONTENT.append(key_box(
"Progressive dysphagia solids -> liquids = mechanical (carcinoma / stricture). "
"Solids AND liquids from onset = motility disorder (achalasia). "
"All cases need endoscopy / barium swallow."))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 7. JAUNDICE
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(7)", "JAUNDICE (Surgical / Obstructive Focus)", colors.HexColor("#92400e")),
sp(2),
]))
w8 = [36*mm, 30*mm, CW - 66*mm]
jd_rows = [
("Painless progressive jaundice\nPalpable gallbladder (Courvoisier's sign)", "Obstructive -- distal", "Carcinoma of head of pancreas (Courvoisier positive), Cholangiocarcinoma, Ampullary carcinoma"),
("Jaundice + colicky RUQ pain\n+ fever (Charcot's triad)", "Obstructive -- CBD stone", "Choledocholithiasis with cholangitis -- EMERGENCY"),
("Jaundice + RUQ pain + Fever + Hypotension + Confusion (Reynolds' pentad)", "Obstructive -- severe", "Suppurative cholangitis -- life-threatening"),
("Intermittent jaundice + pruritus\nNo pain, no fever", "Obstructive", "Benign biliary stricture, CBD stone, Choledochal cyst"),
("Jaundice + anorexia + weight loss + no pain", "Obstructive", "Pancreatic head Ca, Cholangiocarcinoma (Klatskin tumour at hilum)"),
("Pre-hepatic (haemolytic)\nDark urine absent, pale stools absent", "Pre-hepatic", "Sickle cell, G6PD, Hereditary spherocytosis -- surgical = splenectomy sometimes"),
("Hepatic jaundice\nTransaminases elevated", "Hepatocellular", "Hepatitis, Cirrhosis, Drug-induced -- not primarily surgical"),
("Neonatal jaundice\npersisting >2 wks, pale stools, dark urine", "Obstructive", "Biliary atresia -- surgical emergency (Kasai procedure <60 days)"),
]
CONTENT.append(diag_table(
["Clinical Pattern", "Type", "Key Diagnoses"],
jd_rows, w8))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 8. URINARY SYMPTOMS
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(8)", "URINARY SYMPTOMS", MID),
sp(2),
]))
w9 = [42*mm, CW - 42*mm]
uri_rows = [
("Haematuria (painless) -- older patient", "Carcinoma of bladder (TCC) -- refer urgently | Renal cell carcinoma"),
("Haematuria + loin pain + abdominal mass\n(\"classic triad\")", "Renal cell carcinoma (only 10% have all three -- late sign)"),
("Haematuria + severe colicky loin-to-groin pain", "Ureteric calculus (renal colic)"),
("Haematuria + dysuria + frequency + fever", "Pyelonephritis / UTI with haematuria -- usually not surgical unless abscess"),
("Poor stream + nocturia + hesitancy + post-void dribbling\n(older male)", "Benign prostatic hyperplasia (BPH) | Carcinoma of prostate"),
("Acute urinary retention (painful, no urine)\nOlder male", "BPH (most common), Prostate Ca, Stricture, Clot retention post-haematuria"),
("Chronic urinary retention (painless, large bladder)\nOverflow incontinence", "BPH, Neurogenic bladder, Posterior urethral stricture, Spinal cord pathology"),
("Scrotal/testicular pain + swelling\n(young male, sudden onset)", "Testicular torsion -- SURGICAL EMERGENCY (6 hrs window)"),
("Scrotal swelling (painless) + testicular mass", "Testicular tumour (teratoma <30 yrs, seminoma 30-40 yrs)"),
("Urethral discharge + dysuria\n(young sexually active)", "Urethritis (STI: gonorrhoea, chlamydia) -- not surgical primarily"),
]
CONTENT.append(diag_table(
["Urinary Symptom / Context", "Key Diagnoses"],
uri_rows, w9))
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Painless haematuria in any adult = bladder/renal carcinoma until proven otherwise",
"Testicular torsion = 6-hour window -- immediate surgical exploration",
"Acute urinary retention = catheterise then investigate",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 9. CHANGE IN BOWEL HABIT
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(9)", "CHANGE IN BOWEL HABIT", colors.HexColor("#166534")),
sp(2),
]))
w10 = [42*mm, CW - 42*mm]
bh_rows = [
("Alternating constipation and diarrhoea\n>40 yrs, with PR bleed / weight loss", "Colorectal carcinoma -- urgent colonoscopy"),
("Increasing constipation + colicky pain + distension\nGradual onset (older)", "Left-sided colonic tumour (LBO), Diverticular stricture"),
("Constipation only (no other features)\nYounger patient", "Functional / IBS, Hypothyroidism, Drug-induced"),
("Sudden onset constipation + absolute\n(no stool, no flatus)", "Bowel obstruction (SBO or LBO)"),
("Diarrhoea + blood + mucus + urgency + tenesmus", "Ulcerative colitis (UC) -- surgical if fulminant/toxic megacolon"),
("Diarrhoea + crampy RIF pain + weight loss + perianal disease", "Crohn's disease (right-sided, skip lesions, transmural)"),
("Explosive diarrhoea post-antibiotics (elderly)", "Clostridioides difficile colitis -- can require surgical colectomy if toxic"),
("Constipation since birth (neonate)\nAbdominal distension + bilious vomiting", "Hirschsprung's disease -- emergency (Hartmann's or pull-through)"),
("Ribbon-like stools + straining", "Anal stenosis, Low rectal carcinoma, Anal fissure (pain)"),
]
CONTENT.append(diag_table(
["Bowel Habit Pattern / Context", "Key Diagnoses"],
bh_rows, w10, alt_color=colors.HexColor("#f0fdf4")))
CONTENT.append(sp(2))
CONTENT.append(key_box(
"Left colon Ca -> obstructive (constipation, narrow stools). "
"Right colon Ca -> anaemia, weight loss, mass (less obstruction due to wide lumen and liquid stool). "
"Any change in bowel habit >4 weeks in patient >40 yrs = colonoscopy."))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 10. WEIGHT LOSS (SURGICAL CONTEXT)
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(10)", "WEIGHT LOSS (Surgical Context)", colors.HexColor("#7f1d1d")),
sp(2),
]))
w11 = [42*mm, CW - 42*mm]
wl_rows = [
("Weight loss + dysphagia", "Oesophageal Ca, Gastric Ca, Achalasia"),
("Weight loss + painless jaundice", "Carcinoma of head of pancreas, Cholangiocarcinoma"),
("Weight loss + PR bleed + change in bowel habit", "Colorectal carcinoma"),
("Weight loss + haematuria (painless)", "Renal cell carcinoma, Bladder carcinoma"),
("Weight loss + haemoptysis", "Lung carcinoma (not primarily surgical, but surgical referral for lobectomy/VATS)"),
("Weight loss + cervical lymphadenopathy", "Lymphoma, Metastatic carcinoma, TB"),
("Weight loss + epigastric pain radiating to back", "Carcinoma of pancreas, Chronic pancreatitis"),
("Weight loss + night sweats + fever (B-symptoms)", "Lymphoma -- surgical for biopsy"),
("Weight loss + abdominal mass + ascites", "GI carcinoma with peritoneal spread, Lymphoma, Ovarian Ca"),
]
CONTENT.append(diag_table(
["Associated Feature", "Key Surgical Diagnoses"],
wl_rows, w11))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 11. BREAST SYMPTOMS
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(11)", "BREAST SYMPTOMS", colors.HexColor("#9d174d")),
sp(2),
]))
w12 = [42*mm, CW - 42*mm]
br_rows = [
("Painless hard irregular lump\nFixed to skin or deep structures\n>35 yrs", "Breast carcinoma -- URGENT referral"),
("Smooth, firm, rubbery, mobile lump\nYoung female (<35 yrs)", "Fibroadenoma (\"breast mouse\" -- benign)"),
("Tender, fluctuant lump\nLactating woman", "Breast abscess (Staph. aureus most common)"),
("Tender, nodular bilateral lumps\nCyclical pain", "Fibrocystic change (benign)"),
("Bloody / blood-stained nipple discharge", "Intraductal papilloma (most common), Ductal carcinoma"),
("Nipple retraction (new)\nEczematous nipple change", "Breast carcinoma (Paget's disease of nipple)"),
("Peau d'orange skin\nFixed hard lump, lymphadenopathy", "Advanced breast carcinoma (lymphoedema of skin)"),
("Breast lump + axillary lymphadenopathy", "Metastatic breast carcinoma"),
("Breast enlargement in male (gynaecomastia)", "Drug-induced (spironolactone, digoxin, cimetidine), Liver cirrhosis, Testicular tumour"),
]
CONTENT.append(diag_table(
["Clinical Feature / Context", "Key Diagnoses"],
br_rows, w12, alt_color=colors.HexColor("#fff1f2")))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# 12. LIMB / VASCULAR SYMPTOMS
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(12)", "LIMB / VASCULAR SYMPTOMS", colors.HexColor("#1e3a5f")),
sp(2),
]))
w13 = [42*mm, CW - 42*mm]
vasc_rows = [
("Intermittent claudication\n(calf pain on walking, relieved by rest)", "Peripheral arterial disease (PAD) -- atherosclerosis"),
("Rest pain (burning pain at night, relieved by hanging foot)", "Severe PAD (critical limb ischaemia) -- urgent revascularisation"),
("Sudden onset cold, painful, pale, pulseless, paraesthetic limb\n(6 Ps)", "Acute limb ischaemia -- EMERGENCY (embolus or thrombosis)"),
("Varicose veins + ankle ulcer + lipodermatosclerosis", "Chronic venous insufficiency"),
("DVT: calf pain + swelling + warmth", "Deep vein thrombosis -- risk of PE"),
("Pulsatile abdominal mass (>3 cm aorta)\n+ back/loin pain", "Abdominal aortic aneurysm (AAA) -- if painful, urgent; if ruptured, emergency"),
("Expanding pulsatile groin lump\nPost-catheterisation", "False femoral aneurysm (pseudoaneurysm)"),
("Limb swelling (non-pitting)\nPost-axillary dissection / filariasis", "Lymphoedema"),
("Diabetic foot ulcer + cellulitis", "Diabetic foot (neuropathic +/- ischaemic) -- risk of wet gangrene, amputation"),
]
CONTENT.append(diag_table(
["Symptom / Context", "Key Diagnoses"],
vasc_rows, w13))
CONTENT.append(sp(2))
CONTENT.append(red_flags_box([
"Acute limb ischaemia (6 Ps) = surgical emergency (< 6 hours)",
"Ruptured AAA = haemodynamic emergency",
"Wet gangrene = surgical emergency",
]))
CONTENT.append(sp(4))
# ════════════════════════════════════════════════════════════════════════════════
# QUICK-REFERENCE MNEMONICS BOX
# ════════════════════════════════════════════════════════════════════════════════
CONTENT.append(KeepTogether([
sym_header("(*)", "QUICK MNEMONICS FOR MBBS SURGERY", colors.HexColor("#374151")),
sp(2),
]))
mn_data = [
["SOCRATES", "Site, Onset, Character, Radiation, Associated sx, Timing, Exacerbating/relieving, Severity -- for ANY pain"],
["ABCDE (trauma)", "Airway, Breathing, Circulation, Disability, Exposure -- primary survey order"],
["6 Ps of acute ischaemia", "Pain, Pallor, Pulselessness, Paraesthesia, Paralysis, Perishing cold"],
["Courvoisier's Law", "Palpable GB + jaundice = NOT gallstones (wall too fibrosed) -- think Ca head of pancreas"],
["Charcot's Triad", "RUQ pain + Fever + Jaundice = Acute cholangitis (CBD obstruction + infection)"],
["Reynolds' Pentad", "Charcot's triad + Hypotension + Confusion = Suppurative cholangitis (life-threatening)"],
["Murphy's sign", "Inspiratory arrest on deep palpation of RUQ = Acute cholecystitis"],
["Virchow's Triad", "Stasis + Hypercoagulability + Endothelial damage = DVT/thrombosis"],
["Meckel's Rule of 2s", "2 feet from ileocaecal valve, 2 inches long, 2% of population, presents in 1st 2 years of life"],
["Transillumination", "Hydrocoele, Epididymal cyst, Ranula, Cystic hygroma = transilluminate. Solid / blood-filled = do NOT"],
]
mn_tbl = Table([[Paragraph(r[0], ST_CELL_B), Paragraph(r[1], ST_CELL)] for r in mn_data],
colWidths=[48*mm, CW - 48*mm])
mn_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LIGHT_HDR),
("LINEBELOW", (0,0), (-1,-1), 0.3, LINE),
("GRID", (0,0), (-1,-1), 0.2, LINE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
CONTENT.append(mn_tbl)
CONTENT.append(sp(3))
# Footer disclaimer
CONTENT.append(Paragraph(
"Reference: S Das A Manual on Clinical Surgery 13th Ed | Bailey and Love's Short Practice of Surgery 28th Ed | "
"Bailey and Love's | Yamada's Textbook of Gastroenterology | ROSEN's Emergency Medicine | Symptom to Diagnosis 4th Ed "
"-- For educational/clinical training use only.",
ST_FOOTER))
# ══════════════════════════════════════════════════════════════════════════════
# BUILD
# ══════════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=L_MARGIN,
rightMargin=R_MARGIN,
topMargin=T_MARGIN + 28*mm,
bottomMargin=B_MARGIN + 8*mm,
)
doc.build(
CONTENT,
onFirstPage=banner,
onLaterPages=banner_later,
)
print("DONE:", OUTPUT)
cd /home/daytona/workspace/history-taking-template && python symptom_surgical_gu…
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/…
Surgical Symptom Guide MBBS
PDF Document · PDF
| # | Symptom | What's covered |
|---|---|---|
| 1 | Abdominal Pain | Differentials by region (RUQ/Epigastric/RIF/LIF etc.) + character (colicky/tearing/gradual) |
| 2 | Vomiting | Projectile, bilious, faeculent, obstructive, pancreatitis, cholangitis patterns |
| 3 | PR / Rectal Bleeding | Bright red vs melaena, by level (anorectal -> colon -> upper GI), incl. Meckel's & intussusception |
| 4 | Haematemesis | Coffee-ground vs fresh blood, varices, Mallory-Weiss, gastric Ca |
| 5 | Swelling / Lump | By site (neck, breast, groin, scrotum, limb) + 9-feature lump assessment grid |
| 6 | Dysphagia | Solids-only vs both, progressive vs intermittent - carcinoma vs achalasia vs Zenker's |
| 7 | Jaundice | Courvoisier's, Charcot's triad, Reynolds' pentad, biliary atresia (neonate) |
| 8 | Urinary Symptoms | Painless haematuria, retention, renal colic, testicular torsion |
| 9 | Change in Bowel Habit | Left vs right colon Ca patterns, SBO, Hirschsprung's, UC vs Crohn's |
| 10 | Weight Loss | Linked to GI/urological/vascular malignancy |
| 11 | Breast Symptoms | Fibroadenoma vs Ca vs abscess, nipple discharge, Paget's disease |
| 12 | Limb / Vascular | Claudication, acute ischaemia (6 Ps), DVT, AAA, diabetic foot |
Why pd is not downloading
/home/daytona/workspace/history-taking-template/Surgical_Symptom_Guide_MBBS.pdf