Patient 32 yr old male C/o Pain in abdomen since 1 day Periumbilical No red flag Constant No association
| Condition | Why it's periumbilical | Key clue to look for |
|---|---|---|
| Early Appendicitis | Visceral pain from the appendix (T10 dermatome) starts periumbilical, migrates to RIF over 12-24h | Anorexia, pain migration, fever, RIF tenderness |
| Early SBO (Small Bowel Obstruction) | Midgut distension causes colicky periumbilical pain | Prior surgery, hernia, absolute constipation/no flatus, distension, borborygmi |
| Mesenteric Ischemia | Midgut ischemia causes periumbilical pain "out of proportion to exam" | AF, vascular disease, severe pain but soft abdomen |
| Condition | Notes |
|---|---|
| Gastroenteritis | Often periumbilical/diffuse; usually with nausea/vomiting/diarrhea |
| Mesenteric Adenitis | Lymph node inflammation (often post-viral); mimics appendicitis, mainly in young adults |
| Early Crohn's disease | Terminal ileum involvement can present in this age group |
| Peptic ulcer / Duodenitis | Upper GI irritation can refer to periumbilical region |
| Renal colic | Colicky pain, may radiate periumbilically; check for haematuria |
| DKA | Can cause diffuse/periumbilical pain; check glucose in new presentations |
| Hernia (incarcerated) | Always check inguinal/umbilical region |
| Feature | Points |
|---|---|
| Migration of pain to RIF | 1 |
| Anorexia | 1 |
| Nausea/vomiting | 1 |
| RIF tenderness | 2 |
| Rebound tenderness | 1 |
| Elevated temperature (>37.3°C) | 1 |
| Leukocytosis (WBC >10) | 2 |
| Total | /9 |
Make like this
mkdir -p /tmp/workspace/case-cards && ls /tmp/workspace/case-cards/
~/case-cards/generate_card.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable, Table, TableStyle
)
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.platypus import KeepTogether
OUTPUT = "/tmp/workspace/case-cards/case_periumbilical_pain.pdf"
# ── Colour palette (matching reference image) ──────────────────────────────
TEAL = colors.HexColor("#2E7D87") # section headers
DARK_NAVY = colors.HexColor("#1B2A4A") # body text / Rx numbers
ORANGE = colors.HexColor("#D97B2B") # dosage highlights
RED = colors.HexColor("#C0392B") # complications
LIGHT_BG = colors.HexColor("#EAF4F6") # title box background
BORDER = colors.HexColor("#2E7D87") # title box border
GREY_TEXT = colors.HexColor("#888888") # disclaimer
# ── Styles ──────────────────────────────────────────────────────────────────
def S(name, **kw):
base = dict(fontName="Helvetica", fontSize=10, leading=15,
textColor=DARK_NAVY, spaceAfter=2)
base.update(kw)
return ParagraphStyle(name, **base)
s_title = S("title", fontName="Helvetica-Bold", fontSize=14,
textColor=DARK_NAVY, alignment=TA_CENTER, leading=20)
s_section = S("section", fontName="Helvetica-Bold", fontSize=11,
textColor=TEAL, spaceAfter=2, spaceBefore=6)
s_body = S("body", fontSize=10, leading=15)
s_bullet = S("bullet", fontSize=10, leading=15, leftIndent=14,
bulletIndent=4)
s_italic = S("italic", fontName="Helvetica-Oblique", fontSize=10.5,
textColor=DARK_NAVY, leftIndent=10, leading=16)
s_rx_num = S("rxnum", fontSize=10, leading=16, leftIndent=6)
s_rta = S("rta", fontName="Helvetica-Bold", fontSize=11,
textColor=ORANGE, alignment=TA_CENTER, spaceAfter=4)
s_comp = S("comp", fontSize=10, leading=15, textColor=RED,
leftIndent=14, bulletIndent=4)
s_disclaim = S("disclaim",fontSize=7.5, textColor=GREY_TEXT,
alignment=TA_CENTER, leading=11)
def bullet(text, style=s_bullet):
return Paragraph(f"• {text}", style)
def rx_line(num, tab_text, dose_text, rest_text):
"""Returns a Paragraph with mixed bold/coloured text for an Rx line."""
html = (f'<font color="{ORANGE.hexval()}"><b>{num})</b></font>'
f'<b>Tab.</b> {tab_text} '
f'<font color="{ORANGE.hexval()}"><b>{dose_text}</b></font>'
f'{rest_text}')
return Paragraph(html, s_rx_num)
def rx_plain(num, text):
html = f'<font color="{ORANGE.hexval()}"><b>{num})</b></font>{text}'
return Paragraph(html, s_rx_num)
# ── Build story ─────────────────────────────────────────────────────────────
story = []
# ── Title box ────────────────────────────────────────────────────────────────
title_para = Paragraph("CASE: PERIUMBILICAL ABDOMINAL PAIN", s_title)
title_tbl = Table([[title_para]], colWidths=[155*mm])
title_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
("BOX", (0,0), (-1,-1), 1.5, BORDER),
("ROUNDEDCORNERS", [6]),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING",(0,0), (-1,-1), 10),
]))
story.append(title_tbl)
story.append(Spacer(1, 8*mm))
# ── C/o ──────────────────────────────────────────────────────────────────────
story.append(Paragraph("C/o:-", s_section))
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.lightgrey))
story.append(Spacer(1, 2*mm))
for item in [
"Pain in abdomen since 1 day",
"Periumbilical in location",
"Constant in nature",
"No red flag symptoms",
"No associated symptoms",
]:
story.append(bullet(item))
story.append(Spacer(1, 4*mm))
# ── Most probable diagnosis ──────────────────────────────────────────────────
story.append(Paragraph("Most probable diagnosis:", s_section))
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.lightgrey))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"Early Appendicitis / Mesenteric Adenitis / Acute Gastroenteritis",
s_italic))
story.append(Spacer(1, 5*mm))
# ── Advice + Rx (two-column table) ──────────────────────────────────────────
advice_items = [
bullet("CBC"),
bullet("CRP / ESR"),
bullet("Urine R/M"),
bullet("USG Abdomen"),
bullet("Alvarado Score"),
bullet("Surgical Consult if score ≥ 5"),
]
rx_items = [
Paragraph("<b>Rx</b>", S("rxhead", fontName="Helvetica-Bold",
fontSize=13, textColor=DARK_NAVY)),
rx_line("1", "PCM ", "650mg", " x TDS (after food)"),
rx_line("2", "Pan ", "40mg", " x OD (before food)"),
rx_line("3", "Domperidone ", "10mg", " x TDS (before food)"),
rx_plain("4",
" <b>IV Fluids</b> (if dehydrated / vomiting) — "
f'<font color="{ORANGE.hexval()}"><b>NS / RL @ 100 ml/hr</b></font>'),
rx_plain("5",
" Analgesic if needed: <b>Inj.</b> "
f'<font color="{ORANGE.hexval()}"><b>Tramadol 50mg</b></font>'
" slow IV"),
rx_plain("6", " <b>NPO</b> if surgical cause suspected"),
rx_plain("7", " Rest + Oral hydration (ORS) if tolerating orally"),
Spacer(1, 4*mm),
Paragraph(
f'<font color="{ORANGE.hexval()}"><b>R/A 24-48 hours</b></font>'
" (or earlier if pain migrates to RIF / worsens)",
S("rta2", fontName="Helvetica-Bold", fontSize=10,
textColor=ORANGE, alignment=TA_CENTER)),
]
col_advice = [advice_items] # single column
# Build left column as a mini table
adv_inner = [[a] for a in advice_items]
adv_tbl = Table(adv_inner, colWidths=[60*mm])
adv_tbl.setStyle(TableStyle([
("TOPPADDING", (0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
("LEFTPADDING", (0,0),(-1,-1), 0),
("RIGHTPADDING", (0,0),(-1,-1), 0),
]))
rx_inner = [[r] for r in rx_items]
rx_tbl = Table(rx_inner, colWidths=[90*mm])
rx_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), 0),
]))
left_header = Paragraph("Advice:", S("advhdr", fontName="Helvetica-Bold",
fontSize=11, textColor=TEAL))
right_header = Paragraph("", s_body)
two_col = Table(
[[left_header, right_header],
[adv_tbl, rx_tbl]],
colWidths=[62*mm, 93*mm]
)
two_col.setStyle(TableStyle([
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 0),
("RIGHTPADDING", (0,0),(-1,-1), 0),
("LINEABOVE", (0,0),(-1,0), 0.5, colors.lightgrey),
]))
story.append(two_col)
story.append(Spacer(1, 6*mm))
# ── Complications ─────────────────────────────────────────────────────────────
story.append(Paragraph("Complications:", S("comphdr",
fontName="Helvetica-Bold", fontSize=11, textColor=RED,
decoration="underline")))
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.lightgrey))
story.append(Spacer(1, 2*mm))
for item in [
"Perforation peritonitis (if appendicitis missed)",
"Bowel obstruction / strangulation",
"Sepsis / septic shock",
"Drug-induced gastritis (NSAIDs)",
"Missed surgical emergency",
]:
story.append(bullet(item, style=s_comp))
story.append(Spacer(1, 8*mm))
# ── Disclaimer ───────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.4, color=colors.lightgrey))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"Disclaimer: This handbook is intended as a clinical reference guide "
"and does not replace clinical judgment. For educational purposes only.",
s_disclaim))
# ── Render ───────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=20*mm, rightMargin=20*mm,
topMargin=20*mm, bottomMargin=20*mm,
)
doc.build(story)
print("PDF saved to", OUTPUT)
cd /tmp/workspace/case-cards && python generate_card.py 2>&1
case periumbilical pain
PDF Document · PDF
Why not drotaverine
| Risk with Drotaverine | Explanation |
|---|---|
| Masks pain migration | Appendicitis classically migrates to RIF over 12-24h. Antispasmodics can blunt this critical diagnostic sign |
| Reduces guarding/tenderness | Smooth muscle relaxation softens the abdominal wall response, making clinical examination unreliable |
| Delays surgical referral | Patient feels better temporarily → comes back late with perforated appendicitis |
| False reassurance | Pain relief ≠ resolution of pathology |
In any undiagnosed abdominal pain, do not give antispasmodics or analgesics that mask peritoneal signs until a surgical emergency has been excluded.