Inspiration

Over one in ten Americans — roughly 38 million people — has diabetes. For them the number that matters isn't calories or even total carbohydrate. It's glycemic load: how much a serving actually raises blood glucose.

Almost nothing tells you that number. Recipes don't carry it. Nutrition labels report total carbohydrate, which counts table sugar and lentils identically. And generic advice ("eat less sugar") misses the real picture — in a standard cake recipe, the flour contributes almost as much glycemic load as the sugar does.

People managing type 2 diabetes mostly don't want a diet plan. They want to keep cooking the food they already like, slightly differently. That's the tool I wanted to build.

What it does

Paste in a recipe. The app:

Calculates its glycemic load — total, and per serving, with low/medium/ high banding. Shows where the load comes from, ingredient by ingredient. This is usually the surprising part. Suggests substitutions, each with the conversion ratio you need to actually cook it — "use ¼ the amount, coconut flour is extremely absorbent" — and warnings where they matter. Lets you choose how far to go. An aggressiveness slider trades authenticity against glycemic impact. "Keep it familiar" suggests cornmeal and maple syrup (−32%). "Lowest carb" suggests almond flour and allulose (−94%). Ranks against a pluggable objective. Glycemic load is the objective that matters for diabetes and it's the default, but it isn't hardcoded. A second goal — higher protein — reuses the same candidate pool and swaps only what the ranker optimizes for. On a standard 12-serving cake: glycemic load 29.8 → 1.8 per serving, a 94% reduction from three swaps.

Critically, it also knows when to say nothing. Feed it a garden salad and it reports no worthwhile swaps, because there aren't any.

How I built it

Three public datasets that share no common keys:

531,378 recipes (HuggingFace corbt/all-recipes) — free text, normalized into ingredient tokens USDA FoodData Central SR Legacy — 7,793 foods with full macronutrient panels Atkinson et al. 2021 international glycemic index tables (Am J Clin Nutr) — 2,053 ISO 26642:2010-compliant values, extracted from the paper's 139-page supplemental PDF Linking them is TF-IDF cosine over word tokens plus a head-noun filter. USDA writes descriptions inverted, so Bread, egg is a bread and Egg, whole, raw is an egg — plain cosine similarity reliably picks the shorter, wronger one. The 198 most frequent ingredients (78.6% of all occurrences) are hand-verified rather than trusted to fuzzy matching.

Substitutions come from ingredient embeddings — PPMI over recipe co-occurrence, reduced by truncated SVD. But co-occurrence alone doesn't find substitutes: butter and sugar appear together in tens of thousands of recipes without being interchangeable, while butter and margarine almost never co-occur and are nearly perfect substitutes. So ranking combines three signals — shared culinary role, context similarity, and exclusivity, since direct co-occurrence is evidence against substitutability.

Missing glycemic values are estimated by a gradient boosting model over macronutrient composition (MAE 9.64, R² 0.483, versus 14.81 for a mean baseline). It fills only 11.4% of cases — the rest are measured or curated reference values.

Front end is Flask plus a single self-contained page.

Results Metric Value Ingredient linking (occurrence-weighted) ~87% Glycemic value coverage 96.7% of occurrences GI model, 5-fold CV MAE 9.64, R² 0.483 — mean baseline MAE 14.81, R² −0.00 — category-median baseline MAE 11.68

Challenges I ran into

The glycemic index tables aren't an ingredient dictionary. They index individually tested products — "Sugar, from organic fruits", "Jasmine white rice". Fuzzy matching linked plain sugar to a GI-23 product when sucrose is about 65. That one value would have inverted the app's entire recommendation: it would have thought sugar was low-glycemic. Fixed by curating published reference values for staples instead of matching into a product list.

A metric that flattered itself. My gold set hit 100% accuracy — but 37 of 39 test items were also in my hand-curated alias table, so it was grading the lookup table, not the model. Building a genuinely held-out set showed the real tail accuracy was ~42%. That number is in the writeup.

Context similarity isn't substitutability. The first embeddings ranked flour → shortening (0.91). Both live in baking recipes, so their context profiles are nearly identical — but nobody substitutes one for the other. This drove the role and exclusivity constraints.

A fix that made things worse. I added a penalty on branded USDA entries to stop sage matching "Sage Valley Gluten Free Cookies". It caught only all-caps brands, missed every Title Case offender, and broke two matches that had been correct. Reverted, and documented as a negative result.

Testing across cuisines caught the worst bugs. Running Indian, Thai, Mexican, West African, and Japanese recipes surfaced chickpea → meaty ham bone and corn tortilla → venison — the fuzzy matcher had mis-filed two USDA foods into the wrong category, and the swap engine trusted category. Now a swap requires an explicitly assigned culinary role; an un-roled ingredient gets no learned swap. In a diabetes tool, a missing suggestion is recoverable and a wrong one is not.

Those two original outputs are no longer reproducible — later fixes moved the pipeline underneath them — but the guard still is. Drop the role constraint and chickpea comes back with yellow pepper, vegetable broth, tarragon vinegar. Leave it in and chickpea gets no suggestion at all. That silence is the feature.

What I learned

That the interesting engineering was in measuring honestly, not in the model. Every real improvement came from finding a metric that was lying — the 94% "coverage" that meant found something, not found the right thing; the 100% gold-set accuracy that graded my own lookup table.

Also that domain knowledge beats modeling here. USDA lists stevia at 100g net carbohydrate because it measures the bulking agent — usually maltodextrin, which has a higher glycemic index than table sugar. A pure carb-minimizer would have confidently recommended a glycemically worse sweetener.

What's next

The most important gap is one of equity. The recipe corpus is 1990s American home cooking, so the tool works well on Western food and degrades on East and Southeast Asian dishes — and those cuisines belong to the populations (South Asian, East Asian, Hispanic, West African) with the highest diabetes prevalence. Testing across ten cuisines, pad thai, pho, and a sushi bowl all fell below the confidence threshold and the app honestly declined to score them. The method generalizes; this instance, on this corpus, does not yet. A production version needs a recipe corpus that reflects the diets of the people who most need the tool.

Share this project:

Updates

Submission history