Inspiration

Everyone told to eat more protein has had the same week. You install a calorie tracker, weigh a chicken breast, search a database of 400,000 entries, find eleven kinds of "chicken breast", pick one and by Thursday you have stopped.

The failure isn't motivation. It's that the tool demands a precision nobody sustains, for a number most people don't need. Protein is different from calories or other macros in a way that matters: you need one number, and only roughly right. Hitting 130 g instead of 138 g changes nothing. Missing four days because logging is tedious changes everything.

So the whole bet is this: if logging a meal costs a photograph instead of a search, a weigh and a form, people will still be doing it in March.

What it does

Photograph what you eat; ProScan returns the protein and adds it to your day. A nine- question onboarding produces a target from your body and goal, then splits it across the day you actually eat, turns each obstacle you name into an if-then rule, and gives you one rule to follow in week one. The diary keeps itself.

No account, no login, no searching, no weighing, no typing.

How we built it

A Flutter app over a PHP/MySQL backend, with 150 tests written as the arguments were settled rather than afterwards.

Two models, asked at once. The backend fires Gemini and OpenAI simultaneously via curl_multi and takes the first usable answer for latency, not redundancy. A photo of dinner comes back in one second or eleven depending on the weather, and nobody standing over a cooling plate waits for the slow one.

No accounts. Identity is a device id generated on first launch. Nothing to leak, nothing to lose, the trade being that "Restore purchases" is the entire recovery story, so it had to be perfect.

The target is arithmetic, computed twice. Resting energy by Mifflin-St Jeor, scaled by training and goal. The same formula lives in Dart and PHP: the wizard shows the number moving as you answer, which can't wait for a round trip, and the server must answer "what is this person's target" without asking the app. A cross-check test runs identical profiles through both and fails if they ever disagree.

Challenges we ran into

A cliff in the maths. Protein should scale with how much of you there is to maintain, not with fat, so above a BMI of 30 we taper the weight. Our first version applied the taper above BMI 30 but anchored it at the BMI-25 weight, making it discontinuous exactly where it starts. At 178 cm:

Weight Basis Target
95.0 kg 95.0 kg 160 g
95.1 kg 85.6 kg 145 g

A hundred grams of body weight cost 15 g of protein a day, and it didn't recover until ~115 kg. Anyone who gained a pound and watched their target drop would rightly conclude the app was broken. Anchoring at the BMI-30 weight makes both branches agree at the boundary:

$$ w_{\text{ref}} = \begin{cases} w, & \mathrm{BMI} \le 30 \[4pt] 30m^2 + 0.4\,(w - 30m^2), & \mathrm{BMI} > 30 \end{cases} $$

A test now sweeps every height from 150–205 cm and every weight from 40–250 kg, asserting the target never falls as weight rises.

Store plumbing is its own project. A release build was rejected for debug signing, because Gradle falls back to the debug key when key.properties is missing, a convenience that becomes a trap the first time it matters. Then a real chicken-and-egg: Apple won't move a subscription out of Missing Metadata without a screenshot of the paywall, and the paywall can't show prices until the product is fetchable. StoreKit configuration files are the escape hatch, and Xcode injects them at launch, so flutter run, which installs via simctl, silently ignores them and reports Parsing 0 products in response, an error that reads like a dashboard problem and isn't.

Metric core, imperial surface. Our main market thinks in pounds and feet; the maths only works in kilograms. Units are strictly a display layer, storage and the server are always metric. Anything else lets a converted, rounded weight reach the target, drifting a few grams every time someone opens their profile.

Accomplishments that we're proud of

A meal logged in one tap, not one photograph. People eat the same twenty things, so the second week should cost less than the first. Everything eaten before sits behind an Again button on the home screen, one tap and it's logged.

Nothing is typed. Weight, height, goal weight and portion sizes are all set by dragging a tape .

A plan, not a number. Most trackers hand you a figure. ProScan splits your target across the day you actually eat and because muscle protein synthesis saturates around $0.4\ \mathrm{g/kg}$ in a sitting, it flags when one meal is carrying more than a body can use at once.

Corrections that stick. The model is good, not perfect. When it reads a portion wrong you correct it with a dial, and the correction persists to the server.

It survives a bad network. Offline, the naive version showed a target of zero, etc. ... It now falls back to the stored target and the app is usable.

What we learned

Running it finds a class of bug tests cannot. Our projection chart passed everything: correct model, correct points, correct direction, and drew a rising line for someone losing 33 lb. The painter flipped the y-axis on a canvas that already grows downward. It looked entirely plausible and said the opposite of the truth. Only human testing caught it. The same happened with copy rendering -- instead of an em dash, and a note that said "move it earlier than dinner" regardless of which meal was actually largest.

What's next for ProScan: Daily Protein Scanner

  • The evening nudge, properly: a Firebase project, token registration, and the permission asked on the second launch rather than the first. Then it goes back on the paywall, where it will finally be true.
  • Barcode lookup in the camera screen. It already works end to end on the server, but nothing in the UI calls it yet.
  • Wider release. Android alongside iOS, and localisation beyond English.
  • Weekly review, using the fortnight of data the app already keeps: which days went wrong, and whether the rule from week one actually held.

Built With

Share this project:

Updates

Submission history