~/uhs-tracker/create_tracker.py
from openpyxl import Workbook
from openpyxl.styles import (
Font, PatternFill, Alignment, Border, Side, GradientFill
)
from openpyxl.utils import get_column_letter
from openpyxl.formatting.rule import ColorScaleRule, CellIsRule, FormulaRule
from openpyxl.worksheet.datavalidation import DataValidation
wb = Workbook()
# ─────────────────────────────────────────────
# COLOUR PALETTE
# ─────────────────────────────────────────────
C_DARK_BLUE = "1A3A5C" # header bg
C_MED_BLUE = "2E6DA4" # sub-header
C_LIGHT_BLUE = "D9E8F5" # alt row
C_WHITE = "FFFFFF"
C_GREEN_DONE = "70AD47" # completed
C_ORANGE_IP = "F4A83B" # in progress
C_RED_SKIP = "FF4444" # skipped
C_YELLOW_HL = "FFF2CC" # highlight
C_GREY_HDR = "F2F2F2"
C_DARK_TEXT = "1F2D3D"
C_GREEN_BG = "E2EFDA"
C_RED_BG = "FFDEDE"
def hdr_font(sz=11, bold=True, color=C_WHITE):
return Font(name="Arial", size=sz, bold=bold, color=color)
def body_font(sz=10, bold=False, color=C_DARK_TEXT):
return Font(name="Arial", size=sz, bold=bold, color=color)
def fill(hex_color):
return PatternFill("solid", start_color=hex_color, fgColor=hex_color)
def center(wrap=False):
return Alignment(horizontal="center", vertical="center", wrap_text=wrap)
def left(wrap=True):
return Alignment(horizontal="left", vertical="center", wrap_text=wrap)
thin = Side(style="thin", color="BFBFBF")
thick = Side(style="medium", color="1A3A5C")
def thin_border():
return Border(left=thin, right=thin, top=thin, bottom=thin)
def thick_border():
return Border(left=thick, right=thick, top=thick, bottom=thick)
# ═══════════════════════════════════════════════════════════════════════════════
# SHEET 1 – DAILY TRACKER
# ═══════════════════════════════════════════════════════════════════════════════
ws = wb.active
ws.title = "Daily Tracker"
ws.sheet_view.showGridLines = False
ws.sheet_view.zoomScale = 90
# freeze panes: keep day/header visible
ws.freeze_panes = "C4"
# ── DATA ────────────────────────────────────────────────────────────────────
days = [
# (day_num, date_str, subject, topic, blocks)
(1, "Mon 14 Jul", "MEDICINE", "CVS – Heart Failure, MI, ACS",
["Block 1: HF types, staging, drugs (Step-Up Ch.1)",
"Block 2: IHD / MI / STEMI vs NSTEMI",
"Block 3: 20 MCQs – CVS Past UHS",
"Block 4: Osmosis – Heart Failure + MI",
"Block 5: MCQ Review + Error Log"]),
(2, "Tue 15 Jul", "MEDICINE", "CVS – HTN, Arrhythmias, Valvular",
["Block 1: Hypertension + Arrhythmias (AF, VT, Heart Block)",
"Block 2: Valvular disease – murmurs, causes, management",
"Block 3: 20 MCQs – CVS continued",
"Block 4: Osmosis – Arrhythmias / Valvular disease",
"Block 5: MCQ Review + CVS notes consolidation"]),
(3, "Wed 16 Jul", "MEDICINE", "Respiratory – Asthma, COPD, TB, Lung",
["Block 1: Asthma vs COPD – differences, stepwise management",
"Block 2: Pneumonia, TB, Lung Ca, Pleural effusion",
"Block 3: 20 MCQs – Respiratory",
"Block 4: Osmosis – Asthma / COPD / TB",
"Block 5: MCQ Review + Resp summary table"]),
(4, "Thu 17 Jul", "MEDICINE", "GI + Hepatology – Ulcer, IBD, Cirrhosis",
["Block 1: PUD, GERD, IBD (Crohn's vs UC)",
"Block 2: Hepatitis, Cirrhosis, Complications (ascites, SBP, HE, HRS)",
"Block 3: 20 MCQs – GI + Liver",
"Block 4: Osmosis – Cirrhosis / IBD",
"Block 5: MCQ Review + GI comparison table"]),
(5, "Fri 18 Jul", "MEDICINE", "Endocrine + Renal – DM, Thyroid, AKI/CKD",
["Block 1: Diabetes – Type 1 vs 2, DKA vs HHS",
"Block 2: Thyroid + Adrenal disorders; AKI vs CKD",
"Block 3: 20 MCQs – Endocrine + Renal",
"Block 4: Osmosis – Diabetes / Thyroid",
"Block 5: MCQ Review"]),
(6, "Sat 19 Jul", "MEDICINE", "Neurology + Haematology – Stroke, Anaemia",
["Block 1: Stroke (ischaemic vs haemorrhagic), Seizures, Meningitis",
"Block 2: Anaemia types, Leukaemia, Lymphoma, Coagulation",
"Block 3: 20 MCQs – Neuro + Haem",
"Block 4: Osmosis – Stroke / Anaemia",
"Block 5: MCQ Review + Haem table"]),
(7, "Sun 20 Jul", "MEDICINE", "MEDICINE REVISION + 40 MCQ Mock",
["Block 1: Rapid revision – CVS + Respiratory (notes only)",
"Block 2: Rapid revision – GI + Endo + Renal + Neuro",
"Block 3: 40 MCQ Mock Test – Full Medicine (timed)",
"Block 4: Review all wrong answers",
"Block 5: Weak topic targeted re-reading"]),
(8, "Mon 21 Jul", "PAEDIATRICS", "Neonates + Growth + Immunisation",
["Block 1: Neonatal emergencies – RDS, meconium, jaundice, sepsis",
"Block 2: Growth milestones, Malnutrition (Kwashiorkor vs Marasmus), EPI",
"Block 3: 20 MCQs – Paeds Neonates + Nutrition",
"Block 4: Osmosis – Neonatal jaundice / Malnutrition",
"Block 5: MCQ Review"]),
(9, "Tue 22 Jul", "PAEDIATRICS", "Paeds CVS + Respiratory",
["Block 1: Paeds respiratory – Pneumonia, Croup, Bronchiolitis, Asthma",
"Block 2: Congenital heart – VSD, ASD, TOF, PDA features",
"Block 3: 20 MCQs – Paeds CVS + Resp",
"Block 4: Osmosis – TOF / Congenital heart disease",
"Block 5: MCQ Review + Congenital heart table"]),
(10, "Wed 23 Jul", "PAEDIATRICS", "Paeds GI + Neurology + Early Psych prep",
["Block 1: Diarrhoeal diseases, ORS, IMCI approach",
"Block 2: Paeds Neuro – Seizures, Meningitis, Febrile fits",
"Block 3: 20 MCQs – Paeds mixed",
"Block 4: Osmosis – Febrile seizures / Paeds meningitis",
"Block 5: MCQ Review + Paeds revision table"]),
(11, "Thu 24 Jul", "PSYCHIATRY", "Schizophrenia + Depression + Bipolar + Anxiety",
["Block 1: Schizophrenia – types, features, antipsychotics (typical vs atypical)",
"Block 2: Depression, Bipolar, Anxiety (GAD, Panic, OCD, PTSD)",
"Block 3: 20 MCQs – Psychiatry",
"Block 4: Osmosis – Schizophrenia / Depression",
"Block 5: MCQ Review"]),
(12, "Fri 25 Jul", "PSYCHIATRY", "Substance use + Personality + Emergencies",
["Block 1: Substance use, Personality disorders, Somatoform, ECT",
"Block 2: Psychiatric emergencies – NMS, Serotonin syndrome, Lithium toxicity",
"Block 3: 20 MCQs – Psychiatry complete",
"Block 4: Osmosis – Personality disorders / Bipolar",
"Block 5: Review all 5 summary tables"]),
(13, "Sat 26 Jul", "GRAND REVISION", "Full Mock + Final Prep",
["Block 1: Rapid review – Medicine summary tables",
"Block 2: Rapid review – Paeds + Psychiatry tables",
"Block 3: 60 MCQ Full Mock (all subjects, timed 90 min)",
"Block 4: Review every wrong answer",
"Block 5: LIGHT REST – read notes only, sleep early"]),
]
STATUS_OPTIONS = ["", "Done ✓", "In Progress", "Skipped"]
# ── TITLE ROW ────────────────────────────────────────────────────────────────
ws.merge_cells("A1:K1")
title_cell = ws["A1"]
title_cell.value = "UHS FINAL YEAR BLOCK EXAM – DAILY PROGRESS TRACKER"
title_cell.font = Font(name="Arial", size=14, bold=True, color=C_WHITE)
title_cell.fill = fill(C_DARK_BLUE)
title_cell.alignment = center()
ws.row_dimensions[1].height = 32
ws.merge_cells("A2:K2")
sub_cell = ws["A2"]
sub_cell.value = "Medicine + Paediatrics + Psychiatry | July 14 – 26, 2026 | Pomodoro 25+5 min | Max 8 hrs/day | Start 9:00 AM"
sub_cell.font = Font(name="Arial", size=10, italic=True, color=C_WHITE)
sub_cell.fill = fill(C_MED_BLUE)
sub_cell.alignment = center()
ws.row_dimensions[2].height = 20
# ── COLUMN HEADERS ───────────────────────────────────────────────────────────
headers = ["Day", "Date", "Subject", "Topic", "Study Block", "Status", "MCQs Attempted", "MCQs Correct", "Score %", "Osmosis ✓", "Notes / Mistakes"]
col_widths = [5, 12, 14, 32, 40, 14, 16, 14, 10, 12, 36]
for col_idx, (hdr, width) in enumerate(zip(headers, col_widths), start=1):
cell = ws.cell(row=3, column=col_idx, value=hdr)
cell.font = hdr_font(sz=10)
cell.fill = fill(C_DARK_BLUE)
cell.alignment = center(wrap=True)
cell.border = thin_border()
ws.column_dimensions[get_column_letter(col_idx)].width = width
ws.row_dimensions[3].height = 28
# ── DATA VALIDATION: dropdown for Status column (F) ──────────────────────────
dv = DataValidation(
type="list",
formula1='"Done ✓,In Progress,Skipped,"',
allow_blank=True,
showDropDown=False,
showErrorMessage=True,
error="Please choose: Done ✓, In Progress, Skipped, or leave blank",
errorTitle="Invalid entry"
)
ws.add_data_validation(dv)
dv_osmosis = DataValidation(
type="list",
formula1='"Yes ✓,No,-"',
allow_blank=True,
showDropDown=False
)
ws.add_data_validation(dv_osmosis)
# ── ROW DATA ─────────────────────────────────────────────────────────────────
subject_colors = {
"MEDICINE": "D9E8F5",
"PAEDIATRICS": "E2EFDA",
"PSYCHIATRY": "FFF2CC",
"GRAND REVISION":"FCE4D6",
}
current_row = 4
row_records = [] # (start_row, end_row, day_data)
for day_num, date_str, subject, topic, blocks in days:
start_row = current_row
subj_color = subject_colors.get(subject, C_WHITE)
for b_idx, block_text in enumerate(blocks):
is_first = (b_idx == 0)
# Col A – Day number (merged across 5 block rows)
cell_a = ws.cell(row=current_row, column=1)
if is_first:
cell_a.value = day_num
cell_a.font = Font(name="Arial", size=11, bold=True, color=C_WHITE)
cell_a.fill = fill(C_MED_BLUE)
cell_a.alignment = center()
cell_a.border = thin_border()
# Col B – Date (merged)
cell_b = ws.cell(row=current_row, column=2)
if is_first:
cell_b.value = date_str
cell_b.font = Font(name="Arial", size=9, bold=True, color=C_DARK_TEXT)
cell_b.fill = fill(subj_color)
cell_b.alignment = center(wrap=True)
cell_b.border = thin_border()
# Col C – Subject (merged)
cell_c = ws.cell(row=current_row, column=3)
if is_first:
cell_c.value = subject
cell_c.font = Font(name="Arial", size=9, bold=True, color=C_DARK_TEXT)
cell_c.fill = fill(subj_color)
cell_c.alignment = center(wrap=True)
cell_c.border = thin_border()
# Col D – Topic (merged)
cell_d = ws.cell(row=current_row, column=4)
if is_first:
cell_d.value = topic
cell_d.font = Font(name="Arial", size=9, bold=False, color=C_DARK_TEXT)
cell_d.fill = fill(subj_color)
cell_d.alignment = left(wrap=True)
cell_d.border = thin_border()
# Col E – Study Block
cell_e = ws.cell(row=current_row, column=5, value=block_text)
cell_e.font = body_font(sz=9)
cell_e.fill = fill(C_WHITE) if b_idx % 2 == 0 else fill(C_GREY_HDR)
cell_e.alignment = left(wrap=True)
cell_e.border = thin_border()
# Col F – Status (dropdown)
cell_f = ws.cell(row=current_row, column=6, value="")
cell_f.font = body_font(sz=9, bold=True)
cell_f.alignment = center()
cell_f.border = thin_border()
dv.add(cell_f)
# Col G – MCQs Attempted
cell_g = ws.cell(row=current_row, column=7, value="")
cell_g.font = body_font(sz=10)
cell_g.alignment = center()
cell_g.border = thin_border()
# Col H – MCQs Correct
cell_h = ws.cell(row=current_row, column=8, value="")
cell_h.font = body_font(sz=10)
cell_h.alignment = center()
cell_h.border = thin_border()
# Col I – Score % (formula)
g_addr = f"G{current_row}"
h_addr = f"H{current_row}"
cell_i = ws.cell(row=current_row, column=9,
value=f'=IF(AND({g_addr}<>"",{g_addr}>0),{h_addr}/{g_addr},"")')
cell_i.number_format = "0%"
cell_i.font = body_font(sz=10, bold=True)
cell_i.alignment = center()
cell_i.border = thin_border()
# Col J – Osmosis watched
cell_j = ws.cell(row=current_row, column=10, value="")
cell_j.font = body_font(sz=9)
cell_j.alignment = center()
cell_j.border = thin_border()
dv_osmosis.add(cell_j)
# Col K – Notes
cell_k = ws.cell(row=current_row, column=11, value="")
cell_k.font = body_font(sz=9)
cell_k.alignment = left(wrap=True)
cell_k.border = thin_border()
ws.row_dimensions[current_row].height = 22
current_row += 1
# Merge A, B, C, D for the 5 block rows
end_row = current_row - 1
for col in [1, 2, 3, 4]:
ws.merge_cells(start_row=start_row, start_column=col,
end_row=end_row, end_column=col)
row_records.append((start_row, end_row, subject))
# Thick border around the full day block
for r in range(start_row, end_row + 1):
for c in range(1, 12):
cell = ws.cell(row=r, column=c)
left_s = thick if c == 1 else thin
right_s = thick if c == 11 else thin
top_s = thick if r == start_row else thin
bottom_s = thick if r == end_row else thin
cell.border = Border(left=left_s, right=right_s,
top=top_s, bottom=bottom_s)
# ── CONDITIONAL FORMATTING – colour Status column ────────────────────────────
status_range = f"F4:F{current_row - 1}"
ws.conditional_formatting.add(
status_range,
CellIsRule(operator="equal", formula=['"Done ✓"'],
fill=PatternFill("solid", start_color=C_GREEN_DONE, fgColor=C_GREEN_DONE),
font=Font(name="Arial", size=9, bold=True, color=C_WHITE))
)
ws.conditional_formatting.add(
status_range,
CellIsRule(operator="equal", formula=['"In Progress"'],
fill=PatternFill("solid", start_color=C_ORANGE_IP, fgColor=C_ORANGE_IP),
font=Font(name="Arial", size=9, bold=True, color=C_WHITE))
)
ws.conditional_formatting.add(
status_range,
CellIsRule(operator="equal", formula=['"Skipped"'],
fill=PatternFill("solid", start_color=C_RED_SKIP, fgColor=C_RED_SKIP),
font=Font(name="Arial", size=9, bold=True, color=C_WHITE))
)
# ── CONDITIONAL FORMATTING – Score % colour scale ────────────────────────────
score_range = f"I4:I{current_row - 1}"
ws.conditional_formatting.add(
score_range,
ColorScaleRule(start_type="num", start_value=0, start_color="FF4444",
mid_type="num", mid_value=0.6, mid_color="FFA500",
end_type="num", end_value=1, end_color="70AD47")
)
# ── SUMMARY STATS (below tracker) ────────────────────────────────────────────
total_blocks = current_row - 4
summary_row = current_row + 2
ws.merge_cells(f"A{summary_row}:K{summary_row}")
s_hdr = ws.cell(row=summary_row, column=1, value="PROGRESS SUMMARY")
s_hdr.font = Font(name="Arial", size=11, bold=True, color=C_WHITE)
s_hdr.fill = fill(C_DARK_BLUE)
s_hdr.alignment = center()
ws.row_dimensions[summary_row].height = 24
labels = [
("Total Blocks", f"={total_blocks}"),
("Completed (Done ✓)", f'=COUNTIF(F4:F{current_row-1},"Done ✓")'),
("In Progress", f'=COUNTIF(F4:F{current_row-1},"In Progress")'),
("Skipped", f'=COUNTIF(F4:F{current_row-1},"Skipped")'),
("Not Started", f'=COUNTBLANK(F4:F{current_row-1})'),
("% Complete", f'=COUNTIF(F4:F{current_row-1},"Done ✓")/{total_blocks}'),
("Total MCQs Done", f"=SUM(G4:G{current_row-1})"),
("Total MCQs Correct", f"=SUM(H4:H{current_row-1})"),
("Overall MCQ Score", f'=IFERROR(SUM(H4:H{current_row-1})/SUM(G4:G{current_row-1}),"")'),
("Osmosis Videos", f'=COUNTIF(J4:J{current_row-1},"Yes ✓")'),
]
label_colors = [C_MED_BLUE, C_GREEN_DONE, C_ORANGE_IP, "FF4444", C_GREY_HDR,
C_MED_BLUE, "5B9BD5", "5B9BD5", "5B9BD5", "70AD47"]
pct_rows = {6, 9} # 1-indexed in labels list
for i, (label, formula) in enumerate(labels):
r = summary_row + 1 + i
ws.row_dimensions[r].height = 20
lbl_cell = ws.cell(row=r, column=1, value=label)
lbl_cell.font = Font(name="Arial", size=10, bold=True,
color=C_WHITE if label_colors[i] not in (C_GREY_HDR,) else C_DARK_TEXT)
lbl_cell.fill = fill(label_colors[i])
lbl_cell.alignment = left(wrap=False)
lbl_cell.border = thin_border()
val_cell = ws.cell(row=r, column=2, value=formula)
val_cell.font = Font(name="Arial", size=10, bold=True, color=C_DARK_TEXT)
val_cell.alignment = center()
val_cell.border = thin_border()
if i + 1 in pct_rows:
val_cell.number_format = "0.0%"
ws.merge_cells(f"C{r}:K{r}")
ws.cell(row=r, column=3).border = thin_border()
# ═══════════════════════════════════════════════════════════════════════════════
# SHEET 2 – MCQ LOG
# ═══════════════════════════════════════════════════════════════════════════════
ws2 = wb.create_sheet("MCQ Error Log")
ws2.sheet_view.showGridLines = False
ws2.freeze_panes = "A3"
ws2.merge_cells("A1:G1")
t = ws2["A1"]
t.value = "DAILY MCQ ERROR LOG – Write every wrong answer here"
t.font = Font(name="Arial", size=12, bold=True, color=C_WHITE)
t.fill = fill(C_DARK_BLUE)
t.alignment = center()
ws2.row_dimensions[1].height = 28
mcq_headers = ["Day", "Date", "Subject", "Question Summary", "Your Answer", "Correct Answer", "Why I Was Wrong / Key Concept"]
mcq_widths = [5, 12, 14, 42, 20, 20, 50]
for col_idx, (hdr, width) in enumerate(zip(mcq_headers, mcq_widths), start=1):
cell = ws2.cell(row=2, column=col_idx, value=hdr)
cell.font = hdr_font(sz=10)
cell.fill = fill(C_MED_BLUE)
cell.alignment = center(wrap=True)
cell.border = thin_border()
ws2.column_dimensions[get_column_letter(col_idx)].width = width
ws2.row_dimensions[2].height = 26
# Pre-populate 40 empty rows
for r in range(3, 43):
for c in range(1, 8):
cell = ws2.cell(row=r, column=c, value="")
cell.font = body_font(sz=9)
cell.fill = fill(C_WHITE) if r % 2 != 0 else fill(C_GREY_HDR)
cell.alignment = left(wrap=True)
cell.border = thin_border()
ws2.row_dimensions[r].height = 20
# ═══════════════════════════════════════════════════════════════════════════════
# SHEET 3 – POMODORO DAILY SCHEDULE
# ═══════════════════════════════════════════════════════════════════════════════
ws3 = wb.create_sheet("Pomodoro Schedule")
ws3.sheet_view.showGridLines = False
ws3.merge_cells("A1:E1")
pt = ws3["A1"]
pt.value = "DAILY POMODORO SCHEDULE (9:00 AM – 7:30 PM)"
pt.font = Font(name="Arial", size=12, bold=True, color=C_WHITE)
pt.fill = fill(C_DARK_BLUE)
pt.alignment = center()
ws3.row_dimensions[1].height = 28
pom_headers = ["Time", "Block", "Duration", "Activity", "Notes"]
pom_widths = [18, 20, 14, 40, 30]
for col_idx, (hdr, width) in enumerate(zip(pom_headers, pom_widths), start=1):
cell = ws3.cell(row=2, column=col_idx, value=hdr)
cell.font = hdr_font(sz=10)
cell.fill = fill(C_MED_BLUE)
cell.alignment = center()
cell.border = thin_border()
ws3.column_dimensions[get_column_letter(col_idx)].width = width
ws3.row_dimensions[2].height = 24
pom_rows = [
("9:00 – 9:25", "Pomodoro 1", "25 min", "New topic – reading (Step-Up)", ""),
("9:25 – 9:30", "Break 1", "5 min", "Rest / water", ""),
("9:30 – 9:55", "Pomodoro 2", "25 min", "Continue reading + make notes", ""),
("9:55 – 10:00", "Break 2", "5 min", "Rest", ""),
("10:00 – 10:25", "Pomodoro 3", "25 min", "Continue topic / start next sub-topic",""),
("10:25 – 10:30", "Break 3", "5 min", "Rest", ""),
("10:30 – 10:55", "Pomodoro 4", "25 min", "Summary table / flash notes", ""),
("10:55 – 11:25", "LONG BREAK", "30 min", "Stretch, snack, prayer", ""),
("11:25 – 11:50", "Pomodoro 5", "25 min", "Continue topic / Block 2 reading", ""),
("11:50 – 11:55", "Break 5", "5 min", "Rest", ""),
("11:55 – 12:20", "Pomodoro 6", "25 min", "Block 2 reading continued", ""),
("12:20 – 12:25", "Break 6", "5 min", "Rest", ""),
("12:25 – 12:50", "Pomodoro 7", "25 min", "Finalize topic notes", ""),
("12:50 – 12:55", "Break 7", "5 min", "Rest", ""),
("12:55 – 1:20", "Pomodoro 8", "25 min", "Quick review of morning reading", ""),
("1:20 – 1:50", "LONG BREAK", "30 min", "Zuhr + lunch", ""),
("1:50 – 2:15", "Pomodoro 9", "25 min", "MCQ Practice (past UHS) – 10 MCQs", ""),
("2:15 – 2:20", "Break 9", "5 min", "Rest", ""),
("2:20 – 2:45", "Pomodoro 10", "25 min", "MCQ Practice – 10 more MCQs", ""),
("2:45 – 2:50", "Break 10", "5 min", "Rest", ""),
("2:50 – 3:15", "Pomodoro 11", "25 min", "MCQ answer review – mark errors", ""),
("3:15 – 3:20", "Break 11", "5 min", "Rest", ""),
("3:20 – 3:45", "Pomodoro 12", "25 min", "MCQ error log – write in Sheet 2", ""),
("3:45 – 4:15", "LONG BREAK", "30 min", "Asr + rest", ""),
("4:15 – 4:40", "Pomodoro 13", "25 min", "Osmosis video – today's topic", ""),
("4:40 – 4:45", "Break 13", "5 min", "Rest", ""),
("4:45 – 5:10", "Pomodoro 14", "25 min", "Osmosis video continued / notes", ""),
("5:10 – 5:30", "Maghrib", "20 min", "Prayer + tea break", ""),
("5:30 – 5:55", "Pomodoro 15", "25 min", "Active recall – close notes, write summary",""),
("5:55 – 6:00", "Break 15", "5 min", "Rest", ""),
("6:00 – 6:25", "Pomodoro 16", "25 min", "MCQ mistake review + spaced repetition",""),
("6:25 – 7:00", "Dinner/Isha", "35 min", "Dinner + prayer", ""),
("7:00 – 7:25", "Final 25min", "25 min", "Next day preview (headings only)", ""),
("7:25 – 7:30", "DONE", "5 min", "Shut books. Rest. Sleep by 11 PM.", ""),
]
pom_break_color = "FCE4D6"
pom_study_color = "D9E8F5"
pom_long_break = "FFE699"
pom_prayer = "E2EFDA"
for r_idx, row_data in enumerate(pom_rows, start=3):
time_str, block, dur, activity, note = row_data
if "LONG" in block or "LONG" in activity:
bg = pom_long_break
elif "Break" in block or "DONE" in block:
bg = pom_break_color
elif "Maghrib" in block or "Zuhr" in block or "Isha" in block or "Dinner" in block:
bg = pom_prayer
else:
bg = pom_study_color
row_vals = [time_str, block, dur, activity, note]
for c_idx, val in enumerate(row_vals, start=1):
cell = ws3.cell(row=r_idx, column=c_idx, value=val)
cell.font = body_font(sz=9, bold=("LONG" in block or "DONE" in block))
cell.fill = fill(bg)
cell.alignment = center() if c_idx in [1, 2, 3] else left(wrap=False)
cell.border = thin_border()
ws3.row_dimensions[r_idx].height = 18
# ═══════════════════════════════════════════════════════════════════════════════
# SHEET 4 – SUMMARY TABLES (Cheat Sheet)
# ═══════════════════════════════════════════════════════════════════════════════
ws4 = wb.create_sheet("Quick Reference Tables")
ws4.sheet_view.showGridLines = False
ws4.merge_cells("A1:F1")
qt = ws4["A1"]
qt.value = "HIGH-YIELD QUICK REFERENCE TABLES – UHS MCQ Cheat Sheet"
qt.font = Font(name="Arial", size=12, bold=True, color=C_WHITE)
qt.fill = fill(C_DARK_BLUE)
qt.alignment = center()
ws4.row_dimensions[1].height = 28
tables = [
{
"title": "TABLE 1: Heart Failure – HFrEF vs HFpEF",
"headers": ["Feature", "HFrEF (Systolic)", "HFpEF (Diastolic)"],
"rows": [
["LVEF", "< 40%", "> 50%"],
["Main cause", "IHD, Dilated CM", "Hypertension, HCM, Age"],
["Mortality-reducing drugs", "BB + ACEi + MRA (spironolactone)", "None proven"],
["Symptom relief", "Furosemide (loop diuretic)", "Furosemide (loop diuretic)"],
["CXR finding", "Cardiomegaly + pulmonary oedema", "May be normal size"],
["UHS hint", "Drug reducing mortality = Beta blocker", "Treat underlying cause"],
],
"color": "D9E8F5"
},
{
"title": "TABLE 2: COPD vs Asthma",
"headers": ["Feature", "COPD", "Asthma"],
"rows": [
["Age", "> 40 yrs", "Any age (often young)"],
["Cause", "Smoking (almost always)", "Allergy, atopy"],
["FEV1/FVC", "< 0.7 – irreversible", "< 0.7 – reversible"],
["Eosinophils", "Uncommon", "Common"],
["Steroid response", "Partial", "Excellent"],
["Key drug", "LAMA + LABA", "ICS + SABA"],
["UHS hint", "FEV1 < 50% + exacerbations = add ICS", "Stepwise: SABA → ICS → LABA"],
],
"color": "E2EFDA"
},
{
"title": "TABLE 3: Congenital Heart Disease",
"headers": ["Disease", "Shunt", "Cyanotic?", "Key Feature", "UHS Hint"],
"rows": [
["VSD", "L→R", "No*", "Pansystolic murmur, LLSB", "*Eisenmenger: reverses to R→L"],
["ASD", "L→R", "No", "Fixed split S2", "May present in adults"],
["PDA", "L→R", "No", "Continuous machinery murmur", "Indomethacin closes it"],
["TOF", "R→L", "YES", "Squatting relieves cyanosis", "Boot-shaped heart on CXR"],
["TGA", "R→L", "YES from birth", "Egg-on-side CXR", "PGE1 keeps PDA open"],
],
"color": "FFF2CC"
},
{
"title": "TABLE 4: Antipsychotic Drugs",
"headers": ["Drug", "Type", "Key Side Effect", "UHS Hint"],
"rows": [
["Haloperidol", "Typical (1st gen)", "EPS (dystonia, akathisia, TD), NMS", "Most common cause of NMS"],
["Chlorpromazine", "Typical", "Sedation, postural hypotension", "Anti-emetic too"],
["Risperidone", "Atypical (2nd gen)", "Metabolic syndrome, raised prolactin", ""],
["Clozapine", "Atypical", "AGRANULOCYTOSIS – monitor WBC weekly", "Only for treatment-resistant"],
["Olanzapine", "Atypical", "Weight gain, glucose intolerance", ""],
["NMS features", "-", "Hyperthermia + Rigidity + AMS + Autonomic instability", "Stop drug, give dantrolene"],
],
"color": "FCE4D6"
},
{
"title": "TABLE 5: Developmental Milestones",
"headers": ["Age", "Gross Motor", "Fine Motor", "Social / Language"],
"rows": [
["2 months", "Lifts head prone", "—", "Social smile, coos"],
["6 months", "Sits with support", "Palmar grasp", "Babbles, stranger anxiety"],
["9 months", "Pulls to stand", "Pincer grasp developing", "Mama/dada (non-specific)"],
["12 months", "Walks with support", "Neat pincer grasp", "1-2 words, waves bye-bye"],
["18 months", "Walks alone, runs", "Scribbles, tower of 4", "10-20 words, parallel play"],
["2 years", "Runs, kicks ball", "Tower of 6, copies line", "2-word phrases"],
["3 years", "Rides tricycle", "Copies circle", "Full sentences, group play"],
["UHS hint", "Absence of social smile by 3 months = RED FLAG", "", ""],
],
"color": "E2EFDA"
},
]
current_r = 3
ws4.column_dimensions["A"].width = 22
ws4.column_dimensions["B"].width = 28
ws4.column_dimensions["C"].width = 28
ws4.column_dimensions["D"].width = 30
ws4.column_dimensions["E"].width = 28
ws4.column_dimensions["F"].width = 30
for tbl in tables:
# Table title
n_cols = len(tbl["headers"])
last_col = get_column_letter(n_cols)
ws4.merge_cells(f"A{current_r}:{last_col}{current_r}")
title_cell = ws4.cell(row=current_r, column=1, value=tbl["title"])
title_cell.font = Font(name="Arial", size=10, bold=True, color=C_WHITE)
title_cell.fill = fill(C_MED_BLUE)
title_cell.alignment = left(wrap=False)
title_cell.border = thin_border()
ws4.row_dimensions[current_r].height = 22
current_r += 1
# Headers
for c_idx, hdr in enumerate(tbl["headers"], start=1):
cell = ws4.cell(row=current_r, column=c_idx, value=hdr)
cell.font = Font(name="Arial", size=9, bold=True, color=C_WHITE)
cell.fill = fill(C_DARK_BLUE)
cell.alignment = center(wrap=True)
cell.border = thin_border()
ws4.row_dimensions[current_r].height = 20
current_r += 1
# Data rows
for r_idx, row_data in enumerate(tbl["rows"]):
for c_idx, val in enumerate(row_data, start=1):
cell = ws4.cell(row=current_r, column=c_idx, value=val)
cell.font = Font(name="Arial", size=9,
bold=(c_idx == 1),
color=C_DARK_TEXT)
cell.fill = fill(tbl["color"]) if r_idx % 2 == 0 else fill(C_WHITE)
cell.alignment = left(wrap=True)
cell.border = thin_border()
ws4.row_dimensions[current_r].height = 20
current_r += 1
current_r += 1 # gap between tables
# ═══════════════════════════════════════════════════════════════════════════════
# SAVE
# ═══════════════════════════════════════════════════════════════════════════════
output_path = "/home/daytona/workspace/uhs-tracker/UHS_Daily_Progress_Tracker.xlsx"
wb.save(output_path)
print(f"Saved: {output_path}")