05 Case study Private
Wiseprint
A print shop priced every job in the owner’s head. Wiseprint prices it the way he already does (paper, printing, finishing, margin), keeps every quote as a frozen record, and grew into the shop’s job board, intake and press queue. It’s in Greek, at the counter and on his phone.
01 Problem
A one-store print shop in Cyprus priced every job in the owner's head: paper, plus printing, plus finishing, plus a margin chosen on the spot. Phone enquiries went onto paper, or nowhere. Every job was set up on the press by hand, and reconstructing what a customer had been quoted meant remembering it.
The brief was simple: every job exists as one record, with a price, a cost floor and a status, so the owner never has to rebuild a job from memory. It had to be in Greek, at the counter PC and on his phone, and it had to price the way he already prices.
02 What I built
It started as a cost-plus calculator and grew, one small version at a time, into the shop's working tool:
- Quotes for business cards, books, invoice pads and 3D prints. Each shows how many items fit on a sheet, the cost floor, the price with and without VAT, and a quantity-break table.
- The job board. Customers, statuses, payments, stock, and a monthly report with an export the accountant can open.
- The counter. Jobs sent straight to the press (always held, never auto-printed), phone voicemails transcribed on the shop's own Mac, and 3D print sessions tracked one plate at a time.
- Operations. The shop Mac updates itself hourly from a release branch, backs up off-site every evening, and shows a health page with a warning banner.
03 Architecture
A pure pricing engine, about a thousand lines with no Django imports (a test enforces that), behind an adapter that reads the owner's rates. Every saved quote freezes its cost breakdown and a fingerprint of the rates it used, so changing a price tomorrow never rewrites what a customer was quoted yesterday. Django, SQLite and HTMX keep it small: no single-page app, and four runtime dependencies.
04 Key decisions
Decision 1
A pure engine, tested before it existed
- Context
- Pricing is the part the shop can't afford to get wrong, and the hardest to test through a web framework.
- Decision
- The engine takes plain data and returns plain data, with no Django anywhere, and a test enforces that. The first six of its 26 golden cases were worked out by hand before the engine was written, and each later batch arrived with the change it covers.
- Trade-off
- An adapter layer to maintain between the database and the engine.
Decision 2
A saved quote never changes
- Context
- Paper prices go up. A customer who was quoted last week should still see last week's number.
- Decision
- Every saved quote stores its full cost breakdown and a fingerprint of the rates it used, and is never recalculated. "Quote again" reprices at today's rates as a new quote.
- Trade-off
- Every reader must understand every shape a quote has ever had. Those shapes are frozen as test fixtures.
Decision 3
Money rounds once, at the end
- Context
- Rounding at every step quietly loses or invents cents. VAT on a rounded figure must match what the owner says out loud.
- Decision
- Decimal arithmetic throughout, rounded half-up only at line totals. Multiply first and divide last, and keep VAT as a setting, not a constant.
- Trade-off
- A "price per print" times the number of prints can differ from the total by a cent. That's documented, not hidden.
Decision 4
The tool warns; the owner decides
- Context
- It's the owner's shop and the owner's margin.
- Decision
- The tool never discounts on its own. Quantity breaks are suggestions, and a price below cost gets a clear warning, never a block.
- Trade-off
- It can't stop a bad deal. It makes sure nobody makes one by accident.
05 Hard problems
A price that landed exactly on half a cent
Dividing before multiplying turned a 10.00 into 9.99 when the true value sat exactly on a rounding tie. The fix was to divide last, plus a golden test case chosen deliberately to sit on a tie, so the bug can't come back quietly.
Printed quotes without the shop's name
For over twenty versions, the printed customer quote was missing the shop's header: a print-only style rule hid it, and every on-screen test passed. Now a test parses the print stylesheet itself.
Green alone, red together
Two features built in parallel each passed their own tests, then broke together after merging: buttons lost their styling, and a form swapped a whole page into one panel. The fix was a process one: name who owns shared styles and behaviour, and write the joining test before the parallel work starts.
06 Outcome & what I'd do differently
Wiseprint is installed and running on the shop's own Mac. The repo is private, and the shop's prices stay the shop's.
- Validate against real jobs first. The plan was to reprice five real past jobs within 10% before building further. Forty versions later, that check still hasn't run. It comes next.
- "Later" means later. Some features the owner had parked got pulled forward. Speed is easy; restraint is the skill.
- Don't cut review for speed. A few versions shipped with known findings still open, and each one cost more to fix afterwards than before.