Builds · Finance automation
Production Labor Report
A payroll PDF goes in. A live finance workbook comes out, with every formula still intact.
What it replaced
Twenty minutes of retyping, twenty-six times a year.
Every pay period the payroll system produces an Employee Labor Detail report: a PDF, dozens of lines, several locations. The production kitchen's labor has to end up in a workbook that tracks hours and dollars per person per period, so we can watch what it actually costs to make the product.
By hand that means reading the PDF, finding the production lines, adding up each person's regular and overtime, locating their row in four separate sections of the workbook, and typing four numbers per person into the right column. About twenty minutes when nothing is strange.
Now: drop the PDF in a folder, run one command, read what it says it did, and check that the total ties to the payroll report. The download and the tie-out are still manual, on purpose. That's where the judgment is.
How the workflow runs
Four stages. The first one and the last one are mine.
I hand it the report
Nobody automates the login, and choosing the period is a decision.Everything it knows comes from that one file and the workbook's own layout. No database, no history, no network.
It reads the PDF
Text layer in, numbers out. Most of the work is deciding what to ignore.What it keeps
What it throws away
It writes the workbook
Four cells per person, in one column, and nothing else touched.Regular plus overtime.
Wages for the period.
Hours and dollars, kept apart from worked time.
I tie it out
The control point. Nothing is accepted until this passes.Does the workbook total match the payroll report?
If it does, the period is done. If it doesn't, something new showed up, usually a person the automation has never seen. That's the check that catches the whole class of quietly-missing errors, and it's why I haven't put this on a schedule.
Watch it in action
A payroll PDF, an empty column, one command, a filled workbook.
Animated walkthrough · 1:27 Watch the workflow, start to finish The payroll report, the empty column, the script running, the workbook filling in, and the formula surviving. Narrated, with a scrubber and chapters. Open in a new tab →It opens on its own page because it needs the whole window. An illustration rather than a screen recording: every figure in it comes from the real run, and the software is drawn.
Below is the actual terminal output from that run, on generated data. Same numbers every time, nothing is random.
The three amber lines are the whole point. A manager deliberately excluded. An employee whose second role has no GL code, caught from a line the main parser structurally cannot see. And somebody with no row of their own, rolled into a catch-all instead of vanishing.
The demo builds its own inputs. It invents the payroll PDF and the workbook before it runs, so there's nothing real anywhere in the chain. pip install -r requirements.txt then py run_demo.py.
What landed in the workbook
Read back out of the saved file: column E, the period the PDF covered.
| Employee | Hours | Dollars | PTO hrs | |
|---|---|---|---|---|
| Alder, Marcus J | 64.53 | 1,411 | 0.00 | |
| Brandt, Simone | 71.60 | 1,274 | 8.00 | |
| Okafor, Danielle | 40.25 | 704 | 0.00 | |
| Okafor, Danielle (Ice Cream Maker) | 24.55 | 470 | 0.00 | split role |
| Reyes, Tomas | 61.50 | 1,133 | 0.00 | |
| Whitfield, Erin | 80.40 | 1,453 | 0.00 | |
| Castellano, Nico | 53.08 | 908 | 8.00 | |
| Park, Jae-Won | 68.38 | 1,190 | 0.00 | |
| Sandoval, Ruth A | 71.63 | 1,232 | 0.00 | |
| Novak, Priya | 61.48 | 1,028 | 0.00 | |
| Ferreira, Luz | 58.13 | 953 | 0.00 | |
| All Other | 12.75 | 191 | 0.00 | no row of their own |
| Total (ties to the payroll report) | 668.28 | 11,947 | 16.00 |
Where the AI is additive
Nothing at runtime. Everything in the building.
At runtime: nowhere. No model reads the PDF. No agent decides anything. It's deterministic Python and it should be. The layout is fixed and the work is arithmetic. Putting a language model in this loop would add cost, latency, and a way to be creatively wrong about somebody's wages.
In building it: entirely. I don't otherwise write Python. This exists because I could describe what I wanted, watch code appear, run it, watch it fail on a real edge case, and describe the fix. The AI's contribution isn't that the workflow is smart. It's that the workflow exists, built by the person who actually understood the accounting.
That's the pattern I'd point at for most finance work: the model helps you build the tool, the tool does the job.
Where the risks are
Five components, five dials. Where this one actually sits.
| Component | This build | Why |
|---|---|---|
| LLM | none | Fixed layout, arithmetic output. Nothing to reason about. |
| Tools | one file | Reads one PDF, writes one workbook. No network, no credentials, no database. |
| Context | one period | Knows the report in front of it and the workbook's own layout. No history, no access to anything else. |
| Autonomy | I approve | A human triggers it, a human accepts the result. It could run on a schedule. It doesn't, because the tie-out is the control. |
| Skills | prescribed | Fixed procedure, named exceptions in config. It doesn't adapt. It says so instead of improvising. |
The dangerous failure here was never a crash. It was a silent undercount. An employee's second role wasn't GL-mapped, so those hours appeared on a different kind of line entirely and the parser skipped them without a word. The report was just light, and it looked fine.
That's why the tie-out isn't ceremony, and why unmatched people roll into a catch-all instead of being dropped: the total has to keep tying out even when the automation doesn't recognize somebody. Blast radius is one column of one workbook. Clear it and run again.
What's worth stealing
Two decisions that matter more than the libraries.
Write cells, don't generate files. The workbook is alive. People open it, filter it, and have trusted its totals for years. So the automation writes individual cells and leaves every formula, format and other column exactly as it found them. Four of the five sections get written; the fifth is derived by Excel formulas and is deliberately never touched. Generating a fresh export would have been far easier to build, and nobody would use the output.
Match by name, not by position. The sections looked parallel: third row of hours, third row of dollars, same person. Then two people were reordered in one section only. The automation wrote each of their dollars onto the other's row and every total still came out right, which is the worst kind of wrong. Now each section is searched by name, and a name that can't be found falls back to position and warns. Fail loudly.
My notes
Not written yetThis is where I'll write about actually building it:
- What the first version looked like, and how long it took to work.
- The moment I realized hours were missing, and what I thought it was at first.
- What it feels like shipping something that touches a file other people rely on.
- What I'd tell a finance person building their first one.