ReadMyStrip
Live · iOS · Paying customers
A test-strip reader where the model perceives and code decides.
I took ReadMyStrip from concept through App Store launch — product strategy, capture UX, the vision pipeline, backend, subscriptions and iteration — and it has paying customers.
The short version
Real-world photos are messy, and strip brands differ. The answer was to narrow the model’s job to perception, have code make every decision, and let each user’s own chart define what a valid reading is.
- Role
- Founder / AI Product Builder, Yellow Labs Products
- Timeline
- 2026 – present
- Status
- Live on the App Store · paying customers
- Live
- App Store ↗
Key signals
- Paying customers on a $0.99/month or $9.99/year subscription
- Model output validated against the user’s own printed chart
- Scan history and notes added from production feedback
Capabilities shown
- Vision-model pipeline
- Deterministic validation
- Capture UX
- Subscription monetization
- Iterating from feedback
How it was built · I use AI-assisted development to work directly in the implementation layer, while owning the product decisions, architecture, evaluation criteria and production validation.
Subtle colors, bad lighting, and every brand’s chart is different.
Testing pool, spa, or aquarium water means reading a strip of subtle color pads against a reference chart — slow, subjective, and easy to get wrong. Results shift with lighting, eyesight, and guesswork, and every brand prints its chart differently.
Narrow the model’s job.
The first scan architecture leaned on reconstruction — a color-science matcher alongside a model asked to reason about chemistry and decide status. Within days it was replaced: the model now does one thing, picking the closest printed swatch for each pad, and everything after that is deterministic code.
The tradeoffs I made on purpose.
The model perceives; code decides
For each pad the model returns only the label of the closest swatch on the chart. Code parses the value and decides low, in range or high. Any label that isn’t actually printed on that pad’s row is rejected as a hallucination and becomes “unknown.”
Your chart defines a valid reading
Strip brands differ, so each user photographs their own bottle’s chart once. The chart is parsed into rows and ranges, and every later scan is judged against it rather than a generic reference.
Ask for a rescan instead of guessing
When more than 40% of pads can’t be read confidently, the app asks for another photo rather than presenting a partial result as complete.
Pay before the model runs
One free scan, then a subscription — enforced on the server before any model call, so unit economics hold and the paywall can’t be bypassed from the client.
From a messy photo to a reading you can act on.
- 01
Capture
A camera screen draws measured framing guides for the strip and the chart, and the photo is cropped to the guide before upload. Strip scans are camera-only.
- 02
Check the image
Brightness, glare and sharpness are measured before any model call; images outside the limits prompt a retake.
- 03
Read the chart (once)
The chart is cropped into overlapping bands for resolution and parsed into a structured schema of chemicals, units and levels; illegible charts, duplicate rows or suspicious markings are rejected with a retake prompt.
- 04
Perceive
A vision model compares the strip with the user’s saved chart and returns, per chemical, the closest printed label — nothing else.
- 05
Decide
Code validates every label against the chart, parses values, and assigns low, in range, high or unknown; too many unknowns trigger a rescan.
For technical readers · Architecture & stack
The stack supports the product decisions above; it isn't the story.
- App
- ExpoReact NativeTypeScriptiOS
- Vision pipeline
- Guided capture and cropImage quality gates (brightness, glare, sharpness)Band-cropped chart parsingPerception-only vision model callStructured outputs, validated in code
- Platform
- Vercel functionsSupabaseRow-level security
- Payments
- RevenueCatServer-side entitlement checksWebhook sync
Quality by constraint.
Quality today comes from constraint rather than a measured accuracy figure: the model’s output space is limited to labels printed on the user’s chart, and everything else is validated in code.
Output validation
Every reading is checked against the chart before it is shown; an unrecognized label can only ever become “unknown.”
Image quality gates
Dark, overexposed, glary or blurry photos are caught before they reach the model.
Parser contract tests
The chart parser and value/status logic are covered by unit tests.
Structured logging
Every scan logs timings, tokens, outcomes and per-pad reads, which is how production behavior gets reviewed.
Paying customers, and feedback that shipped.
ReadMyStrip is live on the App Store with paying customers, and production use has already reshaped it.
- One free scan, then $0.99/month or $9.99/year
- Entitlements enforced server-side, synced by RevenueCat webhooks
- Scan history with notes — “shocked after rain,” “added chlorine” — added in 1.0.1 from customer feedback
- Notes added to the result screen too, after testing found they could only be added from History
Reliability came from scoping, not from a bigger model. Give the model the narrowest job that still needs it, and make the rest of the system unable to pass along a guess.
What I’d measure next.
The product works in production today. These are the next layers of measurement and rigor I’d add as it matures.
- 01Build a labeled image set across strip brands and lighting conditions, so reading accuracy is measured directly rather than inferred from constraints.
- 02Pin the scan model version, so accuracy measurements stay comparable over time.