Work · Products · 11

Cartwise

Photograph a receipt, get a nutrition summary. The data entry is the whole problem.

Type
Products
When
2026 · shipped
Role
Sole designer and developer. Parsing strategy, data model, interface, deployment.
Scale
One photograph in, an itemised nutrition summary out.

In plain language

Cartwise turns a supermarket receipt into a nutrition summary. You photograph the receipt, it reads the lines, works out what each abbreviated item actually is, and tells you what came into the house on that shop. It does not ask you to log anything, because the receipt was already written for you, and the reason nutrition apps get abandoned is not motivation, it is that logging is a chore you have to remember at the exact moment you least want to.

The question

Can the data-entry step be removed entirely, by using a record the shop already produced, instead of asking a person to reproduce it by hand?

Rough sketch

Where things sit on the screen, and in what reading order. Drawn, not screenshotted, so it stays true when the styling moves on.

Upload

Parsed items

Summary

Upload, review the parse, then the summary. The review step exists because the parse is the part that can be wrong.

Why nutrition apps die

They ask you to log what you ate. People do it for about nine days. The failure is not motivation. It is that logging is a chore you must remember, performed at the exact moment you least want to.

The bet

The receipt already knows. It is generated automatically, it is itemised, and it exists whether or not you decide to create it. Make the receipt the input and logging stops being a habit you have to maintain.

What it does

Upload a receipt photo. Items are parsed, matched against nutrition data, and summarised into what actually entered the house over that shop, as opposed to what was eaten, which is a different and much harder question the app does not pretend to answer.

The genuinely hard part

Item matching, not the nutrition arithmetic. Receipt lines are truncated, abbreviated and retailer-specific: MLK SEMI 2PT has to resolve to semi-skimmed milk, two pints. Every retailer abbreviates differently, and the same product reads differently across two shops. That mapping is where the accuracy is won or lost, and it is the part that would need real work to scale past one country’s supermarkets.

Stack

Next.js 15 with Prisma over a relational store, deployed on Vercel.

How it is put together

Left to right, the path a request or a record takes through the system.

  1. 01

    Capture

    • Receipt photo upload
    • Text extraction
    • Line splitting

    The input already exists. That is the whole bet.

  2. 02

    Resolve

    • Truncated and abbreviated line items
    • Retailer-specific shorthand
    • Match to a known product

    The genuinely hard part.

  3. 03

    Enrich

    • Nutrition lookup per matched item
    • Quantity and unit normalisation
  4. 04

    Report

    • Per-shop summary
    • History across shops
    • What entered the house, not what was eaten
Everything hard is in the middle lane. The nutrition arithmetic at the end is trivial; turning MLK SEMI 2PT into semi-skimmed milk, two pints, is where accuracy is won or lost.

What happens, step by step

In plain language, in the order it happens.

  1. Photograph the receipt

    The one action the user takes. Everything after this is the app's problem.

  2. Read the lines

    The receipt is split into individual line items with quantities and prices.

  3. Resolve each line to a real product

    This is the accuracy bottleneck. Receipt lines are truncated, abbreviated and retailer-specific: MLK SEMI 2PT has to become semi-skimmed milk, two pints, and every retailer abbreviates differently.

  4. Attach nutrition

    Once a line is resolved to a product, the nutrition data is a lookup. This step is easy, which is exactly why the previous one matters.

  5. Summarise the shop

    The output describes what entered the house over that shop, deliberately not what was eaten, which is a different and much harder question the app does not pretend to answer.

Decisions, and what they cost

Every choice worth recording has a road not taken. Both are here.

  • The receipt as the input

    instead ofManual food logging

    Receipts are generated automatically, itemised, and exist whether or not the user decides to create them. Making the receipt the input means logging stops being a habit that has to be maintained, which is the failure mode, not a feature gap.

  • Report what was purchased

    instead ofEstimating what was consumed

    Consumption requires assumptions about waste, portions, sharing and timing that the receipt cannot support. Claiming to measure it would be a fabrication dressed as a feature; saying what it does and does not know is worth more than a bigger number.

  • A relational store through Prisma

    instead ofA document store

    Receipts, line items, products and nutrition records are a normalised graph with real joins across it. This is the shape relational databases are for, and the typed client removes a class of query bugs at compile time.

  • One country's retailers first

    instead ofA general parser

    Abbreviation schemes are retailer-specific and there is no universal grammar to generalise over. Matching well for a known set beats matching badly everywhere, and it names honestly what scaling would actually cost.

What came out of it

  • Live and usable: photograph a receipt, get an itemised nutrition summary back.
  • The data-entry problem is genuinely gone: the user takes one action, not thirty.
  • The honest limit is stated in the product itself: it reports what was purchased, not what was eaten.

Still open

  • Item matching is the accuracy ceiling, and it is retailer-specific. Scaling past one country's supermarkets is a real body of work, not a config change.
  • No feedback loop yet where a user correction improves the next match.

Built with

  • Next.js 15
  • TypeScript
  • Prisma
  • Vercel