POSTS
Ingradient: building the cooking tool I've always wanted
Years ago, a cooking blog got me thinking about reverse-engineering recipes. I thought I could build an application for that. For those of us privileged enough to contribute, open source software is an endless time sync. I kept putting off the idea for some other shiny pet project–usually JSHint.
With my work on JSHint finally maybe wrapping up, and with a whole lot of extra time due to coronavirus-inspired lifestyle changes, I finally set out to build the thing in March. Today, it’s done. It’s called “Ingradient,” and you can find it at ingradient.com.
To cap things off, I thought I’d write a bit about what I built and what I learned.
What is it?
Ingradient is an application designed to help you recreate food products in your kitchen by estimating the ingredient measures. Pick a product you’d like to make, dial in the nutrition facts along with the ingredients, and Ingradient will spit out measurements for each ingredient.
How does it work?
Ingradient works by combining the information you provide with nutrition data published by the US Department of Agriculture. It creates a system of equations that relates the nutrition information of the ingredients with that of the final product. By solving for the variables in that system, it determines the mass of each ingredient.
So, let’s say you want to make a granola bar. Not just any granola bar, but a Nature Valley™ Peanut Butter Crunchy Granola Bar.
You know you’ll need oats, sugar, canola oil, and peanut butter. General Mills also uses rice flour, brown sugar syrup, and–of course–soy lecithin, but you’re happy to skip that stuff. But still, you don’t know how much of each ingredient you need.
…but you do know a bit about its nutrients. One such bar has 190 calories, 8 grams of fat, 2 grams of fiber, and 4 grams of protein. You put all that into Ingradient, and it looks up the amount of calories, fat, fiber, and protein in each ingredient. It builds a system of equations from all that data:
3.79×Moats + 3.87×Msugar + 8.84×Moil + 5.98×MPB = 190 calories
0.07×Moats + 0.00×Msugar + 1.00×Moil + 0.51×MPB = 8 grams of fat
0.10×Moats + 0.00×Msugar + 0.00×Moil + 0.05×MPB = 2 grams of fiber
0.13×Moats + 0.00×Msugar + 0.00×Moil + 0.22×MPB = 4 grams of protein
With four variables and four equations, there is exactly one possible value for each variable. Ingradient uses Gaussian elimination to find the values and reports them back to you:
Moats = 15 grams
Msugar = 15 grams
Moil = 2 grams
MPB = 9 grams
How doesn’t it work?
It’s not perfect, though, not by a long shot. This approach only works if you can provide a nutrient measure (e.g. carbohydrates, saturated fat, etc.) for every ingredient. For products with a bunch of ingredients, you may not have enough nutrients. Even before you run out of nutrients, you’ll find that some of them (e.g. sodium and cholesterol) are present in trace amounts, making them weak signals for inferring measurements.
In the application’s guide, I encourage users to stick with macronutrients and get creative with ingredient simplification. For example, a product that has sugar, corn syrup, and high-fructose corn syrup can probably be approximated with sugar alone (most cooks aren’t likely to have HFCS on hand, anyway).
The most jarring problem, though, is that the application sometimes provides incoherent answers. Want to know how to make a Carrot Cake Larabar? Ingradient will tell you to use 54 grams of dates, 54 grams of almonds, and -29 grams of walnuts. While that solution makes sense on paper, it’s useless in the kitchen. It’s hard to predict when you’ll get an answer like this, but the further your input strays from the actual content, the more likely you are to get gibberish.
The application was “just for fun,” and I’m hoping folks have the same expectations for its output.
Origin of the idea
In 2010, J. Kenji López-Alt wrote an article titled The Ins-n-Outs of an In-N-Out Double-Double, Animal-Style. In it, he used stoichiometry to reverse-engineer the contents of a sandwich dressing based on the ingredients and nutrition data provided by the restaurant that sold it.
This always seemed like a useful application of the chemistry process–one that could generalize to all sorts of other cooking experiments. It’s just so labor-intensive! I thought I could teach a computer to do it for me.
Although I originally envisioned a command-line tool that would never see the light of day, I eventually decided to make this a full-fledged web application. Doing so would allow my work to benefit someone else out there, give me an opportunity to brush up on some new technologies, and let me proselytize for free software a bit.
Lessons
I’m a web developer by trade, but my work in recent years has been focused more on web standards and less on soup-to-nuts application development. That made this project an opportunity to study more practical topics. For instance, even though I’ve collaborated with browser developers to implement Service Workers, I haven’t had much of an opportunity to use them. I finally got to experiment with Service Workers a bit, and in “progressive web app” fashion, Ingradient is offline-enabled. Building a search interface over a large dataset like the USDA’s nutrition data also gave me an opportunity to work with IndexedDB.
The front-end tooling ecosystem has grown a ton in recent years, and when people were complaining about “JavaScript fatigue” as far back as 2015, that’s saying something. Building an application of my own design let me branch out and play with a bunch of new-to-me toys. That started with an initial experiment with Cycle.js. While I appreciate the concept, when it comes to development ergonomics, it doesn’t hold a candle to React. Trouble is, React’s developed by Facebook, and I really can’t abide by that company for much of anything. This makes me especially thankful for Preact which offers the same development experience with none of the ethical baggage. Along those same lines, JSX is a non-standard programming language that’s always left a bad taste in my mouth. I’ve had the htm project bookmarked for years, and I was thrilled for the chance to write in standards-compliant JavaScript (see also this early experiment gluing together Cycle.js and htm.
Okay, okay. I’ll get off my soapbox, now.
The most important lessons were about my personal shortsightedness. Even though I intentionally set out to make an application for use by others, my initial prototype was inscrutable. I’d been thinking about this project for years, so naturally, I knew how to use it. A few user tests made it clear that this was not intuitive and that folks would need a few hints to understand what they were looking at. It’s been years since I formally studied human-computer interaction, and my lack of practice was obvious.
Success
Honestly, I’m a bit disappointed by the ways Ingradient fails. It’s mostly the negative measurements that are getting me down; that seems like the biggest detriment to the tool’s usefulness. I’m still wondering if I can avoid those impossible solutions. If anyone out there has ideas (maybe an algorithm to nudge the input until it can be solved with practical values), then I’m all ears.
That said, the application proves the concept, and it usually gives a good starting point for home-made recreations. That’s why I’m calling it a day. It’s rare for me to have a definitive end to a side-project, so this finality is much appreciated.
Anyway, here’s hoping Ingradient helps you build a few recipes of your own!