03 — Case study
SRU Timetable
My university’s timetable, finally readable.

The problem
The official timetable is built for the registrar: every batch, elective and room in one table. Finding out when my next class is, or when I am actually free today, meant squinting at a spreadsheet.
The first version was a tool that read the Excel export and drew a cleaner week. It worked, but you still had to find the right file every time. The rebuild removes that step. You choose your degree, year and batch and the timetable loads. Excel upload stays as a fallback.
What it does
My week as a real grid
Each class gets room to breathe instead of being squeezed into a spreadsheet cell.
Free Time
The same grid flipped to show your gaps: how many free slots, the longest break, day by day.
Compare batches
Pick two batches and see the hours when both are free.
Attendance calculator
A quick check for one course (how many classes can I skip?) or a tracker for the whole term.
Exports that are complete
PNG, PDF or HTML, always the full schedule and never a cropped screenshot.
Try it
The live app loads your real batch. This recreation uses made-up data to show how the Free Time view finds your gaps.
| Time | Mon | Tue | Wed | Thu | Fri |
|---|---|---|---|---|---|
| 09:30 | DBMSR-204 | DAAR-301 | OSR-110 | DBMSR-204 | free |
| 10:30 | DBMSR-204 | free | CNR-204 | DAAR-301 | DBMSR-204 |
| 11:30 | free | PYLLab 2 | free | free | ELECR-402 |
| 12:30 | OSR-110 | PYLLab 2 | free | OSR-110 | ELECR-402 |
| 13:30 | DAAR-301 | PYLLab 2 | DAAR-301 | free | free |
| 14:30 | free | CNR-204 | free | CNR-204 | free |
L lecture · T tutorial · P lab · Sample data, not a real batch
How it works
The browser only ever talks to my backend. The backend fetches the public timetable data, reshapes it, caches it briefly and throws it away. Nothing is stored and nothing is scraped in bulk.
- 01BrowserReact 19 · TypeScript
- 02My backendFastAPI · reshape · short cache
- 03University timetable pagePublic source of truth
An uploaded Excel file goes through the same pipeline, so the front end never knows which source it came from.
{
"batch": "23CSBTB35",
"timeSlots": ["09:30", "10:30", "..."],
"days": ["Monday", "Tuesday", "..."],
"cells": {
"Monday": {
"09:30": { "value": "DBMS (L)\nRoom 204", "isClass": true, "type": "L" }
}
}
}Engineering decisions
- 01
Two sources, one shape
Live data and Excel files look nothing alike. I reduce both to a single structure, so the grid, the free-time view, comparison and export are written once.
- 02
The iOS export bug that taught me something
PNG export came out blank on iPhones. A blob URL does not carry over to the tab Safari opens it in. I rewrote the export path instead of patching around it.
- 03
A health check that lied
The app kept saying the service was down. My own health endpoint was rejecting HEAD requests. A small bug, but a false “down” banner costs trust.
- 04
Free hosting sleeps
The backend runs on a free tier that goes to sleep, so I added a warm-up ping. The first request no longer feels broken.
- 05
Tests that do not need the internet
Vitest covers the free-time maths and the interface. pytest mocks the university requests, so every test runs offline.
Stack
- React 19
- TypeScript
- Vite
- Tailwind CSS v4
- FastAPI
- Pydantic v2
- httpx
- openpyxl
- Vitest
- pytest
Worth knowing
An independent student project, not affiliated with SR University. For exams, room changes and deadlines, check the official page.
It depends on the university’s public timetable service. When that is down the app says so and offers the Excel upload instead.