Time Tracker — role-scoped timesheets & approvals
An internal platform that replaced a spreadsheet-and-email approval chain with three role-scoped interfaces on a single async FastAPI service — with authorization, conflict detection and an audit trail built in rather than bolted on.
At a glance
The problem
Time was tracked in spreadsheets, submitted by email, approved by reply, and reconciled by hand at month end. Every step was survivable on its own; together they produced a predictable set of failures — entries submitted against closed periods, approvals lost in inboxes, overlapping entries nobody caught until invoicing, and no reliable answer to "who approved this, and when?"
In a clinical-services context that last question is not a nice-to-have. The system had to be able to reconstruct who changed what, when, and under whose authority, months later.
Constraints I designed against
- Three audiences, one truth. Developers, managers and admins need different screens over the same records — without three divergent implementations.
- Auditability. Every state transition needs an immutable record: actor, timestamp, before/after.
- Org chart changes constantly. Approval routing must follow hierarchy data, not hardcoded reporting lines.
- Adoption is the real risk. If entering time is slower than the spreadsheet, people quietly keep using the spreadsheet.
Architecture
Highlighted nodes are the parts that took the most design iteration.
Decisions & trade-offs
Authorization as a dependency, not a decorator soup
Approval routing from hierarchy data
Append-only audit log
Async FastAPI over a sync framework
Interfaces







Where it landed
- Approval turnaround down 60% and administrative errors down 75% once submission, routing and conflict detection stopped being manual steps.
- Roughly 20 hours of manual work removed per week across the organization.
- 92% satisfaction in post-implementation surveys across all three roles.
- ~15% better resource allocation, driven by reports that were previously assembled by hand at month end.
- Audit-grade history — every approval reconstructable from the event log, without archaeology.
What I'd do differently
Model the workflow as an explicit state machine from day one. The rules grew organically and ended up spread across the service layer. A declared set of states and legal transitions would have made conflict detection and escalation fall out of the model instead of being written twice.
Ship the reporting layer earlier. Reporting was scheduled last, but it was the feature that convinced managers the system was worth the switch. Adoption evidence arrives late when the persuasive feature ships late.
Load-test the async paths before the pilot, not after. The bugs that matter in an async service appear under concurrency, and a pilot group is too small to surface them.