The Overtime Calculator replaces a manual Excel workbook for personal overtime tracking. It handles time entry, PDF pay-slip generation, and reconciliation against NYC's ESS payroll system from a single self-hosted web app. The frontend is React 19 with Tailwind and shadcn/ui; the backend is FastAPI with async SQLAlchemy. Data is stored in PostgreSQL with SCD2 audit history so every change is versioned without any manual bookkeeping.
Security was a design constraint from the start, not an afterthought. PII is encrypted at rest using Vault Transit before it reaches the database, so the database never holds plaintext sensitive fields. Before any public exposure, a full white-box pentest was run. Two Critical findings came back: real personal data had been committed to the git repository and was being served unauthenticated, and a personal access token with push access to the main branch was in plaintext in the repository config. Both were remediated along with several High findings before the app went live. The full story is in the pentest post.
UAT runs on a separate subdomain with its own Clerk auth instance and its own database, isolated from production at every layer. The CI pipeline enforces mypy and ruff clean, runs the full pytest suite (300 tests), and gates deployment on both checks. Alembic manages schema migrations for both environments.
Architecture
The FastAPI backend organizes into three main layers: the data model (SQLAlchemy with Alembic migrations), the PDF generation layer (WeasyPrint), and the sync layer (ESS reconciliation). WeasyPrint runs entirely server-side, producing PDFs from the same data model used for reconciliation. Fonts are baked into the Docker image at build time so the container can run under a read-only root filesystem without WeasyPrint crashing on a missing fontconfig cache.
The ESS sync runs as a FastAPI lifespan background task on a Friday morning schedule. A per-process PostgreSQL advisory lock enforces singleton behavior across restarts and container rescheduling, so two instances of the app can never run the sync concurrently. If the sync fails for any reason, an ntfy notification fires immediately; a missed sync is never silent.
Authentication uses Clerk for both environments, with separate Clerk instances for UAT and production. This keeps test accounts and production accounts fully isolated at the auth layer, not just at the database layer.
The Docker image is built in a multi-stage pipeline. The final stage runs as a non-root user with a read-only root filesystem and a minimal set of tmpfs mounts for runtime state. The CI pipeline runs SAST and dependency scanning as separate jobs. Deployment to both UAT and production is SSH-based, gated on the full test and scan suite passing first.