~/timetable/generate_timetable.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, HRFlowable
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import KeepTogether
doc = SimpleDocTemplate(
"/home/daytona/workspace/timetable/Study_Timetable.pdf",
pagesize=A4,
rightMargin=1.5*cm, leftMargin=1.5*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle('Title', fontSize=18, fontName='Helvetica-Bold',
alignment=TA_CENTER, textColor=colors.HexColor('#1a237e'), spaceAfter=4)
subtitle_style = ParagraphStyle('Subtitle', fontSize=10, fontName='Helvetica',
alignment=TA_CENTER, textColor=colors.HexColor('#455a64'), spaceAfter=2)
section_style = ParagraphStyle('Section', fontSize=11, fontName='Helvetica-Bold',
textColor=colors.white, alignment=TA_CENTER)
note_style = ParagraphStyle('Note', fontSize=8.5, fontName='Helvetica',
textColor=colors.HexColor('#37474f'), spaceAfter=3, leading=12)
label_style = ParagraphStyle('Label', fontSize=9, fontName='Helvetica-Bold',
textColor=colors.HexColor('#1a237e'))
# Colors
COL_MORNING = colors.HexColor('#e3f2fd') # light blue
COL_AFTERNOON = colors.HexColor('#e8f5e9') # light green
COL_EVENING = colors.HexColor('#fff3e0') # light orange
COL_SLEEP = colors.HexColor('#ede7f6') # light purple
COL_BUSY = colors.HexColor('#fce4ec') # light pink
COL_HDR = colors.HexColor('#1a237e') # dark navy
COL_HDR2 = colors.HexColor('#1565c0') # medium blue
COL_SPM = colors.HexColor('#bbdefb')
COL_BIO = colors.HexColor('#c8e6c9')
COL_CC = colors.HexColor('#ffe0b2')
COL_PTC = colors.HexColor('#f8bbd0')
COL_REHAB = colors.HexColor('#e1bee7')
elements = []
# ── TITLE ──────────────────────────────────────────────────────────────────────
elements.append(Paragraph("📚 University Exam Study Timetable", title_style))
elements.append(Paragraph("July 14 – August 13, 2026 • Fixed Daily Schedule • All Subjects Every Day", subtitle_style))
elements.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#1a237e'), spaceAfter=6))
# ── SUBJECT LEGEND ─────────────────────────────────────────────────────────────
legend_data = [
[Paragraph("SUBJECT LEGEND", ParagraphStyle('lh', fontSize=9, fontName='Helvetica-Bold',
textColor=colors.white, alignment=TA_CENTER)),
"", "", "", "", ""],
[
Paragraph("SPM\n2 hrs 45 min/day", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#0d47a1'))),
Paragraph("BIOSTATISTICS\n1 hr 15 min/day", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#1b5e20'))),
Paragraph("CLINICAL CARDIO\n3 hrs/day ★", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#e65100'))),
Paragraph("PT CARDIOLOGY\n1 hr 10 min/day", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#880e4f'))),
Paragraph("REHAB\n35 min/day", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#4a148c'))),
Paragraph("TOTAL STUDY\n~9 hrs 25 min", ParagraphStyle('l', fontSize=8, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.HexColor('#212121'))),
]
]
legend_table = Table(legend_data, colWidths=[2.8*cm]*6)
legend_table.setStyle(TableStyle([
('SPAN', (0,0), (5,0)),
('BACKGROUND', (0,0), (5,0), COL_HDR),
('BACKGROUND', (0,1), (0,1), COL_SPM),
('BACKGROUND', (1,1), (1,1), COL_BIO),
('BACKGROUND', (2,1), (2,1), COL_CC),
('BACKGROUND', (3,1), (3,1), COL_PTC),
('BACKGROUND', (4,1), (4,1), COL_REHAB),
('BACKGROUND', (5,1), (5,1), colors.HexColor('#f5f5f5')),
('BOX', (0,0), (-1,-1), 1, COL_HDR),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.white),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
('ROWBACKGROUNDS', (0,0), (-1,-1), [None]),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
]))
elements.append(legend_table)
elements.append(Spacer(1, 8))
# ── HELPER to build a block table ─────────────────────────────────────────────
def block_table(header, header_color, rows, col_widths):
cell_style = ParagraphStyle('cell', fontSize=8.5, fontName='Helvetica', leading=11, textColor=colors.HexColor('#212121'))
bold_style = ParagraphStyle('bold', fontSize=8.5, fontName='Helvetica-Bold', leading=11, textColor=colors.HexColor('#212121'))
data = [[Paragraph(header, ParagraphStyle('hdr', fontSize=9, fontName='Helvetica-Bold',
textColor=colors.white, alignment=TA_CENTER)), "", ""]]
style_cmds = [
('SPAN', (0,0), (2,0)),
('BACKGROUND', (0,0), (2,0), header_color),
('ALIGN', (0,0), (2,0), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BOX', (0,0), (-1,-1), 1, header_color),
('INNERGRID', (0,1), (-1,-1), 0.3, colors.HexColor('#cfd8dc')),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
]
for i, (time, subject, dur, bg) in enumerate(rows):
row_num = i + 1
data.append([
Paragraph(time, cell_style),
Paragraph(subject, bold_style if subject.startswith('**') else cell_style),
Paragraph(dur, cell_style)
])
if bg:
style_cmds.append(('BACKGROUND', (0, row_num), (2, row_num), bg))
t = Table(data, colWidths=col_widths)
t.setStyle(TableStyle(style_cmds))
return t
# Column widths for schedule tables
CW = [3.2*cm, 10.0*cm, 3.5*cm]
# ── MORNING BLOCK ──────────────────────────────────────────────────────────────
morning_rows = [
("3:00 – 3:15 AM", "Wake up • Freshen up • Water", "15 min", colors.HexColor('#f5f5f5')),
("3:15 – 4:30 AM", "SPM (New section / Theory reading / Memorisation)", "75 min", COL_SPM),
("4:30 – 5:15 AM", "Biostatistics (Formulas / Data types / Statistical tests)", "45 min", COL_BIO),
("5:15 – 6:10 AM", "Clinical Cardiology (ECG / Conditions / Mechanisms)", "55 min", COL_CC),
("6:10 – 6:30 AM", "PT Cardiology (Assessment / Exercise protocols)", "20 min", COL_PTC),
("6:30 – 7:00 AM", "Freshen up • Breakfast • College prep", "30 min", colors.HexColor('#f5f5f5')),
]
elements.append(block_table("🌙 MORNING BLOCK | 3:00 AM – 6:30 AM (3 hrs 15 min study)", COL_HDR, morning_rows, CW))
elements.append(Spacer(1, 6))
# ── BUSY ──────────────────────────────────────────────────────────────────────
busy_data = [[Paragraph("🏥 COLLEGE / CLINIC | 7:00 AM – 1:00 PM (No study)", ParagraphStyle('b', fontSize=9,
fontName='Helvetica-BoldOblique', textColor=colors.HexColor('#b71c1c'), alignment=TA_CENTER))]]
busy_table = Table(busy_data, colWidths=[16.7*cm])
busy_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), COL_BUSY),
('BOX', (0,0), (-1,-1), 1, colors.HexColor('#ef9a9a')),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
]))
elements.append(busy_table)
elements.append(Spacer(1, 6))
# ── LUNCH ─────────────────────────────────────────────────────────────────────
lunch_data = [[Paragraph("🍽 LUNCH + REST | 1:00 PM – 2:00 PM", ParagraphStyle('b', fontSize=9,
fontName='Helvetica-Bold', textColor=colors.HexColor('#33691e'), alignment=TA_CENTER))]]
lunch_table = Table(lunch_data, colWidths=[16.7*cm])
lunch_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#f1f8e9')),
('BOX', (0,0), (-1,-1), 1, colors.HexColor('#aed581')),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
]))
elements.append(lunch_table)
elements.append(Spacer(1, 6))
# ── AFTERNOON BLOCK ────────────────────────────────────────────────────────────
afternoon_rows = [
("2:00 – 2:45 PM", "SPM (New section / MCQs / Past paper questions)", "45 min", COL_SPM),
("2:45 – 3:10 PM", "Biostatistics (Problem solving / Past papers)", "25 min", COL_BIO),
("3:10 – 4:00 PM", "Clinical Cardiology (Cases / Revision / Deep topics)", "50 min", COL_CC),
("4:00 – 4:40 PM", "SPM (Continued — Epidemiology / Health programmes)", "40 min", COL_SPM),
("4:40 – 5:00 PM", "Quick recap of all afternoon notes", "20 min", colors.HexColor('#f5f5f5')),
]
elements.append(block_table("☀ AFTERNOON BLOCK | 2:00 PM – 5:00 PM (2 hrs 40 min study)", colors.HexColor('#2e7d32'), afternoon_rows, CW))
elements.append(Spacer(1, 6))
# ── NAP ───────────────────────────────────────────────────────────────────────
nap_data = [[Paragraph("😴 NAP / REST | 5:00 PM – 6:00 PM (Protect this — compensates for short night sleep)", ParagraphStyle('b', fontSize=9,
fontName='Helvetica-Bold', textColor=colors.HexColor('#4a148c'), alignment=TA_CENTER))]]
nap_table = Table(nap_data, colWidths=[16.7*cm])
nap_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), COL_SLEEP),
('BOX', (0,0), (-1,-1), 1, colors.HexColor('#ce93d8')),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
]))
elements.append(nap_table)
elements.append(Spacer(1, 6))
# ── EVENING BLOCK ──────────────────────────────────────────────────────────────
evening_rows = [
("6:00 – 6:10 PM", "Settle in — Scan all notes from the day", "10 min", colors.HexColor('#f5f5f5')),
("6:10 – 7:25 PM", "Clinical Cardiology (Deep topics / ECG / MCQs / Cases)", "75 min", COL_CC),
("7:25 – 8:15 PM", "PT Cardiology (Treatment techniques / Protocols / Questions)", "50 min", COL_PTC),
("8:15 – 8:45 PM", "BREAK + DINNER", "30 min", colors.HexColor('#f5f5f5')),
("8:45 – 9:20 PM", "Rehab (Concepts / Notes / Weak points)", "35 min", COL_REHAB),
("9:20 – 9:45 PM", "Rapid Revision — All 5 subjects, ~5 min each", "25 min", colors.HexColor('#e0f7fa')),
("9:45 – 10:00 PM", "Write 3 key points per subject + Wind down (no screens)", "15 min", colors.HexColor('#f5f5f5')),
]
elements.append(block_table("🌆 EVENING BLOCK | 6:00 PM – 10:00 PM (4 hrs study)", colors.HexColor('#e65100'), evening_rows, CW))
elements.append(Spacer(1, 6))
# ── SLEEP ─────────────────────────────────────────────────────────────────────
sleep_data = [[Paragraph("🌛 SLEEP | 10:00 PM – 3:00 AM = 5 hrs Night Sleep + 1 hr Nap = 6 hrs Total ✅", ParagraphStyle('b', fontSize=9,
fontName='Helvetica-Bold', textColor=colors.HexColor('#1a237e'), alignment=TA_CENTER))]]
sleep_table = Table(sleep_data, colWidths=[16.7*cm])
sleep_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), COL_SLEEP),
('BOX', (0,0), (-1,-1), 1.5, colors.HexColor('#7986cb')),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
]))
elements.append(sleep_table)
elements.append(Spacer(1, 8))
# ── 4-WEEK PHASE PLAN ─────────────────────────────────────────────────────────
elements.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#90a4ae'), spaceAfter=5))
elements.append(Paragraph("4-WEEK CONTENT PROGRESSION (Same timetable daily — content changes each week)",
ParagraphStyle('ph', fontSize=10, fontName='Helvetica-Bold', textColor=colors.HexColor('#1a237e'), spaceAfter=4)))
phase_data = [
[Paragraph("Week", ParagraphStyle('ph2', fontSize=9, fontName='Helvetica-Bold', textColor=colors.white, alignment=TA_CENTER)),
Paragraph("Dates", ParagraphStyle('ph2', fontSize=9, fontName='Helvetica-Bold', textColor=colors.white, alignment=TA_CENTER)),
Paragraph("Focus", ParagraphStyle('ph2', fontSize=9, fontName='Helvetica-Bold', textColor=colors.white, alignment=TA_CENTER))],
["Week 1", "July 14 – 20", "First reading — cover basics, definitions, core concepts of all 5 subjects"],
["Week 2", "July 21 – 27", "Deeper reading — mechanisms, clinical applications, build short notes per topic"],
["Week 3", "July 28 – Aug 3", "MCQ practice + past paper questions for all subjects every day; identify weak spots"],
["Week 4", "Aug 4 – 13", "Intensive revision — weak topics, mock tests, final notes, rapid recall drills"],
]
phase_table = Table(phase_data, colWidths=[2.0*cm, 3.2*cm, 11.5*cm])
phase_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (2,0), COL_HDR),
('BACKGROUND', (0,1), (0,1), colors.HexColor('#bbdefb')),
('BACKGROUND', (0,2), (0,2), colors.HexColor('#c8e6c9')),
('BACKGROUND', (0,3), (0,3), colors.HexColor('#ffe0b2')),
('BACKGROUND', (0,4), (0,4), colors.HexColor('#f8bbd0')),
('BOX', (0,0), (-1,-1), 1, COL_HDR),
('INNERGRID', (0,0), (-1,-1), 0.3, colors.HexColor('#cfd8dc')),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8.5),
('FONTNAME', (0,1), (1,-1), 'Helvetica-Bold'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
elements.append(phase_table)
elements.append(Spacer(1, 8))
# ── KEY RULES ─────────────────────────────────────────────────────────────────
elements.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#90a4ae'), spaceAfter=5))
elements.append(Paragraph("KEY RULES", ParagraphStyle('krh', fontSize=10, fontName='Helvetica-Bold', textColor=colors.HexColor('#1a237e'), spaceAfter=4)))
rules = [
"✅ Do NOT skip any subject — all 5 every single day, no exceptions.",
"✅ Rehab goes last every evening — strongest subject, used when energy is lower.",
"✅ Biostatistics: solve at least 5 problems during afternoon slot — active practice, not just reading.",
"✅ SPM morning slot is peak-focus time — use it for memorisation-heavy content.",
"✅ Clinical Cardiology + PT Cardiology studied same evening — overlapping physiology saves revision time.",
"✅ 10:00 PM hard stop — protect your 5 hrs night sleep. No screens after 9:45 PM.",
"✅ 5:00–6:00 PM nap is NON-NEGOTIABLE — it compensates for short night sleep and prevents week 3–4 burnout.",
"✅ Keep a 'Weak Topics Notebook' — every Saturday, list what you struggled with and target it the next week.",
]
for rule in rules:
elements.append(Paragraph(rule, ParagraphStyle('rule', fontSize=8.5, fontName='Helvetica', leading=13,
textColor=colors.HexColor('#37474f'), leftIndent=5, spaceAfter=2)))
elements.append(Spacer(1, 8))
elements.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#cfd8dc'), spaceAfter=4))
elements.append(Paragraph("Generated by Orris • July 14, 2026 • 1-Month Exam Preparation Plan",
ParagraphStyle('footer', fontSize=7.5, fontName='Helvetica', textColor=colors.HexColor('#90a4ae'), alignment=TA_CENTER)))
doc.build(elements)
print("PDF created successfully.")