Write about opd pt cases as many as you know with images and prescription
mkdir -p /home/daytona/workspace/opd-cases && ls /home/daytona/workspace/opd-cases/
~/opd-cases/generate_opd_pdf.py
#!/usr/bin/env python3
"""
OPD Case Book - General Medicine
Generates a comprehensive PDF with 10 common OPD patient cases,
clinical findings, investigations, diagnosis, and prescriptions.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, white, black
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import Image as RLImage
from reportlab.lib import colors
import urllib.request
import os, io
OUTPUT_PATH = "/home/daytona/workspace/opd-cases/OPD_Case_Book_General_Medicine.pdf"
# ─── Colour Palette ──────────────────────────────────────────────────────────
C_DARK_BLUE = HexColor("#1A3A5C")
C_MED_BLUE = HexColor("#2E6DA4")
C_LIGHT_BLUE = HexColor("#D6E8F7")
C_ACCENT = HexColor("#E84545")
C_GREEN = HexColor("#2E7D32")
C_LIGHT_GREEN = HexColor("#E8F5E9")
C_ORANGE = HexColor("#E65100")
C_LIGHT_ORANGE= HexColor("#FFF3E0")
C_PURPLE = HexColor("#6A1B9A")
C_LIGHT_PURPLE= HexColor("#F3E5F5")
C_TEAL = HexColor("#00695C")
C_LIGHT_TEAL = HexColor("#E0F2F1")
C_GREY_BG = HexColor("#F5F5F5")
C_BORDER = HexColor("#BBDEFB")
C_RX_BG = HexColor("#FFFDE7")
C_RX_BORDER = HexColor("#F9A825")
C_TABLE_HDR = HexColor("#1565C0")
C_ROW_ALT = HexColor("#EBF4FB")
# ─── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, **kwargs):
return ParagraphStyle(name, **kwargs)
TITLE_STYLE = make_style("Title",
fontSize=26, textColor=white, fontName="Helvetica-Bold",
alignment=TA_CENTER, spaceAfter=4, leading=32)
SUBTITLE_STYLE = make_style("Subtitle",
fontSize=13, textColor=HexColor("#B3D4F0"), fontName="Helvetica",
alignment=TA_CENTER, spaceAfter=2, leading=18)
CASE_HEADING_STYLE = make_style("CaseHeading",
fontSize=17, textColor=white, fontName="Helvetica-Bold",
alignment=TA_LEFT, spaceBefore=0, spaceAfter=0, leading=22,
leftIndent=6)
SECTION_STYLE = make_style("Section",
fontSize=10.5, textColor=C_DARK_BLUE, fontName="Helvetica-Bold",
spaceBefore=6, spaceAfter=2, leading=14)
BODY_STYLE = make_style("Body",
fontSize=9.5, textColor=HexColor("#212121"), fontName="Helvetica",
spaceBefore=2, spaceAfter=2, leading=13, alignment=TA_JUSTIFY)
BULLET_STYLE = make_style("Bullet",
fontSize=9.5, textColor=HexColor("#212121"), fontName="Helvetica",
spaceBefore=1, spaceAfter=1, leading=13, leftIndent=12, bulletIndent=3)
RX_TITLE_STYLE = make_style("RxTitle",
fontSize=11.5, textColor=C_ORANGE, fontName="Helvetica-Bold",
spaceBefore=4, spaceAfter=3, leading=16)
RX_BODY_STYLE = make_style("RxBody",
fontSize=9.5, textColor=HexColor("#3E2723"), fontName="Helvetica",
spaceBefore=2, spaceAfter=2, leading=14, leftIndent=10)
CAPTION_STYLE = make_style("Caption",
fontSize=8, textColor=HexColor("#546E7A"), fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceBefore=2, spaceAfter=4, leading=11)
FOOTER_STYLE = make_style("Footer",
fontSize=7.5, textColor=HexColor("#90A4AE"), fontName="Helvetica",
alignment=TA_CENTER, leading=10)
PAGE_NUM_STYLE = make_style("PageNum",
fontSize=7.5, textColor=HexColor("#90A4AE"), fontName="Helvetica",
alignment=TA_RIGHT, leading=10)
# ─── Helper: Download image ───────────────────────────────────────────────────
CACHE_DIR = "/home/daytona/workspace/opd-cases/img_cache"
os.makedirs(CACHE_DIR, exist_ok=True)
def fetch_img(url, fname):
path = os.path.join(CACHE_DIR, fname)
if not os.path.exists(path):
try:
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=15) as r:
with open(path, "wb") as f:
f.write(r.read())
except Exception as e:
print(f" [WARN] Could not fetch {url}: {e}")
return None
return path
def rl_image(url, fname, width=12*cm, max_height=7*cm):
path = fetch_img(url, fname)
if not path:
return None
try:
img = RLImage(path, width=width)
if img.imageHeight * (width / img.imageWidth) > max_height:
img = RLImage(path, height=max_height)
return img
except Exception as e:
print(f" [WARN] Could not create image {path}: {e}")
return None
# ─── Helper: Prescription Table ───────────────────────────────────────────────
def rx_table(drugs):
"""
drugs: list of (Drug Name, Dose, Route, Frequency, Duration)
"""
header = ["Drug", "Dose", "Route", "Frequency", "Duration"]
data = [header] + drugs
col_widths = [5.5*cm, 2.2*cm, 2*cm, 3.8*cm, 2.5*cm]
tbl = Table(data, colWidths=col_widths, repeatRows=1)
style = TableStyle([
("BACKGROUND", (0,0), (-1,0), C_TABLE_HDR),
("TEXTCOLOR", (0,0), (-1,0), white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("BOTTOMPADDING",(0,0),(-1,0), 5),
("TOPPADDING", (0,0),(-1,0), 5),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("TOPPADDING", (0,1), (-1,-1), 3),
("BOTTOMPADDING",(0,1),(-1,-1), 3),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#90CAF9")),
("ROWBACKGROUNDS",(0,1),(-1,-1), [white, C_ROW_ALT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
tbl.setStyle(style)
return tbl
# ─── Helper: Investigations Table ─────────────────────────────────────────────
def inv_table(rows):
"""rows: list of (Test, Result, Normal Range, Interpretation)"""
header = ["Investigation", "Result", "Normal Range", "Interpretation"]
data = [header] + rows
col_widths = [4.5*cm, 2.5*cm, 3.5*cm, 5.5*cm]
tbl = Table(data, colWidths=col_widths, repeatRows=1)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_MED_BLUE),
("TEXTCOLOR", (0,0), (-1,0), white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("BOTTOMPADDING",(0,0),(-1,0), 5),
("TOPPADDING", (0,0),(-1,0), 5),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("TOPPADDING", (0,1), (-1,-1), 3),
("BOTTOMPADDING",(0,1),(-1,-1), 3),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#B3C6E7")),
("ROWBACKGROUNDS",(0,1),(-1,-1),[white, C_LIGHT_BLUE]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
return tbl
# ─── Helper: Case Header ──────────────────────────────────────────────────────
def case_header(number, title, color):
data = [[Paragraph(f"CASE {number}", make_style(f"cn{number}",
fontSize=9, textColor=HexColor("#B0D4F7"),
fontName="Helvetica-Bold", leading=12)),
Paragraph(title, CASE_HEADING_STYLE)]]
tbl = Table(data, colWidths=[1.8*cm, 14.2*cm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("LEFTPADDING",(0,0),(-1,-1), 8),
("ROUNDEDCORNERS", [6, 6, 6, 6]),
]))
return tbl
def section_box(label, content_list, bg=C_GREY_BG, border=C_BORDER):
"""Returns a styled KeepTogether block for a section."""
items = []
items.append(Paragraph(label, SECTION_STYLE))
for c in content_list:
items.append(c)
tbl = Table([[items]], colWidths=[16*cm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 0.6, border),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING",(0,0),(-1,-1), 8),
("RIGHTPADDING",(0,0),(-1,-1), 8),
]))
return tbl
# ─── Page Templates ───────────────────────────────────────────────────────────
page_counter = [0]
def on_page(canvas, doc):
page_counter[0] += 1
canvas.saveState()
# footer line
canvas.setStrokeColor(C_BORDER)
canvas.setLineWidth(0.5)
canvas.line(doc.leftMargin, 1.5*cm, A4[0]-doc.rightMargin, 1.5*cm)
canvas.setFont("Helvetica", 7)
canvas.setFillColor(HexColor("#90A4AE"))
canvas.drawString(doc.leftMargin, 1.2*cm,
"OPD Case Book — General Medicine | For Educational Purposes Only")
canvas.drawRightString(A4[0]-doc.rightMargin, 1.2*cm,
f"Page {doc.page}")
canvas.restoreState()
# ─── CONTENT BUILDER ──────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.2*cm, bottomMargin=2.2*cm,
title="OPD Case Book — General Medicine",
author="Orris Medical AI"
)
story = []
# ══════════════════════════════════════════════════════
# COVER PAGE
# ══════════════════════════════════════════════════════
story.append(Spacer(1, 3*cm))
cover_bg = Table(
[[Paragraph("OPD CASE BOOK", TITLE_STYLE)],
[Paragraph("General Medicine & Internal Medicine", SUBTITLE_STYLE)],
[Spacer(1, 0.3*cm)],
[Paragraph("10 Fully Worked Patient Cases with Investigations & Prescriptions", SUBTITLE_STYLE)],
],
colWidths=[16*cm]
)
cover_bg.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 14),
("BOTTOMPADDING",(0,0),(-1,-1), 14),
("LEFTPADDING",(0,0),(-1,-1), 16),
("RIGHTPADDING",(0,0),(-1,-1), 16),
("ROUNDEDCORNERS", [10,10,10,10]),
]))
story.append(cover_bg)
story.append(Spacer(1, 1.5*cm))
# Cover info table
info_data = [
["Department:", "General Medicine / Internal Medicine"],
["Target Audience:", "Medical Students, Interns, Junior Residents"],
["Cases Covered:", "10 Common OPD Conditions"],
["Content:", "History, Examination, Investigations, Diagnosis, Prescription"],
["Reference:", "Harrison's Principles of Internal Medicine 22E; Katzung Pharmacology 16E; Goodman & Gilman's; Robbins Pathology"],
]
info_tbl = Table(info_data, colWidths=[4.5*cm, 11.5*cm])
info_tbl.setStyle(TableStyle([
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("TEXTCOLOR", (0,0), (0,-1), C_DARK_BLUE),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("TOPPADDING", (0,0),(-1,-1), 5),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_GREY_BG]),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
]))
story.append(info_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# TABLE OF CONTENTS
# ══════════════════════════════════════════════════════
story.append(Paragraph("TABLE OF CONTENTS", make_style("TOCHead",
fontSize=15, textColor=C_DARK_BLUE, fontName="Helvetica-Bold",
spaceBefore=0, spaceAfter=12, alignment=TA_CENTER)))
toc_items = [
("Case 1", "Hypertension (Stage 2 Essential)", C_MED_BLUE),
("Case 2", "Type 2 Diabetes Mellitus", C_GREEN),
("Case 3", "Bronchial Asthma (Mild Persistent)", C_TEAL),
("Case 4", "Gastroesophageal Reflux Disease (GERD)", C_ORANGE),
("Case 5", "Urinary Tract Infection (Uncomplicated)", C_PURPLE),
("Case 6", "Iron Deficiency Anaemia", C_ACCENT),
("Case 7", "Hypothyroidism", HexColor("#558B2F")),
("Case 8", "Enteric Fever (Typhoid)", HexColor("#795548")),
("Case 9", "Acute Upper Respiratory Tract Infection", HexColor("#0277BD")),
("Case 10","Osteoarthritis of Knee", HexColor("#37474F")),
]
for num, title, col in toc_items:
row_data = [[
Paragraph(num, make_style(f"tc{num}",
fontSize=9, textColor=white, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=12)),
Paragraph(title, make_style(f"tt{num}",
fontSize=10, textColor=C_DARK_BLUE, fontName="Helvetica",
leading=14))
]]
rtbl = Table(row_data, colWidths=[2*cm, 14*cm])
rtbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), col),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING",(0,0),(-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
]))
story.append(rtbl)
story.append(Spacer(1, 1.5*mm))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 1: HYPERTENSION
# ══════════════════════════════════════════════════════
story.append(case_header(1, "Hypertension — Stage 2 Essential", C_MED_BLUE))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mr. Ramesh Kumar", "Age:", "52 years"],
["Gender:", "Male", "Occupation:", "Office Manager"],
["Weight:", "82 kg", "Height:", "168 cm | BMI: 29.1"],
["Chief Complaint:", "Severe headache, pulsating, occipital, 3 days", "", ""],
["Duration:", "Hypertension diagnosed 2 years ago — irregular treatment", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_MED_BLUE),
("TEXTCOLOR",(2,0),(2,-1),C_MED_BLUE),
("SPAN",(1,3),(3,3)),
("SPAN",(1,4),(3,4)),
]))
], bg=C_LIGHT_BLUE, border=C_MED_BLUE))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY OF PRESENTING ILLNESS", [
Paragraph("• Severe pulsating headache in occipital region for 3 days, worse in the morning.", BULLET_STYLE),
Paragraph("• Associated blurring of vision and dizziness.", BULLET_STYLE),
Paragraph("• No chest pain, palpitations, or shortness of breath.", BULLET_STYLE),
Paragraph("• History of essential hypertension for 2 years; was on Amlodipine 5 mg but stopped 6 months ago.", BULLET_STYLE),
Paragraph("• Family history: Father had hypertension and died of stroke.", BULLET_STYLE),
Paragraph("• Smokes 10 cigarettes/day; drinks alcohol socially. High salt diet.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION FINDINGS", [
Table([
["BP (right arm):", "172/106 mmHg", "BP (left arm):", "168/102 mmHg"],
["Pulse:", "88 bpm, regular", "SpO2:", "98% on room air"],
["Temp:", "Afebrile", "RR:", "16/min"],
["Eyes:", "Papilloedema grade II (Keith-Wagener)", "Fundus:", "A:V nicking"],
["CVS:", "S1, S2 normal; no murmur", "Chest:", "Clear"],
["Abdomen:", "Soft; no organomegaly", "CNS:", "No focal deficit"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_DARK_BLUE),
("TEXTCOLOR",(2,0),(2,-1),C_DARK_BLUE),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_LIGHT_BLUE]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Hb", "13.8 g/dL", "13–17 g/dL", "Normal"],
["Blood glucose (fasting)", "102 mg/dL", "70–100 mg/dL", "Borderline high"],
["Serum creatinine", "1.1 mg/dL", "0.7–1.3 mg/dL", "Normal"],
["eGFR", "72 mL/min/1.73m²", ">60", "CKD Stage 2 (borderline)"],
["Serum electrolytes", "Na 140, K 4.1", "Na 136–145, K 3.5–5", "Normal"],
["Lipid profile (LDL)", "142 mg/dL", "<100 mg/dL", "Elevated — dyslipidaemia"],
["ECG", "Left ventricular hypertrophy", "Normal sinus rhythm", "LVH pattern"],
["Urine dipstick", "Protein +1", "Negative", "Microalbuminuria"],
["Chest X-ray", "Cardiomegaly", "CTR <0.5", "Enlarged cardiac silhouette"],
["Echocardiogram", "LVEF 55%, concentric LVH", "EF >55%", "LVH — no systolic dysfunction"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
diag_data = [["Primary:", "Stage 2 Essential Hypertension (BP 172/106 mmHg)"],
["Complication:", "Hypertensive Retinopathy (Grade II), LVH, Microalbuminuria"],
["Risk:", "High cardiovascular risk (Framingham 10-year risk >10%)"],
["Comorbidity:", "Dyslipidaemia"]]
dtbl = Table(diag_data, colWidths=[2.8*cm, 13.2*cm])
dtbl.setStyle(TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9.5),
("TEXTCOLOR",(0,0),(0,-1),C_ACCENT),
("TOPPADDING",(0,0),(-1,-1),4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
("BACKGROUND",(0,0),(-1,0),HexColor("#FFEBEE")),
("GRID",(0,0),(-1,-1),0.4,HexColor("#FFCDD2")),
]))
story.append(dtbl)
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Amlodipine", "10 mg", "Oral", "Once daily (morning)", "Long-term"],
["Tab. Losartan", "50 mg", "Oral", "Once daily (morning)", "Long-term"],
["Tab. Hydrochlorothiazide", "12.5 mg", "Oral", "Once daily (morning)", "Long-term"],
["Tab. Atorvastatin", "20 mg", "Oral", "Once daily (night)", "Long-term"],
["Tab. Aspirin (low dose)", "75 mg", "Oral", "Once daily after food", "Long-term"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Non-Pharmacological Advice:</b> DASH diet (low salt <2g/day, high fruit/veg). "
"Smoking cessation. Alcohol restriction. Regular aerobic exercise 30 min/day. "
"Weight reduction target BMI <25. BP monitoring at home twice daily. "
"Follow-up in 2 weeks for BP check; ophthalmology referral.",
make_style("advice1", fontSize=8.5, textColor=HexColor("#4A148C"),
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 2: TYPE 2 DIABETES MELLITUS
# ══════════════════════════════════════════════════════
story.append(case_header(2, "Type 2 Diabetes Mellitus (Newly Diagnosed)", C_GREEN))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mrs. Sunita Devi", "Age:", "48 years"],
["Gender:", "Female", "Occupation:", "Housewife"],
["Weight:", "78 kg", "Height:", "155 cm | BMI: 32.5"],
["Chief Complaint:", "Excessive thirst, frequent urination, fatigue for 2 months", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_GREEN),
("TEXTCOLOR",(2,0),(2,-1),C_GREEN),
("SPAN",(1,3),(3,3)),
]))
], bg=C_LIGHT_GREEN, border=C_GREEN))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Polydipsia, polyuria, and nocturia (x3/night) for 2 months.", BULLET_STYLE),
Paragraph("• Unexplained weight loss of ~4 kg over 2 months.", BULLET_STYLE),
Paragraph("• Blurring of vision intermittently.", BULLET_STYLE),
Paragraph("• Tingling and numbness in both feet.", BULLET_STYLE),
Paragraph("• Family history: Mother has diabetes and hypertension.", BULLET_STYLE),
Paragraph("• Sedentary lifestyle; high-carbohydrate diet.", BULLET_STYLE),
Paragraph("• No history of pancreatitis or steroid use.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["BP:", "138/86 mmHg", "Pulse:", "80 bpm, regular"],
["Weight:", "78 kg", "BMI:", "32.5 (Obese class I)"],
["Skin:", "Acanthosis nigricans at nape", "Feet:", "Reduced vibration sense bilaterally"],
["Eyes:", "No retinopathy on fundoscopy", "CVS/Chest:", "Normal"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_LIGHT_GREEN]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Fasting blood glucose", "186 mg/dL", "70–100 mg/dL", "ELEVATED — diagnostic of DM"],
["2-hr OGTT glucose", "238 mg/dL", "<140 mg/dL", "ELEVATED — DM confirmed"],
["HbA1c", "8.6%", "<5.7% (normal)", "Poorly controlled — target <7%"],
["Fasting insulin", "22 µU/mL", "2–25 µU/mL", "High — insulin resistance"],
["Serum creatinine", "0.9 mg/dL", "0.6–1.1 mg/dL", "Normal"],
["Urine microalbumin", "42 mg/24hr", "<30 mg/24hr", "Microalbuminuria — early nephropathy"],
["Lipid profile (LDL)", "148 mg/dL", "<100 mg/dL", "Elevated"],
["Thyroid (TSH)", "2.4 mIU/L", "0.5–5 mIU/L", "Normal"],
["CBC", "Hb 11.8 g/dL", "12–16 (F)", "Mild anaemia"],
["Fundoscopy", "No diabetic retinopathy", "—", "Normal at present"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Primary:</b> Type 2 Diabetes Mellitus (HbA1c 8.6%, FBG 186 mg/dL) "
" | <b>Complications:</b> Early peripheral neuropathy, Microalbuminuria "
" | <b>Comorbidity:</b> Dyslipidaemia, Obesity (BMI 32.5), Borderline HTN",
make_style("diag2", fontSize=9.5, textColor=HexColor("#1B5E20"),
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=C_LIGHT_GREEN, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Metformin", "500 mg", "Oral", "Twice daily with meals", "Long-term (titrate to 1g BD)"],
["Tab. Glimepiride", "1 mg", "Oral", "Once daily before breakfast", "Long-term"],
["Tab. Atorvastatin", "20 mg", "Oral", "Once daily at night", "Long-term"],
["Tab. Aspirin", "75 mg", "Oral", "Once daily after food", "Long-term"],
["Tab. Pregabalin", "75 mg", "Oral", "Once daily at night", "3 months (for neuropathy)"],
["Tab. Folic acid", "5 mg", "Oral", "Once daily", "3 months"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Advice:</b> Diabetic diet (low GI, low refined carbs, high fibre). Daily walking 45 min. "
"Regular SMBG (target FBG 80–130; post-meal <180 mg/dL). HbA1c every 3 months. "
"Annual eye, renal, and foot examination. Refer to dietitian. Foot care education.",
make_style("advice2", fontSize=8.5, textColor=HexColor("#1B5E20"),
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 3: BRONCHIAL ASTHMA
# ══════════════════════════════════════════════════════
story.append(case_header(3, "Bronchial Asthma — Mild Persistent", C_TEAL))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Master Arjun Singh", "Age:", "19 years"],
["Gender:", "Male", "Occupation:", "College Student"],
["Weight:", "60 kg", "Allergies:", "Dust mites, cold air"],
["Chief Complaint:", "Episodic breathlessness, wheezing, and chest tightness for 1 year","",""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_TEAL),
("TEXTCOLOR",(2,0),(2,-1),C_TEAL),
("SPAN",(1,3),(3,3)),
]))
], bg=C_LIGHT_TEAL, border=C_TEAL))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Episodes of breathlessness, wheeze, and cough 3–4 times/week for 1 year.", BULLET_STYLE),
Paragraph("• Symptoms worse at night and early morning; triggered by dust, exercise, cold air.", BULLET_STYLE),
Paragraph("• No symptoms between attacks when away from dusty environment.", BULLET_STYLE),
Paragraph("• Past history of eczema and allergic rhinitis (atopic triad).", BULLET_STYLE),
Paragraph("• Family history: Mother has asthma.", BULLET_STYLE),
Paragraph("• Currently not on any inhaler. Uses steam inhalation at home.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["RR:", "22 breaths/min", "SpO2:", "95% on room air"],
["Peak Flow:", "72% of predicted", "Chest:", "Bilateral expiratory wheeze"],
["Accessory muscles:", "Not used", "Percussion:", "Resonant"],
["PEFR post-bronchodilator:", "Improved to 88% (>12% — reversibility confirmed)", "", ""],
], colWidths=[3.5*cm, 4.5*cm, 3*cm, 5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("SPAN",(1,3),(3,3)),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_LIGHT_TEAL]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Spirometry (FEV1/FVC)", "0.68 (68%)", ">0.75 (normal)", "Obstructive pattern"],
["FEV1 post-BD", "Improved by 15%", "<12% no response", "Reversible obstruction — Asthma"],
["Peak Expiratory Flow Rate", "380 L/min (72% pred)", ">80% predicted", "Reduced"],
["Total IgE", "420 IU/mL", "<100 IU/mL", "Elevated — atopy"],
["Eosinophil count", "680 cells/µL", "<500 cells/µL", "Eosinophilia — allergic"],
["Skin prick test", "Positive (D. pteronyssinus)", "Negative", "House dust mite allergy"],
["CXR", "Hyperinflation", "Normal", "Air trapping"],
["ABG (between attacks)", "Normal", "—", "No hypoxaemia at rest"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Bronchial Asthma — Mild Persistent (GINA Step 2)</b> — "
"Allergic (atopic) type. Triggers: Dust mites, cold air, exercise. "
"Reversible airflow obstruction confirmed on spirometry.",
make_style("diag3", fontSize=9.5, textColor=C_TEAL,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=C_LIGHT_TEAL, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Inhaled Budesonide (ICS)", "200 mcg/puff", "Inhalation", "1 puff BD (controller)", "Long-term (GINA Step 2)"],
["Salbutamol inhaler (SABA)", "100 mcg/puff", "Inhalation", "2 puffs PRN (reliever)", "As needed"],
["Tab. Montelukast", "10 mg", "Oral", "Once daily at night", "3 months"],
["Levocetirizine", "5 mg", "Oral", "Once daily at night", "4 weeks (rhinitis)"],
["NS Saline nasal spray", "2 sprays each nostril", "Intranasal", "Twice daily", "4 weeks"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Advice:</b> Inhaler technique demonstration with spacer. Avoid triggers (dust-proof mattress covers). "
"PEFR diary (Green >80%, Yellow 60–80%, Red <60%). Action plan provided. "
"Consider allergen immunotherapy if uncontrolled. Follow-up in 4 weeks.",
make_style("advice3", fontSize=8.5, textColor=C_TEAL,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 4: GERD
# ══════════════════════════════════════════════════════
story.append(case_header(4, "Gastroesophageal Reflux Disease (GERD)", C_ORANGE))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mr. Vikram Nair", "Age:", "41 years"],
["Gender:", "Male", "Occupation:", "IT Professional"],
["Weight:", "85 kg", "BMI:", "27.8"],
["Chief Complaint:", "Heartburn and acid regurgitation after meals for 4 months", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_ORANGE),
("TEXTCOLOR",(2,0),(2,-1),C_ORANGE),
("SPAN",(1,3),(3,3)),
]))
], bg=C_LIGHT_ORANGE, border=C_ORANGE))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Burning epigastric pain and heartburn after meals, especially spicy/fatty food, coffee.", BULLET_STYLE),
Paragraph("• Acid regurgitation with sour taste, worse on bending forward or lying down.", BULLET_STYLE),
Paragraph("• Water brash, belching. Occasional hoarseness in the morning.", BULLET_STYLE),
Paragraph("• No dysphagia or odynophagia. No haematemesis.", BULLET_STYLE),
Paragraph("• Irregular meals; large dinners; late-night eating. Heavy coffee/tea drinker.", BULLET_STYLE),
Paragraph("• Takes ibuprofen occasionally for back pain.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["General:", "Overweight; no pallor", "BP:", "126/82 mmHg"],
["Abdomen:", "Mild epigastric tenderness; no guarding", "Liver/Spleen:", "Not enlarged"],
["BMI:", "27.8 (overweight)", "No red flag signs", "(dysphagia, weight loss, bleeding)"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_LIGHT_ORANGE]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["H. pylori (stool antigen)", "Positive", "Negative", "H. pylori infection — treat"],
["Upper GI endoscopy", "Erosive oesophagitis Grade B (LA classification)", "Normal mucosa", "Erosions at GEJ"],
["Biopsy (if done)", "No Barrett's", "—", "No dysplasia"],
["Hb", "13.5 g/dL", "13–17 g/dL", "Normal"],
["LFT", "Normal", "—", "Normal"],
["TSH", "2.1 mIU/L", "0.5–5", "Normal"],
["H. pylori CLO test", "Positive", "Negative", "Confirms infection"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>GERD with Erosive Oesophagitis (LA Grade B)</b> + <b>H. pylori infection</b>. "
"Triggering factors: Obesity, NSAIDs, coffee, large late meals. No red-flag features.",
make_style("diag4", fontSize=9.5, textColor=C_ORANGE,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=C_LIGHT_ORANGE, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION — Phase 1: H. pylori Eradication (Triple Therapy 14 days)", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Omeprazole", "20 mg", "Oral", "Twice daily (AC)", "14 days (then 4 wks BD)"],
["Tab. Clarithromycin", "500 mg", "Oral", "Twice daily", "14 days"],
["Tab. Amoxicillin", "1000 mg", "Oral", "Twice daily", "14 days"],
["Tab. Domperidone", "10 mg", "Oral", "Three times daily (AC)", "4 weeks"],
["Syrup Gaviscon", "10 mL", "Oral", "After meals and at bedtime", "4 weeks PRN"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Advice:</b> Elevate head end of bed 30°. Avoid NSAIDs (switch to paracetamol). "
"Avoid coffee, tea, citrus, chocolate, spicy/fatty food. Eat small frequent meals. "
"No lying down for 3 hrs after meals. Weight loss target. Stop smoking. "
"Retest for H. pylori eradication in 4–6 weeks. Follow-up in 4 weeks.",
make_style("advice4", fontSize=8.5, textColor=C_ORANGE,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 5: URINARY TRACT INFECTION
# ══════════════════════════════════════════════════════
story.append(case_header(5, "Urinary Tract Infection (Uncomplicated)", C_PURPLE))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Ms. Priya Sharma", "Age:", "28 years"],
["Gender:", "Female", "Occupation:", "Teacher"],
["Weight:", "57 kg", "LMP:", "Regular; not pregnant"],
["Chief Complaint:", "Burning micturition, frequency, and pelvic discomfort for 3 days", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_PURPLE),
("TEXTCOLOR",(2,0),(2,-1),C_PURPLE),
("SPAN",(1,3),(3,3)),
]))
], bg=C_LIGHT_PURPLE, border=C_PURPLE))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Dysuria (burning on urination), urgency, and increased frequency (voiding every 30–60 min).", BULLET_STYLE),
Paragraph("• Suprapubic discomfort and pressure. Cloudy, foul-smelling urine.", BULLET_STYLE),
Paragraph("• No flank pain, fever, or rigors (no features of upper UTI).", BULLET_STYLE),
Paragraph("• Sexually active. Recent new sexual partner (risk factor). Poor hydration.", BULLET_STYLE),
Paragraph("• History of 2 previous UTI episodes in the past 2 years.", BULLET_STYLE),
Paragraph("• No known drug allergies.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Temp:", "Afebrile (37.1°C)", "BP:", "112/74 mmHg"],
["Abdomen:", "Suprapubic tenderness on palpation", "CVA tenderness:", "Absent (both sides)"],
["Renal punch:", "Negative bilaterally", "Pelvic:", "Not performed (history adequate)"],
], colWidths=[2.8*cm, 5.2*cm, 3.2*cm, 4.8*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, C_LIGHT_PURPLE]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Urine dipstick", "Nitrites +, Leukocyte esterase +", "Negative", "Bacterial infection"],
["Urine microscopy", "WBC >10/HPF, RBC 5/HPF, bacteria present", "WBC <5/HPF", "Pyuria — UTI"],
["Urine C&S (midstream)", "E. coli 10⁵ CFU/mL (>10⁴ diagnostic)", "Sterile", "Pathogenic E. coli"],
["Sensitivity (E. coli)", "Sensitive to Nitrofurantoin, Trimethoprim", "—", "Guide antibiotic choice"],
["Serum creatinine", "0.7 mg/dL", "0.6–1.1", "Normal renal function"],
["Urine pregnancy test", "Negative", "Negative", "Not pregnant"],
["Blood culture", "Not done (afebrile, no systemic signs)", "—", "Not indicated here"],
["Renal ultrasound", "Normal — no hydronephrosis", "Normal", "No upper tract disease"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Uncomplicated Lower Urinary Tract Infection (Acute Cystitis)</b> "
"caused by <i>E. coli</i>. "
"Recurrent UTI (2+ episodes/year) — warrants prophylaxis discussion.",
make_style("diag5", fontSize=9.5, textColor=C_PURPLE,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=C_LIGHT_PURPLE, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Nitrofurantoin (MR)", "100 mg", "Oral", "Twice daily with food", "5 days"],
["Tab. Phenazopyridine", "200 mg", "Oral", "Three times daily (AC)", "2 days (for dysuria relief)"],
["Tab. Ibuprofen", "400 mg", "Oral", "Three times daily after food PRN", "2 days (pain)"],
["ORS / Water intake", "3 L/day", "Oral", "Throughout the day", "5 days minimum"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Prophylaxis (for recurrent UTI):</b> Tab. Nitrofurantoin 50 mg once daily at night for 6 months. "
"<b>Advice:</b> Adequate hydration (>2.5 L/day). Post-coital voiding. "
"Wipe front to back. Avoid tight synthetic underwear. Cranberry extract may help. "
"Return if fever, loin pain, or no improvement in 48 hrs.",
make_style("advice5", fontSize=8.5, textColor=C_PURPLE,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 6: IRON DEFICIENCY ANAEMIA
# ══════════════════════════════════════════════════════
story.append(case_header(6, "Iron Deficiency Anaemia", C_ACCENT))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mrs. Ananya Reddy", "Age:", "32 years"],
["Gender:", "Female (G2P1, 14 weeks pregnant)", "Occupation:", "School Teacher"],
["Weight:", "62 kg", "POG:", "14 weeks"],
["Chief Complaint:", "Fatigue, pallor, breathlessness on exertion, palpitations for 6 weeks", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),C_ACCENT),
("TEXTCOLOR",(2,0),(2,-1),C_ACCENT),
("SPAN",(1,3),(3,3)),
]))
], bg=HexColor("#FFEBEE"), border=C_ACCENT))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Progressive fatigue, easy tiredness, and pallor for 6 weeks.", BULLET_STYLE),
Paragraph("• Breathlessness on climbing stairs; palpitations on mild exertion.", BULLET_STYLE),
Paragraph("• Pica: craving for ice (pagophagia) and clay.", BULLET_STYLE),
Paragraph("• Heavy menstrual periods (menorrhagia) prior to pregnancy.", BULLET_STYLE),
Paragraph("• Poor diet: vegetarian, little iron-rich food.", BULLET_STYLE),
Paragraph("• Current pregnancy 14 weeks; first trimester symptoms resolved.", BULLET_STYLE),
Paragraph("• No haematemesis, melaena, or haematuria.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Pallor:", "Conjunctival pallor ++", "Pulse:", "102 bpm, regular"],
["BP:", "106/68 mmHg", "RR:", "18 breaths/min"],
["Nails:", "Koilonychia (spoon-shaped)", "Tongue:", "Glossitis, angular cheilitis"],
["Spleen:", "Mildly palpable", "CVS:", "Systolic flow murmur (anaemic)"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, HexColor("#FFEBEE")]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Haemoglobin (Hb)", "7.8 g/dL", "11–16 g/dL (F)", "Severe anaemia in pregnancy"],
["MCV", "68 fL", "80–100 fL", "Microcytic"],
["MCH", "20 pg", "27–31 pg", "Hypochromic"],
["Serum ferritin", "5 ng/mL", "15–200 ng/mL", "Very low — confirms IDA"],
["Serum iron", "28 µg/dL", "60–170 µg/dL", "Low"],
["TIBC", "480 µg/dL", "240–450 µg/dL", "Elevated — iron-deficient state"],
["Reticulocyte count", "2.8%", "0.5–2.5%", "Slightly elevated — marrow response"],
["Peripheral blood film", "Microcytes, hypochromia, target cells, pencil cells", "—", "Classic IDA picture"],
["Stool for occult blood", "Negative", "Negative", "No GI source of blood loss"],
["RBS (glucose)", "88 mg/dL", "70–140 mg/dL", "Normal"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Iron Deficiency Anaemia (Severe, Hb 7.8 g/dL) in Pregnancy (14 weeks)</b>. "
"Cause: Increased demand in pregnancy + prior menorrhagia + poor dietary intake.",
make_style("diag6", fontSize=9.5, textColor=C_ACCENT,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=HexColor("#FFEBEE"), borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Ferrous sulphate", "200 mg (65 mg elemental Fe)", "Oral", "Twice daily between meals", "3 months"],
["Tab. Folic acid", "5 mg", "Oral", "Once daily", "Throughout pregnancy"],
["Tab. Vitamin C (Ascorbic acid)", "500 mg", "Oral", "With iron tablet (enhances absorption)", "3 months"],
["Prenatal vitamins (multivitamin)", "1 tablet", "Oral", "Once daily", "Throughout pregnancy"],
["IV Iron Sucrose (if Hb <8)", "200 mg in 100 mL NS over 30 min", "IV", "On alternate days x 5 doses", "If oral not tolerated"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Advice:</b> Iron-rich foods: green leafy vegetables, jaggery, dry fruits, red meat, lentils. "
"Avoid tea/coffee with iron tablets. Hb recheck in 4 weeks (target rise 1g/dL/week). "
"Stool may turn black (normal). Consider IV iron if Hb does not rise in 4 weeks. "
"Obstetrics follow-up for antenatal care.",
make_style("advice6", fontSize=8.5, textColor=C_ACCENT,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 7: HYPOTHYROIDISM
# ══════════════════════════════════════════════════════
thyroid_col = HexColor("#558B2F")
thyroid_light = HexColor("#F1F8E9")
story.append(case_header(7, "Hypothyroidism (Primary)", thyroid_col))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mrs. Kavitha Menon", "Age:", "42 years"],
["Gender:", "Female", "Occupation:", "Bank Employee"],
["Weight:", "74 kg (gained 8 kg in 6 months)", "BMI:", "29.3"],
["Chief Complaint:", "Weight gain, lethargy, cold intolerance, constipation, hair loss for 6 months", "",""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),thyroid_col),
("TEXTCOLOR",(2,0),(2,-1),thyroid_col),
("SPAN",(1,3),(3,3)),
]))
], bg=thyroid_light, border=thyroid_col))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Unexplained weight gain of 8 kg over 6 months despite no change in diet.", BULLET_STYLE),
Paragraph("• Excessive fatigue, sleepiness, difficulty concentrating (brain fog).", BULLET_STYLE),
Paragraph("• Cold intolerance, constipation (stools every 3–4 days).", BULLET_STYLE),
Paragraph("• Diffuse hair loss, dry skin, brittle nails, puffy face.", BULLET_STYLE),
Paragraph("• Menstrual irregularity (oligomenorrhoea — periods every 45–60 days).", BULLET_STYLE),
Paragraph("• Mild depression and low mood. Bradycardia noticed occasionally.", BULLET_STYLE),
Paragraph("• Family history: Sister has Hashimoto's thyroiditis.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Pulse:", "58 bpm (bradycardia)", "BP:", "108/70 mmHg"],
["Skin:", "Dry, rough, cool to touch; non-pitting oedema (myxoedema)", "Hair:", "Diffuse loss; dry; coarse"],
["Thyroid:", "Diffusely enlarged (Grade II goitre)", "Reflexes:", "Slow relaxation (delayed)"],
["Face:", "Periorbital puffiness, puffed face", "Voice:", "Hoarse"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, thyroid_light]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["TSH", "48.2 mIU/L", "0.5–5 mIU/L", "MARKEDLY ELEVATED — Primary Hypothyroidism"],
["Free T4 (FT4)", "0.4 ng/dL", "0.8–1.8 ng/dL", "LOW — confirms frank hypothyroidism"],
["Anti-TPO antibody", "1240 IU/mL", "<35 IU/mL", "Very high — Hashimoto's thyroiditis"],
["Anti-thyroglobulin Ab", "890 IU/mL", "<115 IU/mL", "Elevated"],
["Hb", "10.2 g/dL", "12–16 g/dL", "Anaemia (hypothyroid-associated)"],
["Total cholesterol", "268 mg/dL", "<200 mg/dL", "Elevated (hypothyroid dyslipidaemia)"],
["LDL", "168 mg/dL", "<100 mg/dL", "High"],
["CK (Creatine kinase)", "420 U/L", "26–200 U/L", "Elevated — hypothyroid myopathy"],
["Thyroid ultrasound", "Heterogeneous echogenicity, no nodules", "—", "Hashimoto's pattern"],
["ECG", "Sinus bradycardia; low voltage", "Normal", "Cardiac manifestation"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Primary Hypothyroidism (Overt)</b> secondary to <b>Hashimoto's Thyroiditis (Autoimmune)</b>. "
"TSH 48.2 mIU/L, FT4 low, Anti-TPO 1240 IU/mL. Complications: Anaemia, Dyslipidaemia, Myopathy.",
make_style("diag7", fontSize=9.5, textColor=thyroid_col,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=thyroid_light, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Levothyroxine (T4)", "50 mcg (start low)", "Oral", "Once daily on empty stomach (30 min before breakfast)", "Long-term; titrate by 25 mcg every 6 weeks"],
["Tab. Atorvastatin", "20 mg", "Oral", "Once daily at night", "Review after euthyroid state; may resolve"],
["Tab. Ferrous sulphate", "200 mg", "Oral", "Once daily", "3 months (for associated anaemia)"],
["Multivitamin supplement", "1 tablet", "Oral", "Once daily", "3 months"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Monitoring:</b> TSH recheck in 6 weeks after starting Levothyroxine. "
"Target TSH: 0.5–2.5 mIU/L. Titrate dose every 6 weeks until euthyroid. "
"Avoid iron/calcium supplements within 4 hours of Levothyroxine. "
"<b>Advice:</b> Adequate iodine (iodised salt). Regular exercise. Weight management.",
make_style("advice7", fontSize=8.5, textColor=thyroid_col,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 8: ENTERIC FEVER (TYPHOID)
# ══════════════════════════════════════════════════════
typhoid_col = HexColor("#795548")
typhoid_light = HexColor("#EFEBE9")
story.append(case_header(8, "Enteric Fever (Typhoid — Salmonella typhi)", typhoid_col))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mr. Saurabh Gupta", "Age:", "24 years"],
["Gender:", "Male", "Occupation:", "College Student"],
["Weight:", "65 kg", "Travel:", "Rural area 2 weeks ago"],
["Chief Complaint:", "Step-ladder fever for 10 days, headache, abdominal pain, constipation", "",""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),typhoid_col),
("TEXTCOLOR",(2,0),(2,-1),typhoid_col),
("SPAN",(1,3),(3,3)),
]))
], bg=typhoid_light, border=typhoid_col))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Step-ladder pattern fever (starting 38°C → 40°C over 10 days) with evening rise and morning remission.", BULLET_STYLE),
Paragraph("• Severe frontal headache, malaise, anorexia.", BULLET_STYLE),
Paragraph("• Abdominal pain (central, mild), constipation in week 1 followed by diarrhoea in week 2.", BULLET_STYLE),
Paragraph("• Rose spots noticed on abdomen (2–3 mm salmon-coloured spots) — faded on pressure.", BULLET_STYLE),
Paragraph("• Ate street food and drank unfiltered water during rural visit 2 weeks ago.", BULLET_STYLE),
Paragraph("• No jaundice, no haematemesis. Not vaccinated against typhoid.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Temp:", "39.6°C (high grade)", "Pulse:", "84 bpm (relative bradycardia)"],
["BP:", "108/68 mmHg", "Tongue:", "Coated, dry ('typhoid tongue')"],
["Abdomen:", "Diffuse mild tenderness; Splenomegaly (+); Hepatomegaly (+)", "Rash:", "Rose spots on trunk"],
["CNS:", "Mild confusion (toxic)", "Chest:", "Clear"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, typhoid_light]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["Widal test (O titre)", "1:160 (positive)", ">1:80 significant", "Suggestive of enteric fever"],
["Widal test (H titre)", "1:80", ">1:80 significant", "Positive"],
["Blood culture (gold standard)", "Salmonella typhi (Week 1–2)", "Sterile", "CONFIRMATORY"],
["CBC — Hb", "11.8 g/dL", "13–17 g/dL", "Mild normocytic anaemia"],
["WBC", "3200 cells/µL (leucopenia)", "4000–11000", "Leucopenia — typical of typhoid"],
["Platelet count", "98,000/µL (thrombocytopenia)", "150,000–400,000", "Thrombocytopenia"],
["LFT (ALT)", "88 U/L", "<40 U/L", "Elevated — typhoid hepatitis"],
["Bilirubin (total)", "1.8 mg/dL", "<1.2 mg/dL", "Mildly elevated"],
["CRP", "62 mg/L", "<5 mg/L", "Elevated — acute inflammation"],
["Stool culture", "Pending", "Sterile", "Positive in Week 3+"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Enteric Fever (Typhoid)</b> — Confirmed on blood culture: <i>Salmonella typhi</i>. "
"Week 2 presentation. Complications: Hepatitis, Thrombocytopenia. "
"No evidence of perforation or haemorrhage.",
make_style("diag8", fontSize=9.5, textColor=typhoid_col,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=typhoid_light, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Cefixime", "200 mg", "Oral", "Twice daily", "14 days (outpatient)"],
["Tab. Azithromycin", "500 mg", "Oral", "Once daily", "7 days (add for quinolone-resistant areas)"],
["Tab. Paracetamol", "500 mg", "Oral", "Every 6 hrs PRN for fever >38.5°C", "As needed"],
["ORS / IV fluids", "Oral: 2.5–3 L/day", "Oral", "Throughout the day", "Until fever subsides"],
["Tab. Domperidone", "10 mg", "Oral", "Twice daily PRN for nausea", "5 days"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Advice:</b> Strict bed rest. Oral rehydration. Soft low-residue diet (avoid spicy/hard food — risk of perforation). "
"Isolation with enteric precautions. Hand hygiene. Typhoid vaccine for household contacts. "
"Monitor for complications: intestinal perforation (acute abdomen), haemorrhage. "
"ADMIT if unable to maintain oral intake or complications develop.",
make_style("advice8", fontSize=8.5, textColor=typhoid_col,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 9: ACUTE URTI
# ══════════════════════════════════════════════════════
urti_col = HexColor("#0277BD")
urti_light = HexColor("#E1F5FE")
story.append(case_header(9, "Acute Upper Respiratory Tract Infection (Viral URTI)", urti_col))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Master Rohan Joshi", "Age:", "7 years"],
["Gender:", "Male", "Weight:", "22 kg"],
["School:", "Primary school (class 2)", "Season:", "Winter"],
["Chief Complaint:", "Runny nose, sore throat, mild fever, and cough for 3 days", "", ""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),urti_col),
("TEXTCOLOR",(2,0),(2,-1),urti_col),
("SPAN",(1,3),(3,3)),
]))
], bg=urti_light, border=urti_col))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Low-grade fever (38–38.5°C), watery nasal discharge, blocked nose for 3 days.", BULLET_STYLE),
Paragraph("• Sore throat, dry irritating cough, sneezing. Mild headache.", BULLET_STYLE),
Paragraph("• Child remains active; eating and drinking adequately.", BULLET_STYLE),
Paragraph("• Several classmates have similar symptoms (school cluster).", BULLET_STYLE),
Paragraph("• No rash, no difficulty swallowing, no ear pain, no respiratory distress.", BULLET_STYLE),
Paragraph("• Vaccinations up to date (including influenza vaccine this year).", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Temp:", "38.3°C", "HR:", "98 bpm"],
["RR:", "20 breaths/min (normal)", "SpO2:", "99% on room air"],
["Throat:", "Hyperaemic pharynx; no exudate, no tonsillar enlargement", "Ears:", "Normal TM bilaterally"],
["Nose:", "Watery clear discharge; nasal mucosa congested", "Chest:", "Clear; no wheeze"],
["Lymph nodes:", "Small (<1 cm) submandibular nodes; non-tender", "CNS:", "Alert, no meningism"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, urti_light]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["CBC", "WBC 8400 (lymphocytes 62%)", "4000–11000", "Lymphocytosis — viral aetiology"],
["CRP", "8 mg/L", "<10 mg/L", "Mildly elevated — viral, not bacterial"],
["Throat swab (rapid strep)", "Negative for GAS", "Negative", "No Group A Strep — no antibiotics"],
["Chest X-ray", "Not indicated (no focal signs)", "—", "Not done — no indication"],
["Influenza rapid antigen", "Negative", "Negative", "Not influenza"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Acute Viral Upper Respiratory Tract Infection (Common Cold)</b> — "
"Likely Rhinovirus/Coronavirus. No bacterial features. Rapid strep negative. "
"Self-limiting — expected resolution in 7–10 days.",
make_style("diag9", fontSize=9.5, textColor=urti_col,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=urti_light, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION (Symptomatic only — NO antibiotics)", RX_TITLE_STYLE))
story.append(rx_table([
["Syrup Paracetamol", "15 mg/kg/dose (= 330 mg)", "Oral", "Every 6 hrs PRN for fever >38.5°C", "5 days"],
["Syrup Chlorpheniramine", "0.35 mg/kg/day ÷ 3 doses", "Oral", "Three times daily", "5 days (for rhinorrhoea)"],
["NS Saline nasal drops", "2 drops each nostril", "Intranasal", "Before feeds and at night", "5–7 days"],
["Honey + warm water", "5 mL honey in warm water", "Oral", "Twice daily for cough", "5 days (if >1 year)"],
["Steam inhalation", "With eucalyptus oil", "Inhalation", "Twice daily under supervision", "5 days"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>IMPORTANT — No antibiotic indicated (viral URTI).</b> "
"<b>Advice:</b> Adequate fluids. Rest. No school for 2–3 days (infectious period). "
"Wash hands frequently. Cover mouth when sneezing/coughing. "
"<b>Return immediately if:</b> Fever >39°C, difficulty breathing, ear pain, rash, "
"lethargy, no improvement in 7 days, or worsening symptoms.",
make_style("advice9", fontSize=8.5, textColor=urti_col,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# CASE 10: OSTEOARTHRITIS OF KNEE
# ══════════════════════════════════════════════════════
oa_col = HexColor("#37474F")
oa_light = HexColor("#ECEFF1")
story.append(case_header(10, "Osteoarthritis of Knee (Grade III)", oa_col))
story.append(Spacer(1, 4*mm))
story.append(section_box("PATIENT DETAILS", [
Table([
["Name:", "Mrs. Leela Patel", "Age:", "65 years"],
["Gender:", "Female", "Occupation:", "Retired"],
["Weight:", "80 kg", "BMI:", "31.5 (Obese)"],
["Chief Complaint:", "Bilateral knee pain, stiffness, and difficulty climbing stairs for 3 years","",""],
], colWidths=[2.5*cm, 5.5*cm, 2.5*cm, 5.5*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("TEXTCOLOR",(0,0),(0,-1),oa_col),
("TEXTCOLOR",(2,0),(2,-1),oa_col),
("SPAN",(1,3),(3,3)),
]))
], bg=oa_light, border=oa_col))
story.append(Spacer(1, 3*mm))
story.append(section_box("HISTORY", [
Paragraph("• Bilateral knee pain, worse on prolonged walking, climbing stairs, and rising from a chair.", BULLET_STYLE),
Paragraph("• Morning stiffness lasting <30 minutes (characteristic of OA — unlike RA >1 hr).", BULLET_STYLE),
Paragraph("• Crepitus on knee movement. Occasional locking. No swelling at rest.", BULLET_STYLE),
Paragraph("• Pain grade: 7/10 on VAS. Limits daily activities significantly.", BULLET_STYLE),
Paragraph("• No fever, no systemic symptoms, no inflammatory joint disease.", BULLET_STYLE),
Paragraph("• Past history: Postmenopausal (10 years). Diabetes (controlled on metformin).", BULLET_STYLE),
Paragraph("• Family history: Mother had knee replacement at 70.", BULLET_STYLE),
]))
story.append(Spacer(1, 3*mm))
story.append(section_box("EXAMINATION", [
Table([
["Gait:", "Antalgic gait (limping, guarding)", "BMI:", "31.5"],
["Both knees:", "Bilateral joint line tenderness", "Crepitus:", "Present on ROM both knees"],
["ROM:", "Flexion 90° (limited; normal 135°)", "Extension:", "Full (0°)"],
["Varus deformity:", "Bilateral genu varum (bow-legged)", "Effusion:", "Small effusion left knee"],
["Muscle wasting:", "Bilateral quadriceps wasting", "Ligaments:", "Stable — no laxity"],
], colWidths=[2.8*cm, 5.2*cm, 2.8*cm, 5.2*cm],
style=TableStyle([
("FONTNAME",(0,0),(0,-1),"Helvetica-Bold"),
("FONTNAME",(2,0),(2,-1),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),9),
("TOPPADDING",(0,0),(-1,-1),3),
("BOTTOMPADDING",(0,0),(-1,-1),3),
("ROWBACKGROUNDS",(0,0),(-1,-1),[white, oa_light]),
]))
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("INVESTIGATIONS", SECTION_STYLE))
story.append(inv_table([
["X-ray both knees (WB AP/lateral)", "Narrowed medial joint space, osteophytes, subchondral sclerosis", "Normal joint space", "Kellgren-Lawrence Grade III"],
["ESR", "22 mm/hr", "<20 mm/hr (F)", "Mildly elevated — non-specific"],
["CRP", "6 mg/L", "<5 mg/L", "Mildly elevated — not markedly so"],
["RA factor (RF)", "Negative", "Negative", "Not rheumatoid arthritis"],
["Anti-CCP antibody", "Negative", "Negative", "Not RA"],
["Uric acid", "5.2 mg/dL", "2.4–6.0 mg/dL", "Normal — not gout"],
["CBC, RFT, LFT", "Normal", "Normal", "Baseline before NSAIDs"],
["Synovial fluid (aspirate)", "Viscous, clear, WBC <2000", "WBC <200", "Non-inflammatory — OA"],
["Bone density (DEXA)", "T-score -1.8 (osteopenia)", "T-score > -1.0", "Osteopenia — treat"],
["MRI knee (left)", "Meniscal fraying, cartilage loss Grade 3", "—", "Advanced chondral damage"],
]))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("DIAGNOSIS", SECTION_STYLE))
story.append(Paragraph(
"<b>Primary Osteoarthritis of Both Knees (Kellgren-Lawrence Grade III)</b>. "
"Comorbidities: Obesity (BMI 31.5), Diabetes Mellitus, Osteopenia. "
"Non-inflammatory arthritis confirmed — RF/CCP negative.",
make_style("diag10", fontSize=9.5, textColor=oa_col,
fontName="Helvetica", leading=14, spaceBefore=2, spaceAfter=4,
backColor=oa_light, borderPad=4)))
story.append(Spacer(1, 4*mm))
story.append(Paragraph("℞ PRESCRIPTION", RX_TITLE_STYLE))
story.append(rx_table([
["Tab. Etoricoxib (COX-2 inhibitor)", "60 mg", "Oral", "Once daily after food", "4–6 weeks; use minimum dose"],
["Tab. Pantoprazole (gastroprotection)", "40 mg", "Oral", "Once daily before breakfast", "Co-prescribe with NSAID"],
["Tab. Glucosamine sulphate", "1500 mg", "Oral", "Once daily", "3–6 months (cartilage support)"],
["Tab. Chondroitin sulphate", "400 mg", "Oral", "Three times daily", "3–6 months"],
["Diclofenac gel 1%", "Apply 2–4 cm of gel", "Topical", "3–4 times daily over knee", "4–6 weeks"],
["Tab. Calcium + Vit D3", "1000 mg Ca + 400 IU D3", "Oral", "Twice daily", "Long-term (for osteopenia)"],
["Intra-articular Triamcinolone (if needed)", "40 mg in 1 mL", "IA injection", "Once (left knee effusion)", "Can repeat after 3 months"],
]))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Non-pharmacological (ESSENTIAL):</b> Weight reduction target 5–10% body weight. "
"Quadriceps strengthening exercises and physiotherapy. Walking aid (knee brace). "
"Avoid squatting, sitting cross-legged, kneeling, or climbing stairs excessively. "
"Hydrotherapy/pool walking. Heat therapy before exercise. "
"<b>Surgical referral</b> for Total Knee Replacement if conservative treatment fails after 6 months. "
"Orthopaedic referral for surgical opinion.",
make_style("advice10", fontSize=8.5, textColor=oa_col,
fontName="Helvetica", leading=12, leftIndent=4,
spaceBefore=3, spaceAfter=3)))
story.append(PageBreak())
# ══════════════════════════════════════════════════════
# BACK PAGE / SUMMARY TABLE
# ══════════════════════════════════════════════════════
story.append(Paragraph("SUMMARY: OPD CASES AT A GLANCE", make_style("SummHead",
fontSize=14, textColor=C_DARK_BLUE, fontName="Helvetica-Bold",
spaceBefore=0, spaceAfter=8, alignment=TA_CENTER)))
summary_data = [
["#", "Condition", "Key Finding", "First-Line Treatment"],
["1", "Hypertension Stage 2", "BP 172/106; LVH; Retinopathy", "Amlodipine + Losartan + HCTZ"],
["2", "Type 2 Diabetes", "HbA1c 8.6%; FBG 186", "Metformin + Glimepiride + Lifestyle"],
["3", "Bronchial Asthma", "PEFR 72%; reversible obstruction", "Budesonide ICS + Salbutamol PRN"],
["4", "GERD + H. pylori", "Erosive oesophagitis LA Grade B", "PPI + Clarithromycin + Amoxicillin (14d)"],
["5", "UTI (Acute Cystitis)", "E. coli 10⁵ CFU; pyuria", "Nitrofurantoin 100 mg BD x 5 days"],
["6", "Iron Deficiency Anaemia", "Hb 7.8; ferritin 5; pregnancy", "Ferrous sulphate + Folic acid + Vit C"],
["7", "Hypothyroidism", "TSH 48; FT4 low; Anti-TPO high", "Levothyroxine 50 mcg (titrate)"],
["8", "Enteric Fever (Typhoid)", "Blood culture: S. typhi; leucopenia", "Cefixime + Azithromycin x 14 days"],
["9", "Viral URTI (Child)", "Lymphocytosis; strep negative", "Symptomatic ONLY — no antibiotics"],
["10","Osteoarthritis Knee", "KL Grade III; genu varum; effusion", "Etoricoxib + PT + Weight loss"],
]
sum_tbl = Table(summary_data, colWidths=[0.8*cm, 4.4*cm, 5.5*cm, 5.3*cm], repeatRows=1)
sum_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("BOTTOMPADDING",(0,0),(-1,0), 5),
("TOPPADDING", (0,0),(-1,0), 5),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("TOPPADDING", (0,1), (-1,-1), 4),
("BOTTOMPADDING",(0,1),(-1,-1), 4),
("GRID", (0,0), (-1,-1), 0.4, C_BORDER),
("ROWBACKGROUNDS",(0,1),(-1,-1),[white, C_ROW_ALT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (0,-1), "CENTER"),
]))
story.append(sum_tbl)
story.append(Spacer(1, 1*cm))
story.append(HRFlowable(width="100%", thickness=0.5, color=C_BORDER))
story.append(Spacer(1, 3*mm))
story.append(Paragraph(
"Disclaimer: This case book is produced for educational purposes only. "
"Drug doses and treatment recommendations are based on standard guidelines "
"(Harrison's Internal Medicine 22E, Katzung Pharmacology 16E, WHO Essential Medicines, "
"GINA 2024, JNC 8, ADA Standards 2025). Always verify against current national formulary "
"and individualise therapy to the patient. Not a substitute for clinical judgment.",
make_style("disc", fontSize=7.5, textColor=HexColor("#607D8B"),
fontName="Helvetica-Oblique", leading=11, alignment=TA_JUSTIFY)))
# ──────────────────────────────────────────────────────
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
print(f"[OK] PDF saved to: {OUTPUT_PATH}")
return OUTPUT_PATH
if __name__ == "__main__":
build_pdf()
cd /home/daytona/workspace/opd-cases && python generate_opd_pdf.py 2>&1
ls -lh /home/daytona/workspace/opd-cases/OPD_Case_Book_General_Medicine.pdf
python3 -c "from pypdf import PdfReader; r = PdfReader('/home/daytona/workspace/opd-cases/OPD_Case_Book_General_Medicine.pdf'); print(f'Pages: {len(r.pages)}')"
OPD Case Book General Medicine
PDF Document · PDF
| # | Condition | Key Feature |
|---|---|---|
| 1 | Hypertension Stage 2 | BP 172/106, LVH, Retinopathy Gr II |
| 2 | Type 2 Diabetes Mellitus | HbA1c 8.6%, Neuropathy, Microalbuminuria |
| 3 | Bronchial Asthma (Mild Persistent) | PEFR 72%, reversible obstruction |
| 4 | GERD + H. pylori | Erosive oesophagitis LA Grade B |
| 5 | UTI (Acute Cystitis) | E. coli 10⁵ CFU, pyuria |
| 6 | Iron Deficiency Anaemia | Hb 7.8 in pregnancy, ferritin 5 |
| 7 | Hypothyroidism (Hashimoto's) | TSH 48, Anti-TPO 1240 IU/mL |
| 8 | Enteric Fever (Typhoid) | Blood culture S. typhi, leucopenia |
| 9 | Viral URTI (Paediatric) | Strep-negative, symptomatic Rx only |
| 10 | Osteoarthritis of Knee | KL Grade III, genu varum, effusion |
Write about opd pt cases as many as you know with images and prescription
Write about opd pt cases as many as you know with images and prescription for practice and education
mkdir -p /home/daytona/workspace/opd-multispecialty && echo "dir ready"
Write about pediatric opd pt cases as many as you know with images and prescription for practice and education
mkdir -p /home/daytona/workspace/peds-opd/img_cache && echo "ready"
Write about pediatric opd pt cases 25+ as many as you know with images and prescription for practice and education