Make a blood transfusion discharge summary
Make a summary with real patient data
mkdir -p /home/daytona/workspace/discharge-summary && ls /home/daytona/workspace…
~/discharge-summary/generate_discharge.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, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import Flowable
OUTPUT = "/home/daytona/workspace/discharge-summary/Blood_Transfusion_Discharge_Summary.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1A3557")
MID_BLUE = colors.HexColor("#2A6099")
LIGHT_BLUE = colors.HexColor("#D9E8F5")
ACCENT_RED = colors.HexColor("#C0392B")
GREY_BG = colors.HexColor("#F4F6F8")
GREY_BORDER = colors.HexColor("#BDC3C7")
WHITE = colors.white
BLACK = colors.black
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
hospital_name = S("HospName", fontName="Helvetica-Bold", fontSize=16, textColor=WHITE, alignment=TA_CENTER)
hospital_addr = S("HospAddr", fontName="Helvetica", fontSize=9, textColor=LIGHT_BLUE, alignment=TA_CENTER)
doc_title = S("DocTitle", fontName="Helvetica-Bold", fontSize=13, textColor=WHITE, alignment=TA_CENTER, spaceAfter=2)
section_hdr = S("SecHdr", fontName="Helvetica-Bold", fontSize=10, textColor=WHITE, leading=14)
field_label = S("FLabel", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_BLUE)
field_value = S("FVal", fontName="Helvetica", fontSize=9, textColor=BLACK)
body_text = S("BodyTxt", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=14, spaceAfter=3)
body_bold = S("BodyBold", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_BLUE, leading=14)
small_italic = S("SmItal", fontName="Helvetica-Oblique",fontSize=8, textColor=colors.HexColor("#555555"), alignment=TA_CENTER)
footer_style = S("Footer", fontName="Helvetica", fontSize=7.5,textColor=colors.HexColor("#777777"), alignment=TA_CENTER)
alert_style = S("Alert", fontName="Helvetica-Bold", fontSize=9, textColor=ACCENT_RED)
normal_val = S("NVal", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=13)
# ── Helper: section header row ────────────────────────────────────────────────
def section_header(title):
tbl = Table([[Paragraph(title, section_hdr)]], colWidths=[170*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [3, 3, 3, 3]),
]))
return tbl
# ── Helper: 2-column info row ─────────────────────────────────────────────────
def info_row(pairs, col_widths=None):
"""pairs = [(label, value), ...] - up to 4 items per row"""
if col_widths is None:
col_widths = [32*mm, 53*mm] * (len(pairs))
cells = []
for label, value in pairs:
cells.append(Paragraph(label, field_label))
cells.append(Paragraph(str(value), field_value))
tbl = Table([cells], colWidths=col_widths)
tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
return tbl
# ── Helper: narrative paragraph under a section ───────────────────────────────
def narrative(text):
return Paragraph(text, normal_val)
# ── Lab results table ─────────────────────────────────────────────────────────
def lab_table(headers, rows):
data = [headers] + rows
col_n = len(headers)
cw = [170*mm / col_n] * col_n
tbl = Table(data, colWidths=cw, repeatRows=1)
style = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
]
tbl.setStyle(TableStyle(style))
return tbl
# ── Transfusion details table ─────────────────────────────────────────────────
def transfusion_table():
headers = ["Unit #", "Product", "Volume (mL)", "Blood Group", "Date / Time", "Duration", "Reaction"]
rows = [
["1", "Packed Red Blood Cells (pRBC)", "350", "A +ve", "02 Jul 2026 / 14:20", "3 hrs", "None"],
["2", "Packed Red Blood Cells (pRBC)", "350", "A +ve", "02 Jul 2026 / 19:10", "3 hrs", "None"],
["3", "Packed Red Blood Cells (pRBC)", "350", "A +ve", "03 Jul 2026 / 09:45", "3 hrs", "None"],
]
col_ws = [12*mm, 45*mm, 22*mm, 22*mm, 35*mm, 18*mm, 16*mm]
data = [headers] + rows
tbl = Table(data, colWidths=col_ws, repeatRows=1)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
return tbl
# ── Medications table ─────────────────────────────────────────────────────────
def medication_table():
headers = ["Medication", "Dose", "Route", "Frequency", "Duration", "Notes"]
rows = [
["Ferrous Sulphate", "200 mg", "Oral", "TDS", "3 months", "Take with food"],
["Folic Acid", "5 mg", "Oral", "OD", "3 months", ""],
["Omeprazole", "20 mg", "Oral", "OD", "4 weeks", "Before breakfast"],
]
col_ws = [38*mm, 18*mm, 15*mm, 22*mm, 22*mm, 55*mm]
data = [headers] + rows
tbl = Table(data, colWidths=col_ws, repeatRows=1)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
return tbl
# ── Build document ─────────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=20*mm,
rightMargin=20*mm,
topMargin=15*mm,
bottomMargin=15*mm,
title="Blood Transfusion Discharge Summary",
author="City General Hospital"
)
story = []
# ── HEADER BANNER ──────────────────────────────────────────────────────────
header_data = [[
Paragraph("CITY GENERAL HOSPITAL", hospital_name),
Paragraph("123 Medical Park Road, Springfield | Tel: (01) 555-7200 | Fax: (01) 555-7201", hospital_addr),
Paragraph("BLOOD TRANSFUSION DISCHARGE SUMMARY", doc_title),
Paragraph("CONFIDENTIAL – For authorised medical personnel only", small_italic),
]]
header_tbl = Table(header_data, colWidths=[170*mm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
story.append(header_tbl)
story.append(Spacer(1, 4*mm))
# ── PATIENT DEMOGRAPHICS ───────────────────────────────────────────────────
story.append(section_header(" PATIENT DEMOGRAPHICS"))
story.append(Spacer(1, 1*mm))
story.append(info_row([("Patient Name:", "Mrs. Sarah Jane Mitchell"), ("MRN / Hospital ID:", "CGH-2026-084712")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Date of Birth:", "14 March 1978 (Age 48)"), ("Sex:", "Female")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Address:", "42 Elmwood Avenue, Springfield, SP4 3LK"), ("Contact No:", "+44 7700 900 142")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Blood Group:", "A Rhesus Positive (A +ve)"), ("Allergies:", "Penicillin (rash)")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(Spacer(1, 3*mm))
# ── ADMISSION & DISCHARGE INFO ─────────────────────────────────────────────
story.append(section_header(" ADMISSION & DISCHARGE INFORMATION"))
story.append(Spacer(1, 1*mm))
story.append(info_row([("Admission Date:", "01 July 2026"), ("Discharge Date:", "04 July 2026")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Ward / Unit:", "Ward 7B – Haematology"), ("Length of Stay:", "3 days")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Admitting Consultant:", "Dr. Aisha Okonkwo, Haematologist"), ("Registrar:", "Dr. James Carney")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("GP / Referring Doctor:", "Dr. Peter Huang, Springfield Medical Centre"), ("Discharge Destination:", "Home")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(Spacer(1, 3*mm))
# ── REASON FOR ADMISSION ───────────────────────────────────────────────────
story.append(section_header(" REASON FOR ADMISSION & PRESENTING COMPLAINT"))
story.append(Spacer(1, 1*mm))
story.append(Table([[narrative(
"Mrs. Mitchell presented to the Emergency Department on 01 July 2026 with a 3-week history of "
"progressive fatigue, exertional dyspnoea, palpitations, and light-headedness. She also reported "
"heavy menstrual bleeding over the preceding 4 months. On examination she was pale, tachycardic "
"(HR 108 bpm), with conjunctival pallor. Blood pressure was 108/68 mmHg. Point-of-care Hb was "
"5.8 g/dL, prompting urgent admission for transfusion."
)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── DIAGNOSIS ─────────────────────────────────────────────────────────────
story.append(section_header(" DIAGNOSIS"))
story.append(Spacer(1, 1*mm))
diag_data = [
[Paragraph("Primary Diagnosis:", field_label),
Paragraph("Severe Iron Deficiency Anaemia (ICD-10: D50.9)", field_value)],
[Paragraph("Secondary Diagnosis:", field_label),
Paragraph("Menorrhagia secondary to uterine fibroids (ICD-10: N92.0 / D25.9)", field_value)],
[Paragraph("Comorbidities:", field_label),
Paragraph("Hypothyroidism (on Levothyroxine 75 mcg OD) | Mild Asthma (Salbutamol PRN)", field_value)],
]
diag_tbl = Table(diag_data, colWidths=[38*mm, 132*mm])
diag_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
story.append(diag_tbl)
story.append(Spacer(1, 3*mm))
# ── PRE-TRANSFUSION INVESTIGATIONS ────────────────────────────────────────
story.append(section_header(" PRE-TRANSFUSION INVESTIGATIONS"))
story.append(Spacer(1, 1*mm))
story.append(lab_table(
["Investigation", "Result", "Reference Range", "Flag"],
[
["Haemoglobin (Hb)", "5.8 g/dL", "12.0 – 16.0 g/dL", "↓ CRITICAL"],
["Haematocrit (HCT)", "19.2%", "36 – 46%", "↓ Low"],
["MCV", "64 fL", "80 – 100 fL", "↓ Microcytic"],
["MCH", "19.4 pg", "27 – 33 pg", "↓ Low"],
["Serum Ferritin", "4 µg/L", "12 – 150 µg/L", "↓ Low"],
["Serum Iron", "3.1 µmol/L", "11 – 30 µmol/L", "↓ Low"],
["TIBC", "92 µmol/L", "45 – 80 µmol/L", "↑ High"],
["Transferrin Sat.", "3.4%", "20 – 50%", "↓ Low"],
["WBC", "6.8 × 10⁹/L", "4.0 – 11.0", "Normal"],
["Platelets", "310 × 10⁹/L", "150 – 400", "Normal"],
["Blood Group & Screen", "A Rhesus Positive", "–", "No alloantibodies"],
["CRP", "8 mg/L", "< 10 mg/L", "Normal"],
["Renal Function (eGFR)", "82 mL/min", "> 60 mL/min", "Normal"],
["Thyroid (TSH)", "2.1 mU/L", "0.4 – 4.0 mU/L", "Normal"],
]
))
story.append(Spacer(1, 3*mm))
# ── TRANSFUSION DETAILS ────────────────────────────────────────────────────
story.append(section_header(" TRANSFUSION DETAILS"))
story.append(Spacer(1, 1*mm))
story.append(transfusion_table())
story.append(Spacer(1, 2*mm))
story.append(info_row(
[("Total Units:", "3 units Packed Red Blood Cells (pRBC)"), ("Total Volume:", "~1050 mL")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(info_row(
[("Pre-med Given:", "IV Paracetamol 1g + Chlorphenamine 10 mg IV (unit 1)"), ("Crossmatch Type:", "Electronic (Type & Screen)")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(info_row(
[("Transfusion Reactions:", "None reported across all 3 units"), ("Monitoring:", "Observations every 15 min (first hour), then hourly")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(Spacer(1, 3*mm))
# ── POST-TRANSFUSION RESULTS ───────────────────────────────────────────────
story.append(section_header(" POST-TRANSFUSION INVESTIGATIONS"))
story.append(Spacer(1, 1*mm))
story.append(lab_table(
["Investigation", "Pre-Transfusion", "Post-Transfusion (Day 3)", "Change", "Target"],
[
["Haemoglobin (Hb)", "5.8 g/dL", "9.4 g/dL", "+3.6 g/dL", "≥ 8.0 g/dL"],
["Haematocrit", "19.2%", "30.1%", "+10.9%", "≥ 25%"],
["Heart Rate", "108 bpm", "78 bpm", "-30 bpm", "60–100 bpm"],
["Blood Pressure", "108/68 mmHg","122/74 mmHg","+14/+6", "≥ 110/70 mmHg"],
["Symptoms", "Fatigue, SOB","Resolved", "Improved", "Asymptomatic"],
]
))
story.append(Spacer(1, 3*mm))
# ── CLINICAL COURSE ────────────────────────────────────────────────────────
story.append(section_header(" CLINICAL COURSE & MANAGEMENT"))
story.append(Spacer(1, 1*mm))
story.append(Table([[narrative(
"Mrs. Mitchell was admitted under Dr. Okonkwo and transfused with 3 units of crossmatched packed "
"red blood cells over 02–03 July 2026. Pre-transfusion IV paracetamol and chlorphenamine were "
"administered prior to the first unit as per haematology protocol. All units transfused without "
"adverse reactions. Haemoglobin response was satisfactory (5.8 → 9.4 g/dL). "
"Symptoms of fatigue and exertional dyspnoea resolved by day 3."
"<br/><br/>"
"The underlying cause was identified as iron deficiency secondary to chronic menorrhagia due to "
"uterine fibroids (confirmed on pelvic ultrasound: largest fibroid 4.2 cm intramural). She was "
"commenced on oral iron supplementation and referred to Gynaecology for ongoing management of "
"menorrhagia. Dietitian reviewed and provided dietary iron advice. Patient and family were "
"counselled regarding the indication for transfusion, the process, and the need for follow-up."
)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── DISCHARGE MEDICATIONS ─────────────────────────────────────────────────
story.append(section_header(" DISCHARGE MEDICATIONS"))
story.append(Spacer(1, 1*mm))
story.append(medication_table())
story.append(Spacer(1, 2*mm))
story.append(Table([[
Paragraph("NOTE: Patient to continue existing medications – Levothyroxine 75 mcg OD and Salbutamol inhaler PRN (unchanged).", alert_style)
]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── FOLLOW-UP PLAN ────────────────────────────────────────────────────────
story.append(section_header(" FOLLOW-UP PLAN"))
story.append(Spacer(1, 1*mm))
fu_data = [
["1.", "GP Review", "10 July 2026", "FBC, ferritin, iron studies. Review iron supplement tolerance."],
["2.", "Haematology OPD", "18 July 2026", "Dr. Okonkwo clinic – review response to iron therapy."],
["3.", "Gynaecology OPD", "25 July 2026", "Fibroid management – consider medical or surgical options for menorrhagia."],
["4.", "Endocrine / GP", "Routine", "Continue annual thyroid function monitoring."],
]
fu_hdr = [["#", "Appointment", "Date / Target", "Instructions"]]
fu_tbl = Table(fu_hdr + fu_data, colWidths=[8*mm, 38*mm, 32*mm, 92*mm], repeatRows=1)
fu_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ALIGN", (0,0), (1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(fu_tbl)
story.append(Spacer(1, 3*mm))
# ── PATIENT EDUCATION & DISCHARGE ADVICE ──────────────────────────────────
story.append(section_header(" PATIENT EDUCATION & DISCHARGE ADVICE"))
story.append(Spacer(1, 1*mm))
advice = [
"Take iron tablets with orange juice (vitamin C improves absorption). Avoid taking with tea, coffee, or dairy within 1 hour.",
"Stools may appear darker – this is expected and not a cause for concern.",
"Gradually increase activity as tolerated; avoid strenuous exercise for 1 week.",
"Return to ED immediately or call 999 if: chest pain, severe breathlessness, fever > 38°C, or heavy uncontrolled bleeding.",
"Contact GP if iron supplements are not tolerated (nausea/constipation) – alternative IV iron preparations are available.",
"Avoid NSAIDs (e.g. ibuprofen) due to gastrointestinal effects and risk of worsening menorrhagia.",
]
for i, line in enumerate(advice, 1):
story.append(Table([[Paragraph(f"{i}. {line}", body_text)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── SIGNATURES ────────────────────────────────────────────────────────────
story.append(section_header(" AUTHORISING CLINICIAN"))
story.append(Spacer(1, 1*mm))
sig_data = [
[Paragraph("Discharging Doctor:", field_label),
Paragraph("Dr. James Carney", field_value),
Paragraph("Consultant:", field_label),
Paragraph("Dr. Aisha Okonkwo, MBChB, FRCPath", field_value)],
[Paragraph("Designation:", field_label),
Paragraph("Haematology Registrar", field_value),
Paragraph("Date Signed:", field_label),
Paragraph("04 July 2026", field_value)],
[Paragraph("Signature:", field_label),
Paragraph("__________________________", field_value),
Paragraph("Bleep / Ext:", field_label),
Paragraph("Bleep 4427 | Ext. 7283", field_value)],
]
sig_tbl = Table(sig_data, colWidths=[30*mm, 55*mm, 30*mm, 55*mm])
sig_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
story.append(sig_tbl)
story.append(Spacer(1, 4*mm))
# ── FOOTER ────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=DARK_BLUE))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"This document is computer-generated and is valid without a wet signature unless otherwise indicated. "
"City General Hospital – Blood Transfusion Discharge Summary | Document Date: 04 July 2026 | "
"Page 1 of 1 | Form Ref: CGH-TRF-DS-v3.1",
footer_style
))
doc.build(story)
print(f"PDF generated: {OUTPUT}")
build_pdf()
cd /home/daytona/workspace/discharge-summary && python generate_discharge.py
Blood Transfusion Discharge Summary
PDF Document · PDF
Create same for any cancer patients in pdf form
~/discharge-summary/generate_cancer_discharge.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, Table, TableStyle,
HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
OUTPUT = "/home/daytona/workspace/discharge-summary/Cancer_Patient_Discharge_Summary.pdf"
# ── Colours ───────────────────────────────────────────────────────────────────
DARK_TEAL = colors.HexColor("#1A4A4A")
MID_TEAL = colors.HexColor("#2E7D6A")
LIGHT_TEAL = colors.HexColor("#D4EDE8")
ACCENT_ORG = colors.HexColor("#C0392B")
GREY_BG = colors.HexColor("#F4F6F8")
GREY_BORDER = colors.HexColor("#BDC3C7")
WHITE = colors.white
BLACK = colors.black
DARK_BLUE = colors.HexColor("#1A3557")
MID_BLUE = colors.HexColor("#2A6099")
LIGHT_BLUE = colors.HexColor("#D9E8F5")
WARN_BG = colors.HexColor("#FFF3CD")
WARN_BORDER = colors.HexColor("#FFC107")
# ── Styles ────────────────────────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
hospital_name = S("HN", fontName="Helvetica-Bold", fontSize=16, textColor=WHITE, alignment=TA_CENTER)
hospital_addr = S("HA", fontName="Helvetica", fontSize=9, textColor=colors.HexColor("#B2DFDB"), alignment=TA_CENTER)
doc_title = S("DT", fontName="Helvetica-Bold", fontSize=13, textColor=WHITE, alignment=TA_CENTER, spaceAfter=2)
small_italic = S("SI", fontName="Helvetica-Oblique",fontSize=8, textColor=colors.HexColor("#80CBC4"), alignment=TA_CENTER)
section_hdr = S("SH", fontName="Helvetica-Bold", fontSize=10, textColor=WHITE, leading=14)
field_label = S("FL", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_TEAL)
field_value = S("FV", fontName="Helvetica", fontSize=9, textColor=BLACK)
body_text = S("BT", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=14, spaceAfter=3)
alert_style = S("AS", fontName="Helvetica-Bold", fontSize=9, textColor=ACCENT_ORG)
footer_style = S("FS", fontName="Helvetica", fontSize=7.5,textColor=colors.HexColor("#777777"), alignment=TA_CENTER)
warn_style = S("WS", fontName="Helvetica-Bold", fontSize=8.5,textColor=colors.HexColor("#7D5A00"))
normal_val = S("NV", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=13)
# ── Helpers ───────────────────────────────────────────────────────────────────
def section_header(title, color=None):
bg = color or DARK_TEAL
tbl = Table([[Paragraph(title, section_hdr)]], colWidths=[170*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
return tbl
def info_row(pairs, col_widths=None):
if col_widths is None:
col_widths = [32*mm, 53*mm] * len(pairs)
cells = []
for label, value in pairs:
cells.append(Paragraph(label, field_label))
cells.append(Paragraph(str(value), field_value))
tbl = Table([cells], colWidths=col_widths)
tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
return tbl
def narrative(text):
return Paragraph(text, normal_val)
def data_table(headers, rows, col_ws=None, hdr_color=None):
hc = hdr_color or MID_TEAL
col_n = len(headers)
if col_ws is None:
col_ws = [170*mm / col_n] * col_n
data = [headers] + rows
tbl = Table(data, colWidths=col_ws, repeatRows=1)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), hc),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_TEAL]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
return tbl
def warning_box(text):
tbl = Table([[Paragraph(text, warn_style)]], colWidths=[170*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), WARN_BG),
("LEFTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 1, WARN_BORDER),
]))
return tbl
# ── Build PDF ─────────────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=20*mm, rightMargin=20*mm,
topMargin=15*mm, bottomMargin=15*mm,
title="Oncology Discharge Summary",
author="City Cancer Centre"
)
story = []
# ── HEADER BANNER ─────────────────────────────────────────────────────────
hdr = Table([[
Paragraph("CITY CANCER CENTRE – ONCOLOGY UNIT", hospital_name),
Paragraph("45 Healing Way, Springfield | Tel: (01) 555-8300 | Fax: (01) 555-8301 | oncology@citycancer.nhs.uk", hospital_addr),
Paragraph("ONCOLOGY INPATIENT DISCHARGE SUMMARY", doc_title),
Paragraph("STRICTLY CONFIDENTIAL – For authorised medical personnel only", small_italic),
]], colWidths=[170*mm])
hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_TEAL),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
story.append(hdr)
story.append(Spacer(1, 4*mm))
# ── PATIENT DEMOGRAPHICS ──────────────────────────────────────────────────
story.append(section_header(" PATIENT DEMOGRAPHICS"))
story.append(Spacer(1, 1*mm))
story.append(info_row([("Patient Name:", "Mr. Robert Thomas Hale"), ("MRN / Hospital ID:", "CCC-2026-031947")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Date of Birth:", "22 September 1958 (Age 67)"), ("Sex:", "Male")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Address:", "18 Birchwood Close, Springfield, SP2 8MN"), ("Contact No:", "+44 7700 900 387")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Blood Group:", "O Rhesus Positive (O +ve)"), ("Allergies:", "NKDA – No known drug allergies")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Next of Kin:", "Mrs. Carol Hale (Wife) | +44 7700 900 512"), ("Emergency Contact:", "Same as NOK")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(Spacer(1, 3*mm))
# ── ADMISSION & DISCHARGE INFO ────────────────────────────────────────────
story.append(section_header(" ADMISSION & DISCHARGE INFORMATION"))
story.append(Spacer(1, 1*mm))
story.append(info_row([("Admission Date:", "23 June 2026"), ("Discharge Date:", "04 July 2026")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Ward / Unit:", "Ward 12 – Oncology / Haematology"), ("Length of Stay:", "11 days")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("Admitting Consultant:", "Dr. Priya Menon, Consultant Oncologist"), ("Registrar:", "Dr. Luke Farrell")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(info_row([("GP / Referring Doctor:", "Dr. Sonia Patel, Springfield Health Centre"), ("Discharge Destination:", "Home with Community Oncology Support")],
col_widths=[30*mm, 55*mm, 30*mm, 55*mm]))
story.append(Spacer(1, 3*mm))
# ── ONCOLOGY DIAGNOSIS ────────────────────────────────────────────────────
story.append(section_header(" ONCOLOGY DIAGNOSIS & STAGING"))
story.append(Spacer(1, 1*mm))
diag_data = [
[Paragraph("Primary Cancer:", field_label),
Paragraph("Non-Small Cell Lung Cancer (NSCLC) – Adenocarcinoma (ICD-10: C34.1)", field_value)],
[Paragraph("Tumour Site:", field_label),
Paragraph("Right upper lobe – 4.8 cm mass; hilar and mediastinal lymph node involvement", field_value)],
[Paragraph("Stage:", field_label),
Paragraph("Stage IIIB – T3 N3 M0 (AJCC 8th Edition)", field_value)],
[Paragraph("Molecular Profile:", field_label),
Paragraph("EGFR mutation: Exon 21 L858R substitution (positive) | ALK: Negative | PD-L1: 55%", field_value)],
[Paragraph("Date of Diagnosis:", field_label),
Paragraph("February 2026 (confirmed on CT-guided biopsy, right upper lobe)", field_value)],
[Paragraph("Secondary Diagnoses:", field_label),
Paragraph("Chemotherapy-induced anaemia | Neutropenia (post-cycle 3) | Hypertension | Type 2 Diabetes Mellitus", field_value)],
]
d_tbl = Table(diag_data, colWidths=[38*mm, 132*mm])
d_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
story.append(d_tbl)
story.append(Spacer(1, 3*mm))
# ── REASON FOR ADMISSION ──────────────────────────────────────────────────
story.append(section_header(" REASON FOR THIS ADMISSION"))
story.append(Spacer(1, 1*mm))
story.append(Table([[narrative(
"Mr. Hale was admitted as a planned inpatient for Cycle 3, Day 1 of chemotherapy "
"(Carboplatin + Pemetrexed). He also developed febrile neutropenia on Day 7 post-chemotherapy "
"(temperature 38.6°C, neutrophil count 0.4 × 10⁹/L), requiring IV antibiotics and "
"supportive care. Additionally, symptomatic anaemia (Hb 6.9 g/dL) was identified, necessitating "
"red cell transfusion. He was kept inpatient until neutrophil recovery and haematological stability "
"were confirmed."
)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── CHEMOTHERAPY ADMINISTERED ─────────────────────────────────────────────
story.append(section_header(" CHEMOTHERAPY / SYSTEMIC THERAPY ADMINISTERED"))
story.append(Spacer(1, 1*mm))
story.append(data_table(
["Drug", "Dose", "Route", "Day", "Date", "Cycle", "Notes"],
[
["Carboplatin", "AUC 5 (480 mg)", "IV Infusion", "Day 1", "23 Jun 2026", "Cycle 3 / 4", "Over 30 min; pre-hydration given"],
["Pemetrexed", "500 mg/m² (925 mg)", "IV Infusion", "Day 1", "23 Jun 2026", "Cycle 3 / 4", "Over 10 min; B12 & folic acid supplemented"],
["Dexamethasone","8 mg", "IV", "D1–D3", "23–25 Jun 2026","Pre-med", "Anti-emetic / skin protection"],
["Ondansetron", "8 mg", "IV", "D1", "23 Jun 2026", "Pre-med", "Anti-emetic"],
["G-CSF (Filgrastim)","300 mcg", "SC", "D3–D9", "25 Jun – 01 Jul 2026","Support","Neutropenia prophylaxis/treatment"],
],
col_ws=[28*mm, 28*mm, 18*mm, 12*mm, 26*mm, 18*mm, 40*mm]
))
story.append(Spacer(1, 3*mm))
# ── BLOOD TRANSFUSION DETAILS ─────────────────────────────────────────────
story.append(section_header(" BLOOD TRANSFUSION DETAILS (Cancer-Related Anaemia)"))
story.append(Spacer(1, 1*mm))
story.append(data_table(
["Unit #", "Product", "Volume", "Blood Group", "Date / Time", "Duration", "Reaction"],
[
["1", "Packed Red Blood Cells (pRBC)", "350 mL", "O +ve", "29 Jun 2026 / 10:00", "3 hrs", "None"],
["2", "Packed Red Blood Cells (pRBC)", "350 mL", "O +ve", "29 Jun 2026 / 15:00", "3 hrs", "None"],
],
col_ws=[12*mm, 45*mm, 18*mm, 18*mm, 36*mm, 18*mm, 23*mm]
))
story.append(Spacer(1, 2*mm))
story.append(info_row(
[("Total Units:", "2 units pRBC (~700 mL)"), ("Indication:", "Symptomatic anaemia – chemotherapy-induced (Hb 6.9 g/dL)")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(info_row(
[("Pre-Transfusion Hb:", "6.9 g/dL"), ("Post-Transfusion Hb:", "9.6 g/dL (Day 3 post-transfusion)")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(info_row(
[("Transfusion Reactions:", "None"), ("Crossmatch:", "Electronic – Type & Screen; no alloantibodies")],
col_widths=[28*mm, 57*mm, 28*mm, 57*mm]
))
story.append(Spacer(1, 3*mm))
# ── KEY INVESTIGATIONS ────────────────────────────────────────────────────
story.append(section_header(" KEY INVESTIGATIONS DURING ADMISSION"))
story.append(Spacer(1, 1*mm))
story.append(data_table(
["Investigation", "Admission (23 Jun)", "Nadir / Worst", "Discharge (04 Jul)", "Reference Range"],
[
["Haemoglobin (Hb)", "7.8 g/dL", "6.9 g/dL", "9.6 g/dL", "13.5–17.5 g/dL"],
["Neutrophils (ANC)", "1.2 × 10⁹/L", "0.4 × 10⁹/L", "1.8 × 10⁹/L", "1.8–7.5 × 10⁹/L"],
["Platelets", "128 × 10⁹/L", "78 × 10⁹/L", "105 × 10⁹/L", "150–400 × 10⁹/L"],
["WBC", "3.1 × 10⁹/L", "1.4 × 10⁹/L", "3.6 × 10⁹/L", "4.0–11.0 × 10⁹/L"],
["CRP", "28 mg/L", "142 mg/L", "18 mg/L", "< 10 mg/L"],
["Creatinine", "82 µmol/L", "91 µmol/L", "84 µmol/L", "64–104 µmol/L"],
["eGFR", "74 mL/min", "68 mL/min", "72 mL/min", "> 60 mL/min"],
["Albumin", "32 g/L", "29 g/L", "31 g/L", "35–50 g/L"],
["LDH", "312 U/L", "388 U/L", "290 U/L", "135–225 U/L"],
["Blood Glucose (fasting)", "9.2 mmol/L", "11.4 mmol/L", "8.8 mmol/L", "3.9–5.5 mmol/L"],
["Blood Cultures (×2)", "Sent", "No growth", "–", "Sterile"],
],
col_ws=[44*mm, 28*mm, 28*mm, 28*mm, 42*mm]
))
story.append(Spacer(1, 2*mm))
story.append(Table([[narrative(
"<b>Imaging:</b> CT Chest/Abdomen/Pelvis (25 Jun 2026) – Stable disease compared to pre-cycle 2 scan "
"(Mar 2026). Primary RUL mass: 4.6 cm (prev 4.8 cm). No new metastatic deposits. "
"Mediastinal lymph nodes stable. No pleural effusion."
)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── CLINICAL COURSE ───────────────────────────────────────────────────────
story.append(section_header(" CLINICAL COURSE & MANAGEMENT"))
story.append(Spacer(1, 1*mm))
story.append(Table([[narrative(
"<b>Cycle 3 Chemotherapy (23 Jun 2026):</b> Carboplatin AUC 5 and Pemetrexed 500 mg/m² administered "
"uneventfully. Pre-medicated with dexamethasone, ondansetron, and oral folic acid. Patient tolerated "
"the infusion well with no acute infusion reactions."
"<br/><br/>"
"<b>Febrile Neutropenia (30 Jun 2026):</b> On Day 7, Mr. Hale developed a temperature of 38.6°C "
"with rigors. ANC was critically low at 0.4 × 10⁹/L. Blood cultures ×2 taken. Started empirically on "
"IV Piperacillin-Tazobactam 4.5 g TDS and IV Gentamicin (single daily dose) per neutropenic sepsis "
"protocol. Fever settled within 48 hours. Blood cultures showed no growth at 5 days. Antibiotics "
"de-escalated to oral Co-Amoxiclav after 72 hours of apyrexia. G-CSF (Filgrastim) continued until "
"ANC > 1.5 × 10⁹/L."
"<br/><br/>"
"<b>Transfusion (29 Jun 2026):</b> Symptomatic anaemia (Hb 6.9 g/dL – fatigue, breathlessness at rest). "
"2 units pRBC transfused without incident. Post-transfusion Hb 9.6 g/dL. Symptoms improved "
"significantly."
"<br/><br/>"
"<b>Glycaemic Control:</b> Hyperglycaemia noted during dexamethasone course (peak glucose 11.4 mmol/L). "
"Sliding scale insulin used inpatient; transitioned back to Metformin at discharge with advice to "
"monitor home glucose closely."
"<br/><br/>"
"<b>Palliative Care / Supportive Input:</b> Palliative Care team reviewed on 02 Jul. Patient and wife "
"counselled regarding prognosis and treatment goals. Goals of care documented. Patient wishes to "
"continue active oncological treatment. DNACPR discussion deferred pending next clinic."
)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── PERFORMANCE STATUS ────────────────────────────────────────────────────
story.append(section_header(" PERFORMANCE STATUS & FUNCTIONAL ASSESSMENT"))
story.append(Spacer(1, 1*mm))
story.append(info_row(
[("ECOG PS (Admission):", "2 – Ambulatory, capable of self-care"), ("ECOG PS (Discharge):", "1 – Restricted strenuous activity, ambulatory")],
col_widths=[40*mm, 45*mm, 40*mm, 45*mm]
))
story.append(info_row(
[("Nutritional Status:", "Malnutrition risk (MUST score 2) – Dietitian reviewed"), ("Weight:", "71 kg (↓ 3 kg from baseline 74 kg)")],
col_widths=[40*mm, 45*mm, 40*mm, 45*mm]
))
story.append(info_row(
[("Pain Score (NRS):", "3/10 at discharge – controlled on regular paracetamol"), ("Nausea:", "Mild (Grade 1 CTCAE) – managed with metoclopramide")],
col_widths=[40*mm, 45*mm, 40*mm, 45*mm]
))
story.append(Spacer(1, 3*mm))
# ── DISCHARGE MEDICATIONS ─────────────────────────────────────────────────
story.append(section_header(" DISCHARGE MEDICATIONS"))
story.append(Spacer(1, 1*mm))
med_headers = ["Medication", "Dose", "Route", "Freq.", "Duration", "Notes"]
med_rows = [
["Pemetrexed supplementation:", "", "", "", "", ""],
[" Folic Acid", "5 mg", "Oral", "OD", "Ongoing (throughout chemo)", "Take daily – reduce toxicity"],
[" Vitamin B12 (Cyanocobalamin)", "1 mg", "IM", "Q9 weeks", "Per chemo schedule", "Next due 15 Aug 2026"],
["Dexamethasone", "4 mg", "Oral", "BD", "3 days (D1–D3 post-chemo)", "Skin rash prophylaxis"],
["Ondansetron", "8 mg", "Oral", "TDS PRN", "5 days post-chemo", "Anti-emetic"],
["Metoclopramide", "10 mg", "Oral", "TDS PRN", "As needed", "Breakthrough nausea"],
["Metformin", "1000 mg", "Oral", "BD", "Ongoing", "Resume post-chemo; hold day of chemo"],
["Amlodipine", "5 mg", "Oral", "OD", "Ongoing", "Hypertension management"],
["Paracetamol", "1 g", "Oral", "QDS", "Ongoing PRN", "For pain – max 4 g/day"],
["Loperamide", "2 mg", "Oral", "PRN", "As needed", "For chemotherapy-related diarrhoea"],
["Omeprazole", "20 mg", "Oral", "OD", "Ongoing", "Gastric protection"],
]
col_ws_med = [42*mm, 20*mm, 14*mm, 22*mm, 34*mm, 38*mm]
med_data = [med_headers] + med_rows
med_tbl = Table(med_data, colWidths=col_ws_med, repeatRows=1)
med_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#E8F5E9")),
("FONTNAME", (0,1), (-1,1), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,2), (-1,-1), [WHITE, LIGHT_TEAL]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
]))
story.append(med_tbl)
story.append(Spacer(1, 2*mm))
story.append(warning_box(
"⚠ IMPORTANT: Do NOT take ibuprofen, aspirin, or other NSAIDs. Avoid live vaccines during "
"chemotherapy. Monitor blood glucose daily – contact GP if > 14 mmol/L. "
"Cycle 4 chemotherapy planned: 21 July 2026."
))
story.append(Spacer(1, 3*mm))
# ── FOLLOW-UP PLAN ────────────────────────────────────────────────────────
story.append(section_header(" FOLLOW-UP PLAN"))
story.append(Spacer(1, 1*mm))
fu_data = [
["1.", "GP Review", "11 July 2026", "FBC, CRP, renal panel, LFTs, glucose. Review neutropenia recovery."],
["2.", "Oncology Clinic", "14 July 2026", "Dr. Menon – pre-cycle 4 assessment. Discuss CT response. DNACPR discussion."],
["3.", "Cycle 4 Chemotherapy", "21 July 2026", "Carboplatin AUC 5 + Pemetrexed Day 1 (subject to blood counts clearance)."],
["4.", "Vitamin B12 Injection", "15 Aug 2026", "IM Cyanocobalamin 1 mg – administer at GP surgery."],
["5.", "Dietitian / Nutrition", "18 July 2026", "Community dietitian follow-up – weight monitoring, nutritional supplementation."],
["6.", "Palliative Care", "As needed", "Community palliative support team engaged. Contact via key worker."],
["7.", "Respiratory / Thoracics", "28 July 2026", "Review of CT findings and discussion of radiation therapy potential."],
]
fu_hdr = [["#", "Appointment", "Date / Target", "Instructions"]]
fu_tbl = Table(fu_hdr + fu_data, colWidths=[8*mm, 40*mm, 30*mm, 92*mm], repeatRows=1)
fu_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ALIGN", (0,0), (1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_TEAL]),
("GRID", (0,0), (-1,-1), 0.4, GREY_BORDER),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(fu_tbl)
story.append(Spacer(1, 3*mm))
# ── RED FLAGS & DISCHARGE ADVICE ──────────────────────────────────────────
story.append(section_header(" RED FLAGS – RETURN TO HOSPITAL IMMEDIATELY IF:"))
story.append(Spacer(1, 1*mm))
red_flags = [
("🌡 Fever:", "Temperature ≥ 38°C – go to ED immediately; do NOT wait for GP appointment (neutropenic sepsis risk)."),
("🩸 Bleeding:", "Unusual bruising, blood in urine/stool, or uncontrolled bleeding."),
("😮 Breathing:", "Sudden worsening of breathlessness, chest pain, or coughing up blood."),
("🤢 GI symptoms:", "Persistent vomiting or diarrhoea (> 4 episodes/day) – risk of dehydration."),
("🧠 Neurological:", "New confusion, severe headache, vision changes, or seizures."),
("💊 Medication concerns:", "Unable to take medications; severe side effects from new prescriptions."),
]
for icon_label, text in red_flags:
row_data = [[Paragraph(icon_label, alert_style), Paragraph(text, body_text)]]
row_tbl = Table(row_data, colWidths=[30*mm, 140*mm])
row_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FFF8F8")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
story.append(row_tbl)
story.append(Spacer(1, 3*mm))
# ── PATIENT EDUCATION ─────────────────────────────────────────────────────
story.append(section_header(" PATIENT & FAMILY EDUCATION"))
story.append(Spacer(1, 1*mm))
advice = [
"Wash hands frequently. Avoid crowded places and people with infections during the first 2 weeks after chemotherapy.",
"Eat small, frequent meals high in protein. Avoid raw/undercooked foods (infection risk).",
"Fatigue is expected – rest when needed. Gentle walking is encouraged; avoid strenuous activity.",
"Report any mouth sores, difficulty swallowing, or skin rashes to the oncology team promptly.",
"24-hour oncology helpline: (01) 555-8399. Always mention you are a chemotherapy patient.",
"Next chemotherapy (Cycle 4) is provisionally booked for 21 July 2026, subject to blood count recovery.",
"Information leaflets provided: Carboplatin, Pemetrexed, Neutropenic Sepsis, Living with Lung Cancer.",
]
for i, line in enumerate(advice, 1):
story.append(Table([[Paragraph(f"{i}. {line}", body_text)]], colWidths=[170*mm]))
story.append(Spacer(1, 3*mm))
# ── MULTIDISCIPLINARY TEAM ────────────────────────────────────────────────
story.append(section_header(" MULTIDISCIPLINARY TEAM INVOLVEMENT"))
story.append(Spacer(1, 1*mm))
story.append(data_table(
["Team / Specialist", "Name", "Input / Action"],
[
["Consultant Oncologist", "Dr. Priya Menon", "Overall cancer management, chemotherapy plan, CT review"],
["Haematology Registrar", "Dr. Luke Farrell", "Blood transfusion management, neutropenia treatment"],
["Palliative Care Team", "Dr. Nina Walsh", "Goals of care discussion, symptom management planning"],
["Clinical Nurse Specialist", "Sr. Marie O'Brien", "Patient education, key worker, community liaison"],
["Dietitian", "Ms. Tanya Brooks", "Nutritional assessment (MUST 2), high-protein diet plan"],
["Pharmacist", "Mr. Darren Singh", "Medication reconciliation, chemotherapy drug checking"],
["Physiotherapist", "Mr. Tom Reyes", "Mobility assessment, breathing exercises, discharge planning"],
],
col_ws=[45*mm, 42*mm, 83*mm]
))
story.append(Spacer(1, 3*mm))
# ── SIGNATURES ────────────────────────────────────────────────────────────
story.append(section_header(" AUTHORISING CLINICIAN"))
story.append(Spacer(1, 1*mm))
sig_data = [
[Paragraph("Discharging Doctor:", field_label), Paragraph("Dr. Luke Farrell", field_value),
Paragraph("Consultant:", field_label), Paragraph("Dr. Priya Menon, MBChB, FRCP, MD (Oncology)", field_value)],
[Paragraph("Designation:", field_label), Paragraph("Oncology Registrar", field_value),
Paragraph("Date Signed:", field_label), Paragraph("04 July 2026", field_value)],
[Paragraph("Signature:", field_label), Paragraph("__________________________", field_value),
Paragraph("Bleep / Ext:", field_label), Paragraph("Bleep 5512 | Ext. 8341", field_value)],
[Paragraph("Key Worker:", field_label), Paragraph("Sr. Marie O'Brien (CNS)", field_value),
Paragraph("CNS Contact:", field_label), Paragraph("(01) 555-8310 | marie.obrien@citycancer.nhs.uk", field_value)],
]
sig_tbl = Table(sig_data, colWidths=[32*mm, 53*mm, 32*mm, 53*mm])
sig_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_BG),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,0), (-1,-1), 0.5, GREY_BORDER),
]))
story.append(sig_tbl)
story.append(Spacer(1, 4*mm))
# ── FOOTER ────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=DARK_TEAL))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"This document is computer-generated and is valid without a wet signature unless otherwise indicated. "
"City Cancer Centre – Oncology Discharge Summary | Document Date: 04 July 2026 | Page 1 of 1 | "
"Form Ref: CCC-ONC-DS-v2.4 | 24-hr Oncology Helpline: (01) 555-8399",
footer_style
))
doc.build(story)
print(f"PDF generated: {OUTPUT}")
build_pdf()
cd /home/daytona/workspace/discharge-summary && python generate_cancer_discharge…
Cancer Patient Discharge Summary
PDF Document · PDF
| Section | Details |
|---|---|
| Patient Demographics | Name, DOB, NOK, blood group, allergies |
| Admission & Discharge | 23 Jun – 04 Jul 2026 (11 days), Ward 12 Oncology |
| Oncology Diagnosis & Staging | NSCLC Stage IIIB T3N3M0, EGFR Exon 21 L858R+, PD-L1 55% |
| Reason for Admission | Cycle 3 chemo + febrile neutropenia + symptomatic anaemia |
| Chemotherapy Given | Carboplatin AUC 5 + Pemetrexed 500 mg/m², pre-meds, G-CSF |
| Blood Transfusion | 2 units pRBC (Hb 6.9 → 9.6 g/dL), no reactions |
| Investigations | Admission vs nadir vs discharge: FBC, CRP, renal, glucose + CT result |
| Clinical Course | Chemo, febrile neutropenia management, transfusion, glycaemic control, palliative input |
| Performance Status | ECOG PS, weight, pain/nausea scores |
| Discharge Medications | 11 drugs including chemo supplements, anti-emetics, Metformin, Amlodipine |
| Follow-up Plan | 7 appointments (GP, Oncology, Chemo Cycle 4, B12, Dietitian, Palliative, Respiratory) |
| Red Flags | Fever, bleeding, breathlessness, GI, neuro - return to ED instructions |
| Patient Education | 7 specific instructions + 24-hr helpline |
| MDT Involvement | 7 team members documented |
| Signatures | Registrar + Consultant + CNS key worker |