-
-
KetoSense's KS monogram — cream and espresso, the app's own palette
-
One tap exports a 4-week nutrition packet for your doctor: targets against actuals, week by week
-
Any language: an Italian insalata caprese, 7 g net carbs, every ingredient USDA Verified
-
Today: net carbs, protein and calories against your own targets, with your streak and a favourite to log again
-
Credits when you need more: 40 a day on Pro, top-ups that never expire, powered by RevenueCat
-
Import any recipe and keto-ify it: sugar becomes erythritol at 0 g net carbs, saving 19 g per slice
-
Generated recipes show their work too: USDA Verified lines, and anything without a match flagged as estimated
-
The Chef builds a keto recipe around what is left of your day: creamy garlic chicken, 6.2 g net carbs per serving
-
Every meal shows its receipt: each ingredient matched to USDA data, and an honest note when a match is only close
Inspiration
Every low-carb tracker we tried had the same two problems. Logging a meal took 6-12 taps: search for the food, disambiguate it from three near-identical entries, pick a serving size, adjust the portion, save. And once the data was in, nothing happened to it — the app told you what you ate, never why a bad week happened.
Our target user has already failed on three or more of these apps. They're not confused about how to use a tracker; they're tired of tools that record and never explain.
What it does
You type or say what you ate, in any language, or take a photo of it. KetoSense extracts the macros itself — no search, no picking from a list. A natural-language input like "half an avocado" or "2% fat pudding, 25g protein" turns into a structured, portion-correct macro estimate in a couple seconds. Then it shows its work: the meal opens on its own receipt, with every ingredient, the grams used, and either the USDA food it matched or a plain "estimated" mark where nothing matched. Paste a whole recipe and you get the same receipt for each line.
AI Chef takes any dish and keto-ifies it on request — ask for pizza or pasta with Smart Swap on, and it rebuilds the recipe around a cauliflower crust or shirataki noodles, still hitting your daily macro targets. On request, it also generates a photorealistic image of the finished dish, and that photo follows the recipe everywhere — into your cookbook and into the diary entry once you log it.
Recipe import works from a link or pasted text, in any language. Paste the link to an ordinary Italian ciambellone from GialloZafferano with Keto-ify on, and it comes back as a keto cake at 4.6 g net carbs per slice: flour becomes almond flour, sugar becomes erythritol, and each swapped line says what it replaced and how much it saves (the sugar alone, 31 g per slice). Keto sweeteners count 0 g net carbs, matched to their own USDA entries, so a keto dessert isn't charged for carbs your body doesn't absorb.
For users managing a condition with a clinician, one tap on Insights exports a 4-week nutrition packet as a PDF: daily targets against actuals, week by week, and how many days were logged, ready to hand to a doctor.
The weekly report is where the app earns its name: it compares this week against your last four weeks and tells you which metric moved and by how much, instead of leaving you to guess. Uncertainty is shown, never hidden — a plain vs. amber marker on any AI-estimated value, so you always know what's a solid number and what's a best guess.
Everything is offline-first. A meal saves locally the instant you log it; sync to the cloud and across your other devices happens underneath, without blocking you mid-bite.
How we built it
React Native (Expo) and TypeScript on the client, Supabase (Postgres, Auth, Edge Functions, Row Level Security) on the backend, OpenAI's GPT-4o / GPT-4o-mini with Structured Outputs doing the actual macro extraction and recipe generation.
The offline-first sync engine is the part we're most particular about: a local Zustand store queues mutations, pushes them on a 60-second interval plus on-mutation debounce, and reconciles pulls with keyset pagination so a row can't slip through a page boundary even while the table is being written to concurrently.
Daily macro targets come from a real Mifflin-St Jeor calculation, not a guessed number:
$$ BMR = 10w + 6.25h - 5a + s $$
where w is weight in kg, h is height in cm, a is age, and s is +5 for male or −161 for female. TDEE scales that by activity level, and the keto macro split (net carbs / protein / fat) is derived from the user's stated goal — cutting, maintaining, or building muscle — not a flat template.
Every entitlement check (subscription status, daily AI-call quota) runs server-side, in the Edge Function itself, before a single token is spent on OpenAI — never trusted from the client.
Macros aren't pure LLM guesswork either. Ingredient lines get parsed and matched against our own self-hosted USDA FoodData Central database first; the LLM only steps in for the judgment calls a lookup table can't make — picking the right candidate match, estimating a gram weight for "a handful of almonds," or filling in just the one line a lookup can't resolve while every other ingredient keeps its matched USDA values. Only when nothing in a recipe resolves does the engine fall back to a single whole-recipe estimate.
The picker can only choose from the candidates search hands it, so search quality caps accuracy. Our search is hybrid. Postgres full-text matching runs alongside pgvector semantic search over embeddings of all 13,601 USDA food descriptions, and the two ranked lists are merged with reciprocal rank fusion. Keyword search alone once handed the picker spinach pasta for "fresh spinach", and every fix was another hand-written rewrite rule with its own exceptions. We measured before switching: on 122 of our harder labelled queries, the right food made the top 5 candidates 81% of the time with keyword search (99 of 122) and 94% with hybrid search (115 of 122). An HNSW index keeps the semantic lookup at about 3 ms in production.
Challenges we ran into
Our semantic search took 35 ms on a laptop and 14 to 42 seconds in production, on the same query and data. The exact vector scan walked the whole table on every call, and in production the table had grown to 217 MB against a 224 MB memory cache: an earlier storage fix had left it twice its real size. We vacuumed the table and moved to an approximate HNSW index, then checked that approximate search didn't cost accuracy. On our 296 labelled evaluation queries, the top 10 matches were identical to the exact scan's for 291 to 293 of them, and recall at 5 didn't move at all.
The AI extraction hallucinated in ways that internal math checks alone couldn't catch. One case: "half an avocado" would occasionally come back as a whole avocado's worth of macros, description and all — internally consistent, just wrong. Our first fix was a new instruction added to the model's prompt telling it to do the fraction arithmetic explicitly. It had zero effect on the actual bug, and it broke an unrelated case elsewhere: "6oz grilled chicken breast" started computing as if 6oz meant 600g. The prompt already carried a dozen other rules, and the new sentence changed behavior nowhere near where we added it.
The fix that actually held was deterministic, not another instruction to the model: a small function that checks whether the returned description preserved the fraction the user stated, and if not, triggers the extraction's existing corrective-retry path. We verified it against real calls, not just a test suite — before the fix was live, 2 of 5 real "half an avocado" requests dropped the fraction; after, 5 of 5 preserved it.
A related case: front-of-pack nutrition labels combining a percentage ("2% fat") with an absolute amount ("25g protein") could make the model solve for a physically unrealistic total package weight — 625g for what's normally a 100-200g single-serving cup — to keep a generic category density satisfied. The fix anchors the solved weight to a realistic package size for the named product and lets the derived density diverge from the generic assumption instead.
Accomplishments that we're proud of
3,600+ automated tests and a zero-any TypeScript policy across the whole codebase. Every AI-behavior fix this week was verified against real OpenAI calls before and after deploying it, not just against a mock — we know the fraction guard works because we watched it catch and correct a real drop, live.
Real billing is wired end-to-end through RevenueCat, with server-side webhook reconciliation handling edge cases most demos skip: grace periods vs. account holds, out-of-order webhook delivery, entitlement changes mid-billing-cycle. Both product types are live and real-money verified, not sandbox: the subscription, and a consumable top-up credit pack for subscribers who blow through their daily AI allowance. We bought the second one ourselves on a physical device this week and watched the credit land server-side seconds later.
We benchmarked the deterministic macro engine instead of asserting it works. A 40-case corpus — English, Hebrew, Spanish, French, and German, each crossed with two foods (chicken and egg), a stated weight vs. a stated count, and raw vs. cooked — showed every gram-stated ingredient resolving through the engine's own USDA match with no LLM estimate needed (100%, 20/20), and the large majority of count-stated ones needing a model-estimated gram weight (90%, 18/20 — there's no universal "grams per egg" or "grams per chicken breast" in a nutrition database), with 2 count cases still landing an exact USDA-portion match. Across all 40 cases, nothing fell all the way through to a full guess. Language wasn't perfectly uniform — Hebrew and German each landed 1 of their 4 count-stated cases on an exact USDA-portion match (the other 3 needed a model estimate), where English, Spanish, and French landed 0 of 4 — but the core split held everywhere: it tracks how the quantity was stated, not which food it was or which language it was written in.
What we learned
Adding a rule to the prompt to fix one case changed output in a case we never touched — the whole prompt shifts, not just the sentence you edited. The fix that held was code around the model, not more text inside it. We also learned that a passing unit test and a correct real API response are different claims. Several of this week's fixes only got confirmed once we made real paid calls before and after deploying — the test suite alone would have said everything was fine.
What's next for KetoSense
Native identity-token sign-in (Google Credential Manager) to replace the current browser-based OAuth flow. An iOS build is a real next step, not yet started — we're Android-first for this submission and haven't run this app on real iOS hardware or a simulator, so we're not claiming iOS visual parity anywhere in this write-up. Ground-truth macro-accuracy benchmarking (labeled dishes vs. measured error, not just resolution-path classification) is the deeper validation layer beyond what we've already measured across five languages.
KetoSense is live in production on Google Play — the Play Store link below installs it directly, no tester group or opt-in step required.
Built With
- deno
- eas-build
- edge-functions
- expo.io
- google-play
- google-sign-in
- gpt-4o
- jest
- lottie
- nativewind
- openai
- pgvector
- postgresql
- posthog
- react-native
- reanimated
- revenuecat
- row-level-security
- sentry
- structured-outputs
- supabase
- text-embedding-3-small
- typescript
- usda-fooddata-central
- zustand

Log in or sign up for Devpost to join the conversation.