Opening the demo. The live build is behind a passcode. It is 5120. Add it to your home screen and it runs full screen on iPhone and Android alike, with no account and nothing to install.
Situation
Australia’s National Residue Survey publishes what it tests for on fresh produce. It does not publish much. Of the 66 fruits and vegetables we cover, four have chemical level results in the public tables. For the other 62 the only evidence available is what monitoring in the United States and the United Kingdom found.
So a parent trying to find out what is on the food they are buying runs into a record that is mostly missing, and into a lot of websites willing to fill the silence with a confident answer. That gap turned out to be the product rather than an obstacle to it.
Task
The core project of the industry studio unit at Monash, after CalmRoute served as the team’s onboarding build. Six people, Team TOXIMO. I was responsible for the backend, the database, the API and the core app screens.
The hard requirement was one we set ourselves and then had to keep under pressure: never label a food safe or unsafe, and never present overseas figures as Australian ones. Both are easy to promise in a planning document and easy to break by accident in a UI.
Action
The app is React Native on Expo. Behind it is a FastAPI service over PostgreSQL, running on AWS Lambda inside a VPC and reaching RDS over a private address. The database holds five tables: one row per produce item, 2,946 rows covering each combination of item, chemical and wash method, 869 PLU sticker codes, and two tables splitting overseas results by organic status and by country of origin.
There are three ways to identify an item without typing. Photograph the sticker and it reads the PLU code, photograph the packaging and it reads the country of origin, photograph the item and it names the produce. All three run on Amazon Rekognition.
Three decisions did most of the work:
Recognition never chooses. Every one of those three endpoints returns a list and waits. A misread digit on a PLU sticker does not fail loudly, it resolves to a different real fruit, so picking the top result confidently would show somebody accurate residue guidance about food they are not holding. The response schemas have nowhere to put a finding, so a recognition result physically cannot carry one.
Missing is stored as null, never zero. “Not studied” and “studied, found nothing” are different findings and the app has to word them differently. A zero would quietly turn the first into the second.
Every dataset carries a manifest. The finished CSVs are paired with their SHA-256 and row count, and a file changed without regenerating its manifest fails the load rather than producing a quietly wrong database.
Result
It works, it is deployed, and the coverage gap is stated on the face of it rather than left to be inferred: 2,332 of the 2,946 chemical records come from American monitoring alone, another 486 from American and British monitoring together, Australian monitoring reaches four of the 66 items, and no Australian washing study exists in the dataset at all.
There are 594 backend tests and 547 mobile tests. Some of the mobile ones read source files as text rather than calling functions, and assert structural properties: that no wording module can reference a recognition result, that a scanner has exactly one call site that opens an item. Those are properties a runtime test cannot reach, and each one exists because the thing it checks broke once. CI runs six jobs including a release gate that fails closed if any other job did not succeed.
Two things I would not have predicted going in. The first is how much of the engineering went into refusing to answer rather than answering, and how often that had to be enforced in a type signature instead of a code review, because a rule nobody can break is worth more than a rule everybody agrees with.
The second is the Expo SDK. We pinned it early to protect testers and it became the thing breaking them, because Expo Go only ever ships one SDK version and an iPhone cannot install an older one. A project left behind simply cannot be opened. That is why every iteration is archived as a web build under its own URL instead: a browser has no such dependency, and the address in a report keeps working after the SDK moves on.
The source is private coursework, so the live web build is the thing to look at.