InspirationInspiration

Food recalls mostly work. The agency posts the notice, the news picks it up, the system does what it is supposed to do. Then the food stays on the shelf anyway.

566 children were poisoned by applesauce that had already been recalled. After a 2025 infant formula recall, the FDA found the recalled product still for sale in more than 175 stores across 36 states, one of them for over three weeks. The recall was never the failure. The last step was, and the last step is a person comparing a PDF against a spreadsheet by hand.

We wanted to build the check nobody was doing.

What it does

Sift runs for one institutional kitchen at a time, a school cafeteria or a restaurant. Nobody logs into it.

The kitchen's inventory software already exports a file. Sift picks that file up over SFTP or email on a schedule, reads every row without dropping any or inventing values, and compares it against a recall corpus. Before service, there is a pull sheet grouped by storage location, so staff can walk the building once.

Every line carries its evidence. Both source records are shown, the inventory row as the kitchen wrote it and the recall as the agency wrote it, with the matching text highlighted, so a reviewer can see exactly why a line was flagged.

Sift also produces the paperwork a recall creates: custody records, credit claims with itemized quantities, state reporting forms with required fields marked, and an impact assessment covering affected meals, service dates, and menu breaks.

How we built it

Python 3.12 and FastAPI on the backend, SQLite for persistence with hand-written SQL and no ORM. A Jinja2 server on port 8000 renders the printable sheets, and an optional Next.js dashboard on port 3000 provides the manager-facing views.

The code is split by responsibility: adapters/ for ingestion boundaries, matching/ for the decision gate, artifacts/ for printed documents, recalls/ for corpus management, and menu/ for meal impact.

The whole system is built around one rule: under-pulling risks a child, over-pulling wastes a case of tomatoes. So every matching rule widens rather than narrows, and there are only two statuses, PULL and HELD. There is no third state meaning "cleared," because the database will not represent one. A CHECK constraint makes automatic clearance impossible to write, not merely discouraged.

Matching is an ordered evidence ladder rather than a score. An exact barcode, or the manufacturer's own item number when the manufacturer also matches, is enough to pull. So is a lot code, a secondary code, or the recalling company plus a matching product description. A resemblance between product names alone is not, and those lines are held for a person. gate.py contains no scoring comparisons at all.

Recall data carries one of three provenance labels everywhere it appears: live, dated-snapshot, or hand-authored. If a feed is unreachable the system runs on the cached corpus and discloses its age rather than presenting stale data as current.

Against our test fixtures, 56 inventory rows matched against 1,012 recall records produce 42 PULL recommendations and 814 HELD lines. 443 tests cover the validation walkthrough, across roughly 4,700 lines of application code and 4,450 lines of tests.

Challenges we ran into

FSIS blocks programmatic access and returns HTTP 403, so meat and poultry recalls could not be fetched the way FDA records can. Rather than quietly omit them or pretend the data was live, we hand-authored that corpus and labeled it as hand-authored everywhere it surfaces.

The hardest decision was refusing to ship a confidence score. Similarity scoring fails exactly where it matters most, because "chicken breast, boneless" scores nearly identically against "chicken breast, boneless" even when the two came from different suppliers and only one is recalled. A high number would give false comfort on the weakest possible evidence.

We also had to make the system argue against itself. An item number matching a recall from a company that did not make your product is a coincidence, so it gets downgraded. A recall naming a lot code the kitchen's export never tracked is not a match either, because the absence of a contradiction is not evidence.

Accomplishments that we're proud of

The central safety rule is enforced by the schema rather than by convention. Automatic clearing is not a bug we avoided, it is a state the database cannot store.

Every pull explains itself in one sentence, because the tier is the kind of evidence and not a threshold anyone tuned. Nothing is ever deleted, so every run reconstructs identically to its original state and the history is a genuine audit trail.

And the system is honest about what it does not know. Stale data, a blocked feed, a missing price, a field no export carried: all of it is stated on the page instead of hidden behind a confident-looking number.

What we learned

The instinct to put a number on everything is usually wrong. We built toward a confidence score before realizing the honest answer was never a number, it was which piece of evidence lined up.

We also learned that the interesting failures are not in the matcher. They are in the data the kitchen never had. Most held lines exist because an export carried no barcode, not because the matching was weak, and surfacing that gap turned out to be more useful than hiding it.

What's next for Sift

Programmatic FSIS ingestion if access opens up, so that corpus stops being hand-maintained. Barcode scanning at receiving, so identifiers enter the system cleanly instead of depending on what an export happens to include. Direct ingestion of distributor notices. Narrowing the held list, which is currently the biggest demand on a reviewer's time. And multi-site rollout for districts running more than one kitchen.

Built With

Share this project:

Updates

Submission history