Gift Registry and Marketplace
Catalogues read by AI, gifts funded together
- Year
- 2026
- Kind
- Apps
- Status
- In progress
- Built at
- QED Analyticals
A gift registry and marketplace where guests pool money toward one gift and vendors turn PDF catalogues into listings with a vision model and OpenCV, all on a double-entry ledger.
Hosts create events with wishlists, guests contribute through a payment gateway, and one gift can be funded by many people. Vendors upload a PDF catalogue: a vision model reads each page into structured products and OpenCV cuts a clean photo of each one. Money moves through a double-entry ledger in integer currency units, with signed webhooks, idempotent writes and a reconciliation sweep. I wrote 799 of its 815 commits over 17 weeks.
The problem
A group gift has more moving parts than a shop. Many guests pay toward one item at different times, through a payment gateway that retries its webhooks, and vendors need their products listed before anyone can buy. Typing a vendor's catalogue in by hand doesn't scale, and money counted twice, or not at all, is the one bug nobody forgives. This marketplace handles both: catalogues are read by a vision model, and every payment moves through a ledger.
How it works
- 01
Render only the pages that sell something
A vendor uploads a PDF catalogue and each page is rasterized at 200 DPI. If the PDF has a text layer with price patterns in it, only the price-bearing pages go to the model, which skips covers and brand pages for free. A scanned catalogue has no such signal, so every non-blank page is read.
- 02
Read each page into a schema
A vision model returns one JSON array per page: name, SKU only if one is printed, price, pack size, a category from a fixed taxonomy, and a bounding box for the photo. It runs at low temperature in JSON mode, with backoff on rate limits. The page's own text is passed in labelled as data, with a note to ignore any instructions inside it, because a catalogue is untrusted input.
- 03
Let OpenCV cut the photo
The model is good at finding a product but its box is loose. The crop pads that box, estimates the background colour from the corners, builds a colour-distance mask, cleans it with morphological closing and opening, and merges the large contours into the real edges. Contrast and sharpness are scored, and a low score triggers a wider re-crop. A second vision pass trims stray strips only if it removes a real margin and keeps most of the product.
- 04
Survive a crash halfway through a catalogue
Each extraction runs in a background thread that mirrors its state to disk by writing a temp file and renaming it, with a heartbeat, so a run whose worker died shows up as interrupted rather than stuck. Finished pages are checkpointed, resume and submit are conditional database updates, and every product starts out pending approval.
- 05
Pool many payments into one gift
Each wishlist item keeps its pooled amount and the set of contribution IDs already applied, so a retried payment can't be credited twice. An item moves from open to partially funded, funded, ordered and fulfilled. When an event closes short of the target, the host can turn the pool into wallet credit or switch to a cheaper product.
- 06
Move every payment through a ledger
Webhooks are verified with HMAC-SHA256 over the raw body, compared in constant time, and the paid amount must match before anything is marked paid. Money lives in a double-entry ledger in integer minor units, and each entry is unique on its source, destination and reference, so a replayed event changes nothing. A sweep checks the gateway before cancelling a payment that looks stale.
Results
- ~1,850
- backend test functions, behind CI gates for coverage, types and lint
- 799
- of the repo's 815 commits are mine, over 17 weeks
Decisions
The model finds, OpenCV cuts
A vision model locates products well but draws loose boxes, and classic image processing finds exact edges but can't tell a product from a logo. Using each for what it's good at gave clean product photos without training a detector.
Claim first, then act
Refunds and order fulfilment first claim the record with a conditional update, and only then call the gateway or the vendor. Two workers can't both act on the same payment, and a crash leaves a visible claim instead of a silent double charge.
Integer money, double entry
Amounts are integers in the smallest currency unit, and every movement is a pair of entries. A refund reverses the entries that were actually booked rather than recomputing them from today's rates.
Refuse unsafe config at startup
Live payment mode won't start without a webhook secret, and mock mode won't start in production. Misconfiguration fails loudly at boot instead of quietly accepting unsigned payments.