Case Study — Auction House Operations

A spreadsheet knows
what. It never knows who, or when.

An auction house ran its inventory across spreadsheets, a shared photo folder and a group chat. Every piece of it worked, right up until two people disagreed about where a lot stood and nothing in the system could settle it. We built the system of record: intake to catalog to auction, with AI drafting the tedious part and a person approving all of it.

At A Glance ● in use
7 lifecycle stages, from
quick catalog to sold
Sector
Auction house — internal ops
Scope
68 screens, one system of record
Stack
Laravel 13 · Vue 3 · Postgres 17
The AI's job
Drafts the catalog copy; a person approves
Deletes
Soft everywhere, with bulk restore
What Changed

Same work.
One place it now lives.

The staff already knew how to run an auction. The tools were the problem: three of them, none aware of the others, and no way to answer who changed what. Nothing here reinvents the operation — it just gives it a memory.

× Inventory tracked across spreadsheets, a shared photo folder, and a group chat
One record per item, from intake to sold, with every change attributed
× Item status kept in three places that quietly disagreed with each other
Status computed by the database itself — it cannot drift, and code cannot overwrite it
× Catalog copy typed by hand, item by item, in the format the auction software wanted
Photos in, draft title, description and keywords out — reviewed and approved by a cataloger
× Photos emailed around or dropped in a folder, named by whoever took them
Phone camera straight to storage, converted and optimized on the way in
× Changing a hundred items meant a hundred edits, or a mass update nobody could trace
Queued bulk actions across thousands of items, with live progress and a full audit trail
× Deleting the wrong thing meant it was gone
Soft deletes throughout, a recently-deleted screen with bulk restore, and a retention schedule

Three ideas the whole
system is built on.

01

The lifecycle is the spine

Inventory moves through seven stages — quick catalog, photograph, store, assign, review, catalog, auction — and then forks to sold or unsold. That path is not a status dropdown bolted on afterwards. Filters, dashboards, bulk actions and the item stepper all key off the same lifecycle, so where a piece stands is the same answer everywhere in the system.

02

State you cannot corrupt

Where an item stands is derived by the database from stage timestamps, not stored alongside them and hoped to agree. Sold and unsold are made mutually exclusive by a constraint, not by a method someone might route around. Trust in an operations tool is not a feature you add — it is what you get by making the wrong state impossible to write.

03

AI drafts, people approve

A vision model reads the photos and returns catalog data already shaped to the columns the auction software downstream expects. A cataloger reviews it, regenerates with feedback where it is off, and approves it into real inventory. The model never publishes anything on its own, and when the API is down a circuit breaker stops the queue instead of retrying into a wall.

An internal tool is where teams accept bad software. We took the exception and then didn't use it.

The design system is checked into the repository — a colour scale, a five-role type scale, component specs, and named anti-references: not the default template, not enterprise grey, not consumer playful, not the spreadsheet it replaced. Dense where staff scan, calm and deliberate at the commit points — deleting, publishing, marking a lot sold. Status is never communicated by colour alone.

Inside The Build

The decisions
nobody sees.

None of these are visible in a screenshot. They are the reason the tool is still trusted after nine months of daily use, and the reason a bad afternoon is recoverable.

Status the app cannot lie about

Three sources of truth for stage completion were collapsed into one. Timestamps became the only record; status and progress became generated columns computed from them and indexed. No backfill, no drift, and no write path — including raw SQL — that can disagree.

Auditing writes that fire no events

A bulk edit through the query builder skips model events, so a twenty-five-thousand-item change used to leave no trail. Auditing moved into a custom builder, which covers every mass update written since — and every one written in future — without anyone having to remember.

A circuit breaker on the AI

Failures are counted in cache; past a threshold the circuit opens and jobs fail fast for a cooldown window rather than hammering a provider that is already struggling. Concurrent categorization batches are throttled on the way in.

Built for a phone camera

Uploads are presigned and go straight to storage. iPhone photos are converted from HEIC and compressed in the browser before they ever leave, then optimized again by a queue worker — so cataloging from a phone on the warehouse floor is the normal path, not the degraded one.

Lot numbers that reuse their gaps

Numbers are allocated as the lowest unused value in an auction rather than from a counter, so renaming or removing a lot frees its number instead of stranding it. Inserts serialize on a row lock, and a unique constraint keeps an item from landing in two lots.

Realtime, with a fallback

Job progress, image processing and bulk actions broadcast over WebSockets and the interface subscribes — but it also polls if the socket never arrives, and failed broadcasts are retried. Progress bars are the thing users trust least when they are wrong.

On The Bad Day

Every operation has one.
This is what it costs.

A team that stops trusting its own tool goes back to the spreadsheet, and nobody tells you they have. So the question worth asking of an operations system is not what it does on a good day — it's what the worst afternoon of the quarter costs you.

Somebody bulk-edits a few thousand items and gets it wrong
Every change is attributed and recorded, so it can be found and reversed rather than reconstructed from memory
Something is deleted the week before the sale
Nothing is really deleted — a recently-deleted screen restores it in bulk, on a retention window the house sets
Two people disagree about where a lot stands
One status, derived from the record itself, reading the same on every screen and in every export
A lot number is skipped, reused, or lands on two items
Numbers are allocated from what actually exists, and an item cannot sit in two lots at once
The AI writes something wrong into the catalog
Nothing it produces becomes inventory until a cataloger has read it and approved it
Cataloging stalls because a phone or a service is having a bad day
Photos upload from the floor as the normal path, and a failing provider stops the queue instead of poisoning the batch

Work of this shape is custom operations software, the cataloging half is AI integration, and the way it replaced the spreadsheets is process automation.

Still Running On Spreadsheets?

The spreadsheet was never the problem. Outgrowing it is.

Tell us how the work actually moves today — the files, the group chats, the one person who knows. We will map it, tell you which parts are worth building software for and which are fine as they are, and what the first version costs.