Skip to content

Point the camera at a broken part, find it on the shelf

A cross-platform app that identifies a plumbing or electrical part from a photo and points to the aisle it's in, running the recognition on the phone itself, because the stores have almost no signal past the front doors.

11:06
FettlewayOn this phone
No signal · still worksNamed in 208 ms
15mm compression elbow0.93Brass · 90° · pack of 1 · HP-10482 · £3.49Aisle7Bay4ShelfC23 on the shelfCached stock · 2 h 14 min old
Recent findsCastlegate · last hour
10:58½in BSP isolating valveA6 · B3In stock
10:4122mm push-fit equal teeA7 · B2Out here
10:2320mm stuffing glandA11 · B5In stock
10:09Not named — sent to counterCounterCounter
ScanMapList3On phone

How the work was scoped

Industry

E-commerce & Retail

Duration

14 weeks

Cooperation model

Time & materials

Services
React Native buildOn-device visionStore release
Integrations
ShopifyStripeKlaviyoShipStation
Technologies
React NativeTypeScriptTensorFlow LiteOn-device vision modelSQLiteApp Store & Play Store release
Team
1 Project lead1 Product designer2 Mobile engineers1 Backend engineer1 QA engineer

Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.

Recognition on the phone

Identification runs on-device, so the store's dead coverage stops mattering.

A quantized TensorFlow Lite model runs on the phone, so identification takes about 200ms and works in the aisle where there's no signal at all, which is the place the app is actually used. Inference runs on the device's own accelerator where one exists and falls back to CPU where it doesn't, and the model file ships with the binary, so there's no download on first run.

What shipped
  • Quantised TFLite model, ~200ms on-device inference
  • Works in aisles with no signal, which is where it's used
  • Model ships in the binary, no first-run download
11:06
On this phoneEverything that works with the signal offOffline
Part model v9TensorFlow Lite · int8 · shipped in the app, no downloadSize34 MBFittings400Families8
Last scan 208 ms · neural engineFalls back to the CPU on phones without one
What it knows400 fittings
Compression72
Threaded brass (BSP)58
Push-fit64
Solvent-weld waste48
Valves & isolators44
Tap & WC connectors40
Cable glands & boxes38
Terminals & connectors36
Store maps heldrefreshed 08:52
Castlegate Riverside Park Northfield Wharf Road Kingsmead Eastmoor
ScanMapList3On phone
On screen

What actually lives on the phone: a 34 MB quantized model shipped inside the app, the last scan named in 208 ms on the neural engine with a CPU fallback, the eight families making up its 400 fittings, and six store maps held locally.

10:09
Not sure — asking the counterCastlegate · no signal
No confident matchNothing scored above 0.45, so this phone won’t name it. A wrong part is worse than no answer.
Closest four, all declinedthreshold 0.45
½in BSP hex nipple0.41
15mm tank connector0.38
¾in × ½in BSP reducing bush0.33
15mm compression stop end0.29
May not be one of the 400 it knows — search instead
Counter ticketCT-0917-06Photo attached · 4 closest guessesTrade counter · 2 ahead of youSends when the phone finds signalTrade counter is by the front doors, left of the registers
SearchGo to counter
On screen

The handoff in place of a confident wrong answer: four candidates all under the 0.45 threshold, an offer to search in case the part is outside the 400, and a counter ticket carrying the photograph.

Scoped to what returns

The model covers the 400 fittings behind most returns, not an open catalog.

The model recognizes the four hundred fittings behind most returns, not the whole catalog. A narrow model is small enough to run on a mid-range phone and accurate enough to be trusted, where an open-catalog model would have been neither. Anything outside that set returns not recognized and offers search, which is a better answer than a confident wrong part number.

What shipped
  • Scoped to the 400 fittings behind most returns
  • Small enough for a mid-range phone, accurate enough to trust
  • Out-of-set inputs decline instead of guessing
11:06
Aisle 7 · bay 415mm compression elbow · Castlegate23 here
Map held on phone · 2 h 14 min old · 08:52
CASTLEGATE · FLOOR PLAN123456789101112YouTrade counterEntrance
Aisle 7 · Pipe fittings7 bays on this run
1Push-fit elbows & inserts34 lines
2Push-fit tees & reducers29 lines
3Compression couplers26 lines
4Compression elbows & teesShelf C · yours
5Threaded brass (BSP)40 lines
6Pipe clips & olives22 lines
7Tap & WC connectors27 lines
ScanMapList3On phone

A cached aisle map

On screen

The cached floor plan walking the customer to aisle seven, bay four, stamped with how old the map is, with every bay on that run and what sits in it.

Each store's map held locally and refreshed opportunistically when signal is available.

Once a part is named it still has to be found, so each store's aisle map and stock snapshot are cached on the device and refreshed opportunistically whenever there's signal: at the counter, in the parking lot, anywhere. With a connection the app resolves against live stock; without one it shows the cached position and says how old it is, never pretending to be current.

What shipped
  • Per-store aisle map and stock cached locally
  • Refreshed opportunistically whenever signal appears
  • Cached results state their age instead of implying freshness
Introduction

What we were brought in to do

Customers walk in holding a broken fitting and no idea what it's called. We built an iOS and Android app that names the part from a photo and shows where it sits in that specific store, and made the whole recognition path run on-device, because a steel-shelved hardware store is a dead zone.

A hardware retailer where the single biggest consumer of counter time was returns, and most returns were the same failure: a customer took home a fitting that looked right. They arrive holding a broken part and no vocabulary for it, and naming it correctly requires a staff member who knows fittings, of whom there are two per store on a good day.

Mobile Engineering & AI

Where the old way broke

Naming a part was the whole bottleneck. Customers waited for a staff member who knew fittings, guessed, and took the wrong thing home, and returns were the single biggest source of counter time. A cloud-based scanner was tried first and failed for a simple reason: there's no usable signal in the aisles where people actually stand.

A cloud scanner had already been tried and abandoned. The reason was mundane and decisive: a hardware store is steel shelving in a metal shed, and there's no usable signal in the aisles where customers actually stand holding the part. The app worked in the parking lot and at the counter, which are the two places nobody needs it.

We shipped a single React Native codebase with a compact recognition model running on the phone, so identification works with the network completely off. Once a part is named, the app resolves it against that store's live stock and aisle map when signal returns at the counter, and falls back to a cached map when it doesn't.

What we built together

  • Ran recognition fully on-device so the aisles' dead zone stopped mattering

    On-device was settled by the failed cloud attempt: the aisles have no signal, and that's where the customer and the broken part actually are.

  • Scoped the model to the 400 fittings that drove most of the returns

    The model covers the four hundred fittings behind most returns, not the whole catalog, because a narrow model is both small enough and accurate enough.

  • Cached each store's aisle map locally, refreshed whenever the app had signal

    Each store's aisle map and stock snapshot cache locally and refresh opportunistically, so a named part still has a location when there's no connection.

  • Handed off to a staff lookup when confidence was low, with no guessing

    Below the confidence threshold the app declines and offers a staff lookup. It never returns its best guess.

Process

Phase by phase

  1. Phase 1: Decide where it runs

    On-device, because of the aisles

    Established that recognition had to run fully on-device, since the aisles have almost no signal.

    • Feasibility study
    • Model size budget
  2. Phase 2: Scope the model

    400 fittings, not everything

    Scoped the recognition model to the 400 fittings that drove most returns, trading breadth for accuracy.

    • Training set
    • Accuracy benchmarks
  3. Phase 3: Cache locally

    The map, held on the phone

    Cached each store's aisle map locally and refreshed it whenever the app had connectivity.

    • Local cache
    • Sync strategy
  4. Phase 4: Handle doubt

    Hand off, do not guess

    Built a staff-lookup handoff for low-confidence matches instead of returning a confident wrong answer.

    • Confidence thresholds
    • Handoff flow
    • Store release
10:41
Named · not on this shelfCastlegate · aisle 7, bay 2 is empty
22mm push-fit equal tee0.88White plastic · pack of 1 · HP-11207Out of stock here£4.85
Stock for 6 stores held on phone · 2 h 14 min old · 08:52
Four other stores have itby quantity
Riverside Park2.4 mi · A7 · B212 in stock
Kingsmead6.5 mi · A5 · B18 in stock
Northfield3.1 mi · A7 · B35 in stock
Wharf Road4.8 mi · A9 · B23 in stock
Usually bought with itin stock here
22mm pipe insert ×2A7 · B1£1.10
22mm pipe clip ×10A7 · B6£2.35
Plastic pipe cutterA12 · B3£8.99
Other sizesAdd to list
ScanMapList3On phone
On screen

A part named but out of stock here, with the four other stores that have it from the cached stock snapshot, its age stated, and what's usually bought alongside.

Operational results after launch

-46%

Wrong-part returns

Works

Identification with signal off

Under 4s

Median time to name a part

Wrong-part returns compares the two quarters after launch with the two before, at stores where the app was promoted. Identification with signal off is a capability claim, not a measurement. Median time to name a part is measured in-app from camera open to a named result, excluding the low-confidence handoffs.

Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.

Ways of working

About our collaboration

01
  1. 01

    A cross-functional team of 5 worked on a time & materials basis over 14 weeks, covering React Native build, On-device vision, Store release. We ran daily standups with their own lead in the room, and a demo at the end of every sprint. Scope changed twice during the engagement, and both times the change was priced and agreed before work started.

    Time and materials, because the model's scope genuinely couldn't be fixed in advance: how narrow it had to be to run acceptably on a mid-range phone was something to be discovered. Recognition accuracy was measured against real returned parts photographed in real aisle lighting, not a clean dataset, and that's what set the four-hundred-item boundary.

What it settled

What we'd carry into the next one

  1. 01

    Scoping the model down was what made on-device accuracy possible. Breadth would have cost both.

    Breadth and on-device accuracy trade against each other directly: an open-catalogue model would have been too large to run and too uncertain to trust.

  2. 02

    The signal dead zone was the defining constraint, and designing around it was cheaper than fighting it.

    The dead zone wasn't an edge case to engineer around. It was the operating environment, and accepting it produced a simpler system than fighting it would have.

  3. 03

    A confident wrong part is worse than no answer; the handoff was a feature, not a fallback.

    Declining is the whole value proposition here. The failure being solved is a customer confidently taking the wrong part home, and a wrong answer reproduces it exactly.

One scan, in an aisle with no signal

Named, located and checked for stock without a single bar of signal.

Choose the part in the customer’s hand and follow it from the camera frame to the shelf. One is on the shelf, one is out of stock here, and one the model won’t name. Switch steps, or use the arrow keys once one is focused.

Holding
Signal off · every step on the phone

Named: 15mm compression elbow

15mm compression elbow0.93
15mm compression tee0.04
15mm compression coupler0.02

Quantised TensorFlow Lite on the phone's accelerator (CPU where there isn't one), about 200 ms. The white mark is the 0.45 threshold; the top score clears it, so the part is named.

Illustrative: runner-up scores and the 600 ms “matching” pause are for reading, not measurements; on-device inference is about 200 ms. Parts, bays, stock counts and store names are invented.

Architecture

From a photo in a dead zone to the right bay, with the network optional

One React Native codebase on iOS and Android. The dead zone is the operating environment, not an edge case, so every step up to the bay runs on the handset and the network only ever makes an answer fresher.

  1. 01 · Trigger
    Camera frame in the aisleCaptured where the customer stands with the broken part, which is where there's no signal. Nothing waits on a network.
  2. 02 · Inference
    Quantised TFLite on the phoneAbout 200 ms on the device's accelerator, falling back to CPU. The model ships in the binary: no first-run download.
  3. 03 · Decision
    400 fittings, one thresholdScoped to the fittings behind most returns. Below the confidence threshold, or outside the set, it declines and offers search or a staff lookup.
  4. 04 · State
    Per-store map + stock in SQLiteEach store's aisle map and stock snapshot cached on the phone, so a named part still has a location with no connection.
  5. 05 · Delivery
    Live stock when signal returnsRefreshed opportunistically: counter, parking lot, anywhere. With signal it resolves live; without, the cached answer states its age.

No signal, no guessing, no stale surprises

Keeping the wrong part out of the customer’s hand

Works where the part is

Recognition runs entirely on the phone, so identification works with the signal completely off: in the steel-shelved aisle, well beyond the parking lot and the counter where a cloud scanner used to work.

Declines rather than guesses

Below the confidence threshold, or for anything outside the fittings it covers, the app returns no part and offers search or a staff lookup. A confident wrong answer is the failure it was built to stop.

Cached answers say how old they are

Aisle positions and stock come from the per-store copy on the phone when there's no connection, and show its age. With signal the app resolves against live stock, so the copy never poses as current.

Need an app that recognises things on the phone, where there’s no signal? Scope your build in 3 minutes.

Scope your build
Have a project?

Let's talk

Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.