Giant Reports by Brobd Solutions · 2026 · Product Engineer
Canteen management
A payment log rebuilt as a metered utility after the founder brought a better model than mine.
The problem
Ghanaian day schools feed children at a canteen and take small cash sums every day, some paid ahead weekly or termly. It is the most cash-intensive money in the school and the least recorded: an exercise book, a cash tin, and a reconciliation done from memory. The founder put it plainly. Schools get cheated at the canteen. Collectors under-remit, kitchen staff under-serve and take stock home, and because nobody can say how much cash should exist at the end of a day, nobody can say how much is missing.
I built the first version as a payment log, and it is the clearest illustration of the problem because it encoded it. The collector sets an amount at the top of the screen, taps Daily or Weekly against each child, and publishes. The number is typed by a person, the class roster is the denominator, and a child who is absent is indistinguishable from a child who owes money.
- 01
The price of a meal depended on who was collecting
Rates lived in a text field at the top of the collection screen. Whatever was typed became the charge, so the price of a meal depended on who was collecting and what they remembered.
- 02
The class roster was the wrong denominator
Collection was measured against everyone on the roll, so absent children, exempt children and children who brought lunch from home all read as debtors. Collection rates looked worse than they were and arrears accrued against families who owed nothing.
- 03
Unpaid meant four different things
A single status column collapsed did not eat, has not paid yet, is exempt, and is covered by a termly invoice into one word. The collector could not tell which rows needed action.
- 04
A gap in the cash had nowhere to live
When a class handed over less than the records said it took, either the day refused to close and stranded a whole school on one child's lost coin, or the difference quietly vanished.
Design thinking
Before any code, the thinking starts with brainstorming the problem space: who uses this, what can go wrong, what edge cases exist when money and attendance intersect. Every scenario that matters gets talked through, from a child arriving late to a guardian opting out mid-term to a collector who miscounts cash.
From there I build a canvas, a scratchpad for mapping out every screen and user flow before committing to code. Flows get rearranged and thrown away until the model holds.
Only once the canvas proves the logic does building in code start.
What changed
Before
Version one. An Amounts bar the collector fills in before anything can be recorded, one Status column where every child reads Unpaid, and Recorded 0 of 8 measured against the whole class.
After
The rebuild. Rates read-only from policy, Expected to eat 44 with its derivation printed underneath, and Recorded 5 of 41 measured against those who can still be charged.
The daily loop
Collection is a routine done under time pressure with a queue of children waiting, so the whole design is judged on whether it survives being done quickly.
- 01
Land on the collector home
Cash taken today, money paid in the app, and students recorded, then every class as a card with its own running total. The first question a collector has is which class am I doing, so that is the whole screen.
- 02
Open a class
The roster, with the derivation at the top and every child already carrying a resolved status and a settlement method. Nothing here waits on the collector to work anything out, and a child on a wallet is asked only whether they ate.
- 03
Record an outcome that is not a plain payment
Daily, weekly and termly sit on the row itself. This modal is only for the outcomes that need an amount or a reason: part payment, credit, exempt, opt out. The product states the version one difference in its own words.
- 04
Count the cash at day's end
Handover is per class, and the system already knows what should be there. The bursar enters what actually is.
- 05
Come up short
A variance needs a reason before the day can close. Classes that have not been counted stay open rather than blocking everyone else.
Iterations made from feedback
The founder came to the demo two and a half weeks in with a sixteen-screen prototype of his own and a different mental model, and he was right. Canteen is a metered utility, not a payment log. One engine answers a single question per child per day: is this a chargeable meal? It resolves through the calendar, then school policy, then participation, then attendance. Cadence stops deciding whether a meal is chargeable and only decides when it settles.
The load-bearing decision inside the rebuild is that eligibility and recording are separate acts. The system resolves what should happen today. A person records what actually happened. Nothing ever settles itself, so there is no auto-deduction from a wallet and an attendance mark never counts as a payment.
The second is two separate ledgers. Feeding money never blends with school fees, so a child can be square on lunch and behind on tuition and both read correctly, and lunch arrears roll into next term as their own bill rather than disappearing into the fees invoice.
- 1
Rates, read-only, from policy
Why
In version one this was an editable field at the top of the collection screen. Money should not be definable at the counter, so it moved to policy and comes back as a fact with a route to change it in the right place.
What it changed
The price of a meal stopped depending on who was collecting. The collection screen only records.
- 2
Expected to eat, and its arithmetic
Why
The whole product in one number. Printing the derivation underneath, roll minus absent minus exempt minus opted out, means nobody has to trust it. Every input is data the platform already holds.
What it changed
Fake arrears stopped accruing against families who owed nothing, and the kitchen, the collector and the ledger all read the same figure.
- 3
One status column, two kinds of truth
Why
System-resolved outcomes like absent and exempt sit in the same column as recorded ones like paid by cash. Splitting them into two columns tested worse: the collector reads down one column asking what still needs me.
What it changed
Unpaid stopped meaning four different things. A row now says whether it needs a person, and if not, why not.
- 4
Arrears, kept apart from today
Why
What someone owes from previous days is a different question from what is due now, and merging them is how a collector ends up taking arrears money at the counter, which this phase deliberately does not do.
What it changed
Today stays a clean decision. Arrears are visible without becoming something to collect on the spot.
- 5
Actions only where a decision is owed
Why
Four buttons with no primary among them, because cadence is genuinely unpredictable per child and per day. They appear only on rows still awaiting a decision.
What it changed
The eye goes to the rows that need work. A resolved row offers nothing to press, so there is nothing to press by mistake.
Tradeoffs
I threw away two and a half weeks of work in one meeting. That was only affordable because of how it was built, which is the point of the method rather than an aside.
Frictionless adoption went deliberately. Collection is impossible until the attendance register is marked, which means the canteen now depends on a completely different part of the school system. The founder wanted exactly that: if the school's cash depends on attendance being marked, supervision arrives on its own. It also means a teacher who has not marked the register can stop the canteen collecting, so that screen had to do more than refuse. It names the blocker, says who clears it, and offers a one-tap route to fix it.
Hard guarantees went too, in favour of reason and proceed. A short handover closes with a named reason instead of blocking, which accepts that some gaps get explained and never chased, in exchange for days that always close.
And the refund button went entirely, once it was clear refunds are a rare manual accounting event rather than a product flow.
Outcomes
Cash integrity and the platform leverage built on top of it landed together, because they are input and output of the same thing. Policy layering across school, grade level and student, collection by class and by search, cash handover with counting and reconciliation, the feeding ledger with a shortage holding account, arrears with guardian reminders, the audit log, and wallets with mobile money top-ups and shared family pots.
The metered model is now the direction for canteen across the product, and two new roles, collector and canteen supervisor, entered the permission model because of it.
Every pain point, answered
The price of a meal depended on who was collecting
Rates lived in a text field at the top of the collection screen. Whatever was typed became the charge, so the price of a meal depended on who was collecting and what they remembered.
- Rates moved to policy and render read-only on the roster with a link to edit them where they belong. The collection screen stopped being a place where money is defined and became a place where it is recorded.
The class roster was the wrong denominator
Collection was measured against everyone on the roll, so absent children, exempt children and children who brought lunch from home all read as debtors. Collection rates looked worse than they were and arrears accrued against families who owed nothing.
- The lead statistic became expected to eat, derived live as roll minus absent minus exempt minus opted out, with the arithmetic printed underneath it. Nobody types that number and nobody can.
Unpaid meant four different things
A single status column collapsed did not eat, has not paid yet, is exempt, and is covered by a termly invoice into one word. The collector could not tell which rows needed action.
- Status became system-resolved and settlement-typed, so paid by wallet, paid by cash, paid in the parents' app, exempt on health grounds, absent from the register, opted out and on the school bill are all distinct. Only rows genuinely awaiting a decision carry buttons.
A gap in the cash had nowhere to live
When a class handed over less than the records said it took, either the day refused to close and stranded a whole school on one child's lost coin, or the difference quietly vanished.
- Variance needs a named reason before the day closes, and the unresolved amount then sits in a shortage holding account, visible and outstanding, until an outcome accounts for it. Explained is not resolved.
The handover
A pull request is raised with the full prototype and detailed documentation covering the entire feature. Every screen, every state, every edge case is documented so a developer picking it up has no ambiguity about what was built or why.
The docs spell out limitations, what the prototype fakes versus what comes from real endpoints, and where the boundaries are between frontend state and backend responsibility. Open questions are listed plainly rather than guessed at, and next steps are scoped so the backend team knows exactly what to build first.
How it was built
This is the fullest expression of how I work. I ran the entire feature, product thinking and code, assisted daily by an agentic workflow, on a method I think of as design in code: the codebase is the design tool, a disposable branch is the artboard, and the output is a production ready prototype that communicates design intent to engineers.
On the playground branch, every surface carries a floating toolbar of state knobs, so any state, empty or partial or overdrawn or offline or six weeks into a term, is one click away without completing a flow. That is how I quickly iterate and accelerate my workflow and output. When a feature is ready, work is ported to a clean branch, productionized, and raised as a PR.
The one-day first version and the five-day rebuild are the proof. When building is that cheap, being overruled after a demo is a normal Tuesday, and the second version is better precisely because the first one existed to argue with.
Thank you for reading all the way through. I appreciate the time. If this sounds like the kind of work you need, do get in touch. I’d love to hear what you’re building.