Build Cal AI
KINDAreplaces $9.99/mosaves $119.88/yrback to the verdict
A private photo food log whose hard problems are presenting an estimate honestly when the published error is tens of percent, making the correction step the centre of the interface rather than a dialogue nobody opens, and learning a personal calibration from corrections without pretending it is precision.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application on the machine that serves your phone.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - API keypay per image; a few meals a day is small change, and the ceiling in this build is what stops it becoming otherwise
Why The estimate is one call per photograph, and this is the only part of the build that leaves your machine or costs money.
Get it Create a key with the provider you prefer and set a spend limit in their console.
Verify
One photograph returns valid structured output - have readyfree
Why The whole design stands or falls on the capture being two taps, and that is only testable on the device you will actually use at dinner.
Get it Open the app on the phone and add it to the home screen.
Verify
Two taps from the icon produce a stored photograph - have readyfree
Why The camera will not open on an insecure origin from another device, which is the single most common reason this kind of build stalls on day one.
Get it A local certificate authority for your network, or a hostname with a real certificate pointed at the local address.
Verify
The phone opens the app without a certificate warning and the camera works - decidefree
Why The literature is clear that the error is large and directional, and deciding presentation rules afterwards means deciding them from whatever looked nicest.
Get it Write down that ranges are mandatory, that the middle value is never shown alone, and what the minimum correction count for calibration is.
Verify
All three rules appear in the README and are asserted in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- meals(id, at, photo_path, note, model_id, prompt_version, raw_response_json, api_cost_minor, state) - items(id, meal_id, name, grams_low, grams_mid, grams_high, kcal_low, kcal_mid, kcal_high, protein_g, carbs_g, fat_g, confidence, source) · source is model, edited or manual - corrections(id, item_id, field, model_value, user_value, at) · the record of every time the estimate was wrong, which is the only calibration data that exists - calibration(period_start, period_end, n_items, median_ratio, iqr_low, iqr_high) · computed from corrections, shown with its sample size or not shown - targets(id, kind, value, set_at, hidden) · optional, and hideable, because a target is not a requirement - settings(photo_retention_days, monthly_cost_ceiling_minor, numbers_hidden, model_id) - Every displayed figure carries a low and a high. There is no column anywhere in this schema holding a single authoritative calorie value.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
MODEL_API_KEYsecret | required | sk-...redacted | The multimodal model. Give it a provider-side spend limit as well as the ceiling in this app. |
MODEL_ID | required | a multimodal model identifier | Stored with every meal, because a different model gives different numbers. |
DATABASE_PATH | required | ./data/food.db | Meals, items, corrections and calibration. Back this up; the photographs matter less. |
PHOTO_DIR | required | ./data/photos | Meal photographs. Deleted after the retention period, rows retained. |
PHOTO_RETENTION_DAYS | required | 30 | How long pictures of your meals are kept. Shorter is a reasonable default. |
MONTHLY_COST_CEILING | required | 5.00 | Estimation stops at this figure for the month and manual entry continues. |
CALIBRATION_MIN_CORRECTIONS | required | 40 | Below this, no calibration figure is shown at all. |
The build, in order
A camera that opens, and a meal that stays yours
Photographing a meal takes one tap and nothing leaves the machine except the model call.
This number decides whether the app survives the first week.
Files
src/app/page.tsxsrc/components/Capture.tsxA phone photograph is several megabytes of detail the model does not use.
Files
src/components/Capture.tsxThe camera will not open otherwise, and this trips everyone once.
Files
docs/lan-setup.md
done when · tick each as it passeswatch out- A capture flow that takes three taps and two seconds is abandoned by the fourth day, which is the actual reason paid trackers win, so any friction you add here costs more than any accuracy you gain elsewhere. Measure the taps from opening the app to a stored photograph and keep it at two.
An estimate that arrives as a range
The model returns items with low, middle and high values, validated, and never invented on failure.
The range is the honest output of this measurement.
Files
src/lib/estimate.tssrc/lib/schema.tsNever fabricate a number when the response did not parse.
Files
src/lib/estimate.tstests/estimate.test.tsSo a strange estimate can be explained rather than argued about.
Files
src/lib/meals.ts
done when · tick each as it passeswatch out- Asking for one number per food and displaying it produces an interface that looks like a scale reading, and people then plan around a figure whose published error on mixed meals runs into the tens of percent. Ask for a range, store all three values, and let the interface have no way of showing the middle one on its own.
The correction step is the product
Confirming a meal means editing what the model got wrong, and the edit is recorded.
An edit step people can skip is an edit step people skip.
Files
src/app/confirm/page.tsxThat is where the energy a photograph cannot see is hiding.
Files
src/app/confirm/page.tsxIt is the only data you will ever have about your own errors.
Files
src/lib/corrections.tstests/corrections.test.ts
done when · tick each as it passeswatch out- Saving the estimate straight to the log with an edit button somewhere afterwards means nobody ever edits, and the log fills with numbers that the published evidence says are biased downwards and get worse as the plate gets bigger. Put the edit between the photograph and the log, and ask about oil and sauce by name because that is where the missing energy is.
Calibration from your own corrections
The app learns how wrong it usually is for you, and says so with the sample size.
Old corrections describe an older diet and an older model.
Files
src/lib/calibration.tstests/calibration.test.tsA ratio from eight corrections is a rumour.
Files
src/lib/calibration.tsStored numbers are what the model said and what you said.
Files
src/lib/meals.ts
done when · tick each as it passeswatch out- Quietly multiplying future estimates by a factor learned from a dozen corrections produces numbers that look better and are not, because the factor was fitted on the meals you happened to correct and those are the ones that looked wrong. Display the calibration with its sample size, let the user decide whether to apply it, and never fold it into a stored value.
Weeks, not days, and a way outproduct builder
History is presented at the timescale the error allows, the photographs expire, and the spend has a ceiling.
Day to day movement is mostly measurement error.
Files
src/app/history/page.tsxThe picture stops being useful long before the log does.
Files
src/lib/retention.tsA rented model with no ceiling is a surprise bill.
Files
src/lib/cost.tstests/cost.test.ts
done when · tick each as it passeswatch out- A daily total displayed to the calorie with a target line invites someone to act on a difference of a couple of hundred between two days, when the published per-meal error is far larger than that difference, so the interface is reporting noise as a result. Lead with a rolling average and a band, and keep day figures behind a tap with their uncertainty attached.
That is the whole plan for Cal AI. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Anything clinical, including carbohydrate counting for dosing.
- Barcode scanning and a packaged food database.
- Health platform synchronisation.
- Streaks, notifications and coaching copy.
- A native app with widgets, notifications and instant cold start
- Barcode scanning against a maintained packaged-food database
- HealthKit / Google Fit / Apple Watch sync
- Streaks, coaching copy and the habit scaffolding that makes tracking stick
- Whatever portion-size calibration they have learned from millions of corrected logs
- A reference object in the photograph, which the literature suggests helps less than supplying ingredients does
- Recipes you cook often, stored once and logged by name, which removes the model from the meals you eat most
- A monthly report of how far the model was from your corrections, which is the only accuracy number that means anything here
Need the files? The project pack on the verdict page hands your agent the whole brief · more wellness.