~/opd-prescriptions/generate_prescriptions.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import KeepTogether
import datetime
OUTPUT = "/home/daytona/workspace/opd-prescriptions/OPD_Prescriptions_Adult.pdf"
# ── Styles ──────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
clinic_name_style = ParagraphStyle(
"ClinicName", fontSize=16, fontName="Helvetica-Bold",
textColor=colors.HexColor("#1a3c6e"), alignment=TA_CENTER, spaceAfter=2
)
clinic_sub_style = ParagraphStyle(
"ClinicSub", fontSize=9, fontName="Helvetica",
textColor=colors.HexColor("#555555"), alignment=TA_CENTER, spaceAfter=2
)
section_title_style = ParagraphStyle(
"SectionTitle", fontSize=13, fontName="Helvetica-Bold",
textColor=colors.white, alignment=TA_CENTER,
spaceAfter=4, spaceBefore=4,
backColor=colors.HexColor("#1a3c6e"),
leftIndent=0, rightIndent=0
)
complaint_style = ParagraphStyle(
"Complaint", fontSize=10, fontName="Helvetica-Bold",
textColor=colors.HexColor("#1a3c6e"), spaceAfter=2
)
normal_style = ParagraphStyle(
"Normal2", fontSize=9.5, fontName="Helvetica",
textColor=colors.black, spaceAfter=3, leading=14
)
rx_header_style = ParagraphStyle(
"RxHeader", fontSize=20, fontName="Helvetica-BoldOblique",
textColor=colors.HexColor("#1a3c6e"), spaceAfter=4
)
drug_style = ParagraphStyle(
"Drug", fontSize=10, fontName="Helvetica-Bold",
textColor=colors.HexColor("#1a3c6e"), spaceAfter=1
)
sig_style = ParagraphStyle(
"Sig", fontSize=9.5, fontName="Helvetica",
textColor=colors.HexColor("#333333"), leftIndent=12, spaceAfter=4, leading=13
)
advice_style = ParagraphStyle(
"Advice", fontSize=9, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#444444"), leftIndent=10, spaceAfter=3, leading=13
)
footer_style = ParagraphStyle(
"Footer", fontSize=8, fontName="Helvetica",
textColor=colors.HexColor("#888888"), alignment=TA_CENTER
)
label_style = ParagraphStyle(
"Label", fontSize=9, fontName="Helvetica-Bold",
textColor=colors.HexColor("#555555"), spaceAfter=1
)
# ── Prescriptions Data ───────────────────────────────────────────────────
today = datetime.date.today().strftime("%d %B %Y")
prescriptions = [
{
"condition": "Common Cold / Upper Respiratory Tract Infection (URTI)",
"symptoms": "Runny nose, nasal congestion, sneezing, sore throat, mild fever, watery eyes",
"diagnosis": "Acute URTI (Symptomatic)",
"drugs": [
{
"name": "1. Paracetamol (Acetaminophen) 500 mg Tablet",
"sig": "1-2 tablets every 6-8 hours as needed for fever or discomfort\nMax: 4 g/day | Duration: 3-5 days"
},
{
"name": "2. Cetirizine 10 mg Tablet (Antihistamine)",
"sig": "1 tablet once daily at bedtime\nDuration: 5 days"
},
{
"name": "3. Pseudoephedrine 60 mg + Triprolidine 2.5 mg (Actifed or equivalent)",
"sig": "1 tablet every 8 hours\nDuration: 3-5 days | Avoid in hypertensive patients"
},
{
"name": "4. Saline Nasal Spray (0.9% NaCl)",
"sig": "2 sprays each nostril 3-4 times daily as needed for congestion"
},
{
"name": "5. Throat Lozenges (Benzocaine / Menthol based)",
"sig": "1 lozenge every 3-4 hours as needed for throat pain\nMax: 8 lozenges/day | Duration: 5 days"
},
],
"advice": [
"Rest adequately; increase fluid intake (warm water, soups, herbal teas)",
"Avoid cold drinks and exposure to dust/smoke",
"Steam inhalation 2x/day may relieve congestion",
"Return if fever >38.5°C persists beyond 3 days or symptoms worsen",
"Antibiotics are NOT required for viral URTI",
],
"followup": "Review after 5 days if not improved"
},
{
"condition": "Fever / Influenza (Flu-like Illness)",
"symptoms": "High-grade fever, body aches, myalgia, headache, chills, fatigue, loss of appetite",
"diagnosis": "Pyrexia of Probable Viral Origin / Influenza-like Illness",
"drugs": [
{
"name": "1. Paracetamol 500 mg Tablet",
"sig": "2 tablets every 6 hours while fever persists\nMax: 4 g/day | Duration: 5 days"
},
{
"name": "2. Ibuprofen 400 mg Tablet (Antipyretic / Anti-inflammatory)",
"sig": "1 tablet every 8 hours WITH FOOD\nDuration: 3-5 days | Avoid in peptic ulcer, renal disease, asthma"
},
{
"name": "3. Diclofenac Sodium 50 mg Tablet (for severe myalgia/body aches)",
"sig": "1 tablet every 8-12 hours with food\nDuration: 3 days"
},
{
"name": "4. Oral Rehydration Salts (ORS) Sachets",
"sig": "1 sachet dissolved in 1 litre of clean water; sip throughout the day\nUse as long as fever persists"
},
{
"name": "5. Vitamin C 500 mg + Zinc 10 mg Tablet",
"sig": "1 tablet once daily with meals\nDuration: 7-10 days"
},
],
"advice": [
"Strict bed rest; drink at least 2-3 litres of fluids daily",
"Tepid sponging for very high fever (>39°C)",
"Light easily digestible diet; avoid fatty/spicy food",
"Return immediately if fever >40°C, convulsions, extreme weakness, or breathlessness",
"Do NOT give Aspirin for fever in adults with flu (risk of Reye's syndrome interaction)",
],
"followup": "Review after 3 days or earlier if condition deteriorates"
},
{
"condition": "Acute Gastroenteritis / Diarrhea",
"symptoms": "Loose watery stools (3+ times/day), nausea, vomiting, abdominal cramps, low-grade fever",
"diagnosis": "Acute Gastroenteritis (Non-dysenteric, Symptomatic)",
"drugs": [
{
"name": "1. Oral Rehydration Salts (ORS) Sachets",
"sig": "1 sachet in 1 litre of water; drink 200-400 mL after each loose stool\nContinue until diarrhea stops"
},
{
"name": "2. Loperamide HCl 2 mg Capsule (Anti-motility)",
"sig": "2 capsules (4 mg) initially, then 1 capsule after each loose stool\nMax: 8 mg/day | Duration: 2 days | Avoid if fever or bloody stool"
},
{
"name": "3. Metoclopramide 10 mg Tablet (Anti-emetic)",
"sig": "1 tablet 30 minutes before meals, 3 times daily\nDuration: 3 days | Avoid in children < 15 yrs"
},
{
"name": "4. Omeprazole 20 mg Capsule (Gastric protection)",
"sig": "1 capsule once daily before breakfast\nDuration: 5 days"
},
{
"name": "5. Ciprofloxacin 500 mg Tablet (if bacterial cause suspected: watery + cramping)",
"sig": "1 tablet every 12 hours\nDuration: 3-5 days | Prescribe only when indicated"
},
{
"name": "6. Zinc Sulfate 20 mg Tablet",
"sig": "1 tablet once daily\nDuration: 10-14 days (WHO recommended adjunct)"
},
],
"advice": [
"Drink plenty of clear fluids (ORS, clear soups, coconut water)",
"Eat bland foods: rice, banana, toast, plain boiled potato (BRAT diet)",
"Avoid dairy, fatty, spicy, and fried foods until fully recovered",
"Strict hand hygiene especially before meals and after toilet",
"Return if vomiting is persistent, blood in stool, or signs of severe dehydration",
],
"followup": "Review in 2-3 days; IV fluids if unable to tolerate oral intake"
},
{
"condition": "Cough / Acute Bronchitis",
"symptoms": "Persistent dry or productive cough, chest tightness, mild dyspnea, possible low-grade fever, sputum",
"diagnosis": "Acute Bronchitis / Productive Cough (Symptomatic)",
"drugs": [
{
"name": "1. Ambroxol HCl 30 mg Syrup / Tablet (Expectorant / Mucolytic)",
"sig": "1 tablet (or 10 mL syrup) 3 times daily after meals\nDuration: 5-7 days"
},
{
"name": "2. Salbutamol 2 mg Tablet (Bronchodilator - if wheezing present)",
"sig": "1 tablet 3 times daily\nDuration: 5 days | Use MDI (100 mcg/puff, 2 puffs TDS) if available"
},
{
"name": "3. Dextromethorphan 15 mg Tablet (for dry, non-productive cough)",
"sig": "1 tablet every 6-8 hours as needed\nDuration: 3-5 days | Do NOT use with productive cough"
},
{
"name": "4. Prednisolone 5 mg Tablet (short course - for severe bronchospasm)",
"sig": "4 tablets (20 mg) once daily in the morning with food\nDuration: 5 days then stop (no taper needed for short course)"
},
{
"name": "5. Azithromycin 500 mg Tablet (if atypical/bacterial cause suspected)",
"sig": "1 tablet once daily\nDuration: 3-5 days | Take on empty stomach or with food"
},
],
"advice": [
"Warm fluids and steam inhalation 3x/day to loosen mucus",
"Avoid smoking and exposure to dust, fumes, and cold air",
"Honey and warm lemon water may soothe the throat and reduce cough",
"Elevate head while sleeping for comfort",
"Return if cough persists > 3 weeks, hemoptysis, or high fever develops",
],
"followup": "Review in 7 days; chest X-ray if no improvement"
},
{
"condition": "Headache / Tension-Type Headache",
"symptoms": "Bilateral dull/pressing headache, neck stiffness, eye strain, stress, mild nausea, no aura",
"diagnosis": "Tension-Type Headache (Episodic, Symptomatic)",
"drugs": [
{
"name": "1. Paracetamol 500 mg + Caffeine 65 mg Tablet (Panadol Extra or equivalent)",
"sig": "1-2 tablets every 6 hours as needed for headache\nMax: 8 tablets/day | Do not use daily for > 10 days (medication overuse headache)"
},
{
"name": "2. Ibuprofen 400 mg Tablet",
"sig": "1 tablet every 8 hours WITH FOOD\nDuration: 3 days maximum per episode"
},
{
"name": "3. Metoclopramide 10 mg Tablet (for associated nausea)",
"sig": "1 tablet 3 times daily as needed for nausea\nDuration: 2-3 days"
},
{
"name": "4. Amitriptyline 10 mg Tablet (for frequent/recurrent tension headache - prophylaxis)",
"sig": "1 tablet at bedtime\nDuration: 6-8 weeks (review before continuation)"
},
],
"advice": [
"Identify and avoid triggers: stress, poor posture, eye strain, dehydration, irregular meals",
"Adequate sleep (7-8 hrs/night) and regular meal schedule",
"Relaxation techniques, warm compress to neck/shoulders",
"Limit screen time and ensure proper lighting while working",
"Return immediately if sudden severe headache ('thunderclap'), vision changes, or neurological symptoms",
],
"followup": "Review in 2 weeks if headache is recurrent or worsening"
},
{
"condition": "Skin Infection / Infected Wound (Mild-Moderate)",
"symptoms": "Localized skin redness, warmth, swelling, purulent discharge, pain, tenderness at wound site",
"diagnosis": "Cellulitis / Superficial Skin Infection / Infected Wound (Symptomatic)",
"drugs": [
{
"name": "1. Amoxicillin-Clavulanate (Co-amoxiclav) 625 mg Tablet",
"sig": "1 tablet every 8 hours with food\nDuration: 5-7 days"
},
{
"name": "2. Flucloxacillin 500 mg Capsule (if staphylococcal infection suspected)",
"sig": "1 capsule every 6 hours on empty stomach (30 min before meals)\nDuration: 5-7 days"
},
{
"name": "3. Cetirizine 10 mg Tablet (for associated itch/urticaria)",
"sig": "1 tablet once daily at bedtime\nDuration: 5 days"
},
{
"name": "4. Ibuprofen 400 mg Tablet (for pain and inflammation)",
"sig": "1 tablet every 8 hours WITH FOOD\nDuration: 3-5 days"
},
{
"name": "5. Mupirocin 2% Ointment (Topical antibiotic)",
"sig": "Apply thin layer to affected area 3 times daily after cleaning wound\nDuration: 7-10 days | Cover with sterile dressing"
},
{
"name": "6. Povidone-Iodine Solution (Betadine 10%)",
"sig": "Clean wound with diluted solution (1:10 in water) twice daily before dressing"
},
],
"advice": [
"Keep wound clean and dry; change dressing daily or when soiled",
"Elevate affected limb to reduce swelling",
"Complete the full antibiotic course even if the wound appears healed",
"Do NOT squeeze or puncture boils or abscesses at home",
"Return if spreading redness, high fever, pus discharge increasing, or red streaks appear",
],
"followup": "Wound review in 3-5 days; surgical drainage if abscess not improving"
},
{
"condition": "Urinary Tract Infection (UTI) - Symptoms Only",
"symptoms": "Burning/painful urination (dysuria), increased urinary frequency, urgency, cloudy or foul-smelling urine, lower abdominal discomfort",
"diagnosis": "Symptomatic Lower Urinary Tract Infection (Uncomplicated Cystitis - Adult Female)",
"drugs": [
{
"name": "1. Nitrofurantoin 100 mg (Modified-release) Capsule - First Line",
"sig": "1 capsule twice daily WITH FOOD\nDuration: 5 days"
},
{
"name": "2. Trimethoprim 200 mg Tablet (Alternative first-line)",
"sig": "1 tablet twice daily\nDuration: 7 days"
},
{
"name": "3. Ciprofloxacin 500 mg Tablet (if above not tolerated / suspected resistance)",
"sig": "1 tablet every 12 hours\nDuration: 3 days for uncomplicated UTI"
},
{
"name": "4. Phenazopyridine 200 mg Tablet (Urinary analgesic - symptomatic relief)",
"sig": "1 tablet 3 times daily AFTER MEALS\nDuration: 2 days only | Will turn urine orange - this is normal"
},
{
"name": "5. Ibuprofen 400 mg Tablet (for pelvic/lower abdominal pain)",
"sig": "1 tablet every 8 hours WITH FOOD\nDuration: 2-3 days"
},
],
"advice": [
"Increase fluid intake to at least 2-3 litres of water per day",
"Void completely after each urination and always after sexual intercourse",
"Wipe front to back after using the toilet",
"Avoid caffeine, alcohol, and spicy food until symptoms resolve",
"Return immediately if fever, back/flank pain, rigors develop (may indicate pyelonephritis)",
],
"followup": "Review in 5-7 days; urine culture if symptoms recur or persist"
},
]
# ── PDF Generation ───────────────────────────────────────────────────────
def build_prescription_header():
"""Returns clinic header elements."""
elements = []
elements.append(Spacer(1, 4*mm))
elements.append(Paragraph("CITY GENERAL HOSPITAL", clinic_name_style))
elements.append(Paragraph("Department of General Medicine – Outpatient Department (OPD)", clinic_sub_style))
elements.append(Paragraph("General OPD | Tel: +XX-XXXX-XXXXXX | www.citygeneralhospital.example", clinic_sub_style))
elements.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor("#1a3c6e"), spaceAfter=4))
return elements
def build_patient_info_table():
"""Returns patient info table."""
data = [
[Paragraph("<b>Patient Name:</b> ________________________", normal_style),
Paragraph("<b>Age/Sex:</b> _______ / _______", normal_style),
Paragraph("<b>Date:</b> " + today, normal_style)],
[Paragraph("<b>Address:</b> ________________________", normal_style),
Paragraph("<b>Weight:</b> __________ kg", normal_style),
Paragraph("<b>OPD No:</b> __________", normal_style)],
]
table = Table(data, colWidths=[75*mm, 55*mm, 50*mm])
table.setStyle(TableStyle([
("FONTSIZE", (0, 0), (-1, -1), 9),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("TOPPADDING", (0, 0), (-1, -1), 4),
("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#cccccc")),
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#f4f8ff")),
]))
return table
def build_prescription_page(rx):
elements = []
# Header
elements.extend(build_prescription_header())
# Condition Banner
elements.append(Spacer(1, 2*mm))
banner_data = [[Paragraph(f" Rx — {rx['condition']}", section_title_style)]]
banner_table = Table(banner_data, colWidths=[180*mm])
banner_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#1a3c6e")),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 8),
]))
elements.append(banner_table)
elements.append(Spacer(1, 3*mm))
# Patient Info
elements.append(build_patient_info_table())
elements.append(Spacer(1, 3*mm))
# Presenting Symptoms
sym_data = [[
Paragraph("<b>Presenting Symptoms:</b>", label_style),
Paragraph(rx["symptoms"], normal_style)
]]
sym_table = Table(sym_data, colWidths=[42*mm, 138*mm])
sym_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#eef4ff")),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#aac4ee")),
]))
elements.append(sym_table)
elements.append(Spacer(1, 2*mm))
# Working Diagnosis
diag_data = [[
Paragraph("<b>Working Diagnosis:</b>", label_style),
Paragraph(rx["diagnosis"], complaint_style)
]]
diag_table = Table(diag_data, colWidths=[42*mm, 138*mm])
diag_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#fff8e1")),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#f0c040")),
]))
elements.append(diag_table)
elements.append(Spacer(1, 4*mm))
# Rx symbol
elements.append(Paragraph("℞", rx_header_style))
# Drugs table
drug_rows = []
for drug in rx["drugs"]:
drug_rows.append([
Paragraph(drug["name"], drug_style),
Paragraph(drug["sig"].replace("\n", "<br/>"), sig_style)
])
drug_table = Table(drug_rows, colWidths=[78*mm, 102*mm])
drug_table.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "TOP"),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("RIGHTPADDING", (0, 0), (-1, -1), 6),
("ROWBACKGROUNDS", (0, 0), (-1, -1), [colors.HexColor("#f0f6ff"), colors.white]),
("LINEBELOW", (0, 0), (-1, -2), 0.3, colors.HexColor("#d0d8e8")),
("BOX", (0, 0), (-1, -1), 0.7, colors.HexColor("#1a3c6e")),
]))
elements.append(drug_table)
elements.append(Spacer(1, 4*mm))
# Advice Section
elements.append(Paragraph("General Advice & Instructions:", complaint_style))
for i, adv in enumerate(rx["advice"], 1):
elements.append(Paragraph(f" {i}. {adv}", advice_style))
elements.append(Spacer(1, 3*mm))
# Follow-up
fu_data = [[
Paragraph("<b>Follow-up / Review:</b>", label_style),
Paragraph(rx["followup"], normal_style)
]]
fu_table = Table(fu_data, colWidths=[42*mm, 138*mm])
fu_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#e8f5e9")),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#66bb6a")),
]))
elements.append(fu_table)
elements.append(Spacer(1, 4*mm))
# Signature line
sig_data = [[
Paragraph("Doctor's Signature: _____________________", normal_style),
Paragraph("Designation: _____________________", normal_style),
Paragraph("Stamp:", normal_style),
]]
sig_table = Table(sig_data, colWidths=[65*mm, 65*mm, 50*mm])
sig_table.setStyle(TableStyle([
("TOPPADDING", (0, 0), (-1, -1), 12),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("LINEABOVE", (0, 0), (-1, 0), 0.5, colors.HexColor("#999999")),
]))
elements.append(sig_table)
# Footer
elements.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#cccccc"), spaceBefore=4))
elements.append(Paragraph(
"This prescription is issued for symptomatic treatment only. Lab investigations may be required if symptoms persist or worsen. "
"All medications should be taken as directed. Do not share prescriptions. | City General Hospital OPD",
footer_style
))
return elements
# ── Build PDF ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=15*mm,
leftMargin=15*mm,
topMargin=10*mm,
bottomMargin=12*mm
)
story = []
for i, rx in enumerate(prescriptions):
story.extend(build_prescription_page(rx))
if i < len(prescriptions) - 1:
story.append(PageBreak())
doc.build(story)
print(f"PDF generated: {OUTPUT}")