Inspiration
Employers steal an estimated $15–50 billion a year from U.S. workers in unpaid wages which is more than all robberies, burglaries, and motor vehicle theft in the country combined. Fewer than 3% of those dollars are ever recovered.
The problem is that the only record of hours worked is controlled by the employer. If a worker believes they were underpaid, the paper trail that would prove it belongs entirely to the employer.
The idea for PayTrack came from a 1946 Supreme Court case, Anderson v. Mt. Clemens Pottery Co. The Court held that when an employer fails to keep the records required by the Fair Labor Standards Act, the worker only needs enough evidence to show the amount of work "as a matter of just and reasonable inference." At that point, the burden shifts to the employer to disprove it.
This means the worker's own record doesn't need to be unforgeable. It just needs to be reasonable and contemporaneous. That's a bar which software can actually clear.
What it does
PayTrack lets a worker build their own evidence trail, end to end:
- Log hours independently. Clock in and out (or add a shift retroactively), optionally tagged with GPS. The worker sets up their own workplace location, which is what makes the record theirs.
- Photograph a paystub. A vision-based AI model reads the printed figures (pay period, hours paid, hourly rate, gross pay) straight off the image and pre-fills a form. The worker checks every number against the paper in their hand before confirming it.
- See the gap, in dollars. A rule engine computes what the logged hours are actually worth under the applicable wage law (federal FLSA or California) and compares it against what the paystub says was paid. Overtime is calculated per workweek, exactly as the law requires. It's never averaged across a pay period, which is the single most common way this calculation goes wrong.
- Get it explained in plain English. A second AI model turns the computed numbers into a short, plain-language explanation a worker could read out loud to a labor commissioner. Critically, this model is never allowed to state a number of its own. Every figure it writes must trace back to the actual calculation, or the explanation is thrown out entirely.
- Flag paperwork violations. California law requires nine specific pieces of information on every itemized wage statement. PayTrack checks for all nine and flags what's missing, as a separate, independent issue from any unpaid wages.
- Export an evidence packet. A generated PDF bundles the hash-chained hour log, the paystub, the discrepancy calculation with statute citations, and integrity/notarization timestamps into one document.
- Know what to do next. A dedicated panel gives filing deadlines, the correct agency to contact, and a clear explanation of anti-retaliation protections, since fear of retaliation (not lack of evidence) is the biggest reason workers don't come forward.
How we built it
Stack: React + Vite frontend, Node/Express backend, PostgreSQL (managed by Render), Google's Gemini API for both AI features.
The ledger. The hours_log table has a database-level trigger that makes it refuse UPDATE and DELETE — even from the application's own credentials. Once an hour is logged, it cannot be silently edited later, by a bug or by anyone. Entries are hash-chained so any tampering is evident even if it were somehow forced through.
The rule engine. Before writing a single line of the actual overtime calculation, I hand-computed nine test scenarios by hand — federal and California, single weeks and biweekly periods, edge cases like a 13-hour day or a worker's 7th consecutive day. Two of those fixtures were deliberately designed to catch opposite failure modes: one where a naive implementation silently underpays the worker (by averaging hours across a pay period instead of calculating week-by-week), and one where a naive implementation overpays (by double-counting hours that were already promoted to overtime). Only after those answers existed on paper did I write the code meant to reproduce them.
The AI features. Two separate calls to Gemini, doing two very different jobs. The first reads a photographed paystub and extracts structured figures — vision input, JSON output, nothing more. The second takes the already-computed analysis and writes a plain-English explanation of it. That second model is deliberately never given the ability to do its own math: every response is checked, word by word, for any number that doesn't trace back to the real calculation, and a response that fails this check is discarded outright rather than shown with a warning. An AI-written explanation that disagrees with the numbers on screen is worse than no explanation at all, because a worker holding two different figures for the same pay period has a record nobody can use.
Architecture on Render. Two Render services — a static frontend and an Express API — plus one managed Postgres database, all on the free tier. The API is the only place the Gemini key is ever held, which is the whole reason a backend exists at all rather than calling the AI directly from the browser. Interestingly, the two AI features run on two different Gemini models on purpose: Google's free tier limits requests per model per day, not per API key, so splitting them means heavy use of one feature (like re-testing the explainer) can never accidentally exhaust the quota the other feature (photo extraction, which has no fallback) depends on.
Process. Before building any individual feature, I wrote a short spec describing exactly what it should take in, what it should produce, and what rules it had to follow. That let pieces be built and tested independently while staying confident they'd fit together correctly later.
Challenges we ran into
Overtime math is easy to get wrong. The two fixture "traps" mentioned above weren't hypothetical. My early implementations really did fail them. A biweekly pay period of 45 hours one week and 35 the next averages to exactly 40 hours a week, which a naive calculation reads as zero overtime. This quietly shorts the worker by real money. Conversely, California's rule that daily and weekly overtime can't be double-counted (called "non-pyramiding") is easy to violate by accident, which overstates what's owed. Both failure modes produce a plausible-looking wrong number, which is exactly the kind of bug that's dangerous in a tool meant to produce evidence.
Making an AI feature that can't lie about numbers. The naive version of "validate every number in the AI's explanation against the real data" has a subtle hole: the validator has to allow some bare numbers through (like "8 hours" or "1.5x" for stating overtime rules), but if it's too permissive, a rounding constant like "40" (the weekly overtime threshold) can accidentally license the AI to write "you are owed $40" which is a real invented dollar figure disguised as a legitimate statutory reference. The fix was treating dollar amounts as a stricter category than plain numbers, checked against an explicit list of the actual money fields in the calculation, not just any number that appears anywhere in the data.
A UI that had grown too tall. After several rounds of adding features to the main results screen, it had grown to over 8,000 pixels of scrolling which is more than 11 full screens on a typical laptop. The fix was splitting the page into two views by purpose rather than by section: "Your result" (the figures) and "What to do next" (filing, deadlines, protections), which a worker only needs once they're ready to act.
Real-world photo extraction. Late in the build, I tested the photo-reading feature against a simulated real phone photo, one that was rotated, resting on a desk, with uneven lighting, rather than a perfect clean scan. It correctly read the dates, hours, and gross pay, and it correctly refused to guess a rate that genuinely wasn't printed on the stub, rather than inventing one.
Switching AI providers mid-build. To avoid unexpected billing, I moved both AI features from one provider to another partway through. Because the AI-calling code was written behind a small internal interface from the start, this ended up being a single-file change rather than a rewrite.
Accomplishments that we're proud of
- A tool that never invents a number. Every dollar figure traces back to something computed or typed in.
- Nine hand-verified test fixtures, two built specifically to catch opposite-direction bugs before they could reach a worker.
- A ledger that can't be quietly edited, enforced at the database level.
- The AI explainer can't contradict its own numbers. A wrong explanation gets rejected.
- Tested end-to-end on a realistic simulated paystub photo.
What we learned
- The actual mechanics of wage law: overtime calculated strictly per workweek, California's non-pyramiding rule, meal-and-rest-break premiums, and the nine required elements of a compliant wage statement.
- How to design an AI feature for a domain where being wrong is worse than being unavailable*, and that this means building real validation and rejection paths.
- That LLM API rate limits can be scoped per model rather than per key, which changes how you should architect multiple AI features in the same app.
- That measuring a UI problem (literally counting pixels of scroll) leads to a much better fix than guessing at what "feels long."
- That the best test data isn't the cleanest test data. A slightly messy, rotated, unevenly-lit simulated photo told me far more about whether the extraction feature actually works than a perfect scan.
What's next for PayTrack
- More jurisdictions. Right now PayTrack supports federal FLSA and California law; extending the same rule-engine pattern to other states is the most direct way to help more workers.
- A background queue for bulk paystub processing, so a worker who's been shorted for months can backfill many pay periods at once instead of one at a time.
- Scheduled, cron-based chain notarization (currently a lightweight on-write fallback, since scheduled jobs require a paid hosting tier) for an even stronger, activity-independent tamper-evidence guarantee.
- Handling tips, commissions, and non-discretionary bonuses in the regular-rate calculation. Currently explicitly out of scope and flagged as such, since supporting them correctly changes the underlying math in ways that need the same fixtures-first rigor as the rest of the engine.
- Getting it in front of the people it's actually for Legal aid organizations and workers' rights centers who already talk to underpaid workers every day, to find out what's missing before it matters in a real case.
Built With
- claude
- javascript
- llm
- react
- render
Log in or sign up for Devpost to join the conversation.