--- name: noshery-macro-estimate description: >- Estimate the calories and macronutrients (protein, fat, carbs, fiber) of a food or a composite dish from a description, photo, or a nutrition table the user already has, then output a single Noshery import code (the text "noshery1:" followed by base64-encoded JSON). Use whenever the user wants to log food in Noshery, asks for a "Noshery import code", or says "convert this into a Noshery code". --- ## When to use Use this skill when either is true: - The user describes a food or a meal (in text or a photo) and wants it logged in Noshery, asks for a "Noshery import code", or says "convert this into a Noshery code". - The user already has a macro estimate or a nutrition table (from you earlier in the chat, from a label, from another tool) and asks to turn it into a Noshery code. In that case do not re-estimate — see "Converting a table the user already has". The end product is always exactly one line: `noshery1:` followed by base64 of a compact JSON object. The Noshery app decodes that line, shows a read-only review with an "AI estimate" disclaimer, and drops the user on a pre-filled, still editable form. ## How to estimate 1. **Classify the input.** - One single item (an apple, a slice of bread, a yogurt, a protein bar) → `type: "food"`. One object, top-level numbers. - A composite meal with more than one distinct component (a bowl, a plate, a sandwich, a stew) → `type: "dish"`. Break it into ingredients and put one object per ingredient in the `ingredients` array. 2. **Estimate weights as COOKED / as-served grams.** If the user says "150 g of raw chicken", convert to the cooked weight they will actually eat (~110 g). Every `grams` value is the portion on the plate, not the raw or dry weight. 3. **Give every nutrient value as a PORTION TOTAL, never per 100 g.** For a food, the top-level `kcal / protein / fat / carbs / fiber` are the totals for the whole `grams` portion. For a dish, each ingredient's `kcal / protein / fat / carbs / fiber` are the totals for that ingredient's `grams`. Do not send per-100 g numbers anywhere. **`carbs` is NET carbs — carbohydrate EXCLUDING fiber** (the EU nutrition label / Open Food Facts convention). Fiber is reported separately in the `fiber` field, never folded into `carbs`. If your reference data gives a US "Total Carbohydrate" figure (fiber included), subtract fiber first: `carbs = total_carbohydrate − fiber`. 4. **Use well-known reference values.** Prefer USDA-style reference data for the food per 100 g, then scale to the portion. Round sensibly (kcal to whole numbers, macros to 1 decimal). 5. **State your assumptions in prose BEFORE the code.** A short paragraph: what you assumed for portion size, cooking method, oil/sauce, brand. Then emit the code on its own line. 6. **Keep `kcal` consistent with the energy formula.** Noshery derives Calories as `kcal = protein·4 + carbs·4 + fat·9 + fiber·2`, clamped at `>= 0`. Because `carbs` is net of fiber, fiber contributes its own 2 kcal/g on top rather than discounting the carb energy. If you genuinely cannot estimate with any confidence (too little information, contradictory input), say so and ask for what you need — do not emit a code with guessed numbers. ## The JSON schema One object. Two variants, selected by `type`. | Field | Type | Required | Unit / notes | | ------------- | -------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `noshery` | int | **required** | Must be `1`. Hard version gate. | | `type` | `"food"` \| `"dish"` | **required** | Any other value is rejected. | | `name` | string | **required** | Non-empty after trim. | | `grams` | number | **required for `food`** | Portion weight, must be `> 0`. Optional for `dish` (cross-check only). | | `kcal` | number | **required for `food`** | Portion total, finite, `>= 0`. Optional for `dish` (cross-check only). | | `protein` | number | **required for `food`** | Grams, portion total, finite, `>= 0`. Optional for `dish`. | | `fat` | number | **required for `food`** | Grams, portion total, finite, `>= 0`. Optional for `dish`. | | `carbs` | number | **required for `food`** | Grams, portion total, finite, `>= 0`. Optional for `dish`. **NET carbs — carbohydrate EXCLUDING fiber** (EU / Open Food Facts convention). | | `fiber` | number | optional | Grams, portion total, finite, `>= 0`. Defaults to `0` if missing. Separate macro, not part of `carbs`; adds 2 kcal/g. | | `pieces` | number | optional, **`food` only** | Count of pieces in the portion. Defaults to `1`. Rounded, clamped to `>= 1`. Ignored for `dish`. | | `ingredients` | array | **required and non-empty for `type: "dish"`** | Ignored for `type: "food"`. | Each element of `ingredients` is an object: | Field | Type | Required | Unit / notes | | --------- | ------ | ------------ | -------------------------------------------------- | | `name` | string | **required** | Non-empty after trim. | | `grams` | number | **required** | Cooked / as-served weight, `> 0`. | | `kcal` | number | **required** | Portion total for this ingredient, finite, `>= 0`. | | `protein` | number | **required** | Grams, portion total, finite, `>= 0`. | | `fat` | number | **required** | Grams, portion total, finite, `>= 0`. | | `carbs` | number | **required** | Grams, portion total, finite, `>= 0`. NET carbs — EXCLUDING fiber. | | `fiber` | number | optional | Grams, portion total, `>= 0`. Defaults to `0`. Separate from `carbs`; adds 2 kcal/g. | Rules to hammer: - **All numbers are portion totals, not per-100 g.** Anywhere. - `grams` must be `> 0` (top-level for `food`, and every ingredient for `dish`). - **`carbs` is net of fiber.** Report carbohydrate EXCLUDING fiber in `carbs` and put fiber in `fiber`. Never add fiber into `carbs`. - `fiber` is optional and defaults to `0`. It is a separate macro that adds `2 kcal/g` on top of the net carbs (it does not discount carb energy). - `pieces` is `food`-only and defaults to `1`. - `ingredients` is required and must be non-empty when `type` is `"dish"`, and is ignored when `type` is `"food"`. - For a `dish`, the app sums the ingredients itself. Top-level `grams/kcal/protein/fat/carbs/fiber` on a dish are optional and used only for a soft cross-check warning — you can omit them. - The app rejects only malformed tokens. Implausible-but-well-formed numbers, and a calorie total that disagrees with the energy formula `protein·4 + carbs·4 + fat·9 + fiber·2` (clamped `>= 0`), are accepted with a visible warning. Still, aim for values that pass a sanity check: kcal density `<= ~1000` per 100 g, and `protein + fat + carbs + fiber <= grams`. ## Encoding step 1. Build the JSON object above as **compact JSON** — no newlines, no spaces between tokens (`JSON.stringify` with no spacing). 2. Take the UTF-8 bytes of that JSON string and **base64-encode** them. Standard base64 is fine (`+` `/` `=`); base64url works too. Do not wrap or line-break. 3. Prefix the result literally with `noshery1:`. 4. Output that one line and nothing after it. Before → after: ``` {"noshery":1,"type":"food","name":"Raw apple","grams":180,"kcal":94,"protein":0.5,"fat":0.3,"carbs":20.6,"fiber":4.4,"pieces":1} ``` becomes ``` noshery1:eyJub3NoZXJ5IjoxLCJ0eXBlIjoiZm9vZCIsIm5hbWUiOiJSYXcgYXBwbGUi... ``` The app also accepts the line wrapped in a triple-backtick fence, but a bare line is preferred. ## Worked example — food **Request:** "Log one medium raw apple." **Estimate (prose):** A medium apple with skin is roughly 180 g. USDA reference for raw apple with skin is about 52 kcal, 0.3 g protein, 0.2 g fat, 13.8 g total carbohydrate, 2.4 g fiber per 100 g — so ~11.4 g NET carbs per 100 g (13.8 − 2.4). Scaled to 180 g: ~94 kcal, 0.5 g protein, 0.3 g fat, ~20.6 g net carbs, 4.4 g fiber. One piece. (Check: 0.5·4 + 20.6·4 + 0.3·9 + 4.4·2 ≈ 96 kcal, rounded to 94.) **JSON:** ```json { "noshery": 1, "type": "food", "name": "Raw apple", "grams": 180, "kcal": 94, "protein": 0.5, "fat": 0.3, "carbs": 20.6, "fiber": 4.4, "pieces": 1 } ``` **Token:** ``` noshery1:eyJub3NoZXJ5IjoxLCJ0eXBlIjoiZm9vZCIsIm5hbWUiOiJSYXcgYXBwbGUiLCJncmFtcyI6MTgwLCJrY2FsIjo5NCwicHJvdGVpbiI6MC41LCJmYXQiOjAuMywiY2FyYnMiOjIwLjYsImZpYmVyIjo0LjQsInBpZWNlcyI6MX0= ``` ## Worked example — dish **Request:** "Chicken rice bowl: a grilled chicken breast, a scoop of white rice, some teriyaki sauce." **Estimate (prose):** Assuming a ~150 g cooked grilled chicken breast (no added oil), ~200 g cooked white rice, and ~30 g store teriyaki sauce. Per-ingredient portion totals, with `carbs` reported NET of fiber: | Ingredient | grams | kcal | protein | fat | carbs (net) | fiber | | ---------------------- | ------- | ------- | -------- | ------- | ----------- | ------- | | Chicken breast, cooked | 150 | 248 | 46.5 | 5.4 | 0 | 0 | | White rice, cooked | 200 | 260 | 5.4 | 0.6 | 54.8 | 1.2 | | Teriyaki sauce | 30 | 26 | 0.9 | 0 | 5.6 | 0.1 | | **Total** | **380** | **534** | **52.8** | **6.0** | **60.4** | **1.3** | **JSON:** ```json { "noshery": 1, "type": "dish", "name": "Chicken rice bowl", "ingredients": [ { "name": "Chicken breast, cooked", "grams": 150, "kcal": 248, "protein": 46.5, "fat": 5.4, "carbs": 0, "fiber": 0 }, { "name": "White rice, cooked", "grams": 200, "kcal": 260, "protein": 5.4, "fat": 0.6, "carbs": 54.8, "fiber": 1.2 }, { "name": "Teriyaki sauce", "grams": 30, "kcal": 26, "protein": 0.9, "fat": 0, "carbs": 5.6, "fiber": 0.1 } ] } ``` **Token:** ``` noshery1:eyJub3NoZXJ5IjoxLCJ0eXBlIjoiZGlzaCIsIm5hbWUiOiJDaGlja2VuIHJpY2UgYm93bCIsImluZ3JlZGllbnRzIjpbeyJuYW1lIjoiQ2hpY2tlbiBicmVhc3QsIGNvb2tlZCIsImdyYW1zIjoxNTAsImtjYWwiOjI0OCwicHJvdGVpbiI6NDYuNSwiZmF0Ijo1LjQsImNhcmJzIjowLCJmaWJlciI6MH0seyJuYW1lIjoiV2hpdGUgcmljZSwgY29va2VkIiwiZ3JhbXMiOjIwMCwia2NhbCI6MjYwLCJwcm90ZWluIjo1LjQsImZhdCI6MC42LCJjYXJicyI6NTQuOCwiZmliZXIiOjEuMn0seyJuYW1lIjoiVGVyaXlha2kgc2F1Y2UiLCJncmFtcyI6MzAsImtjYWwiOjI2LCJwcm90ZWluIjowLjksImZhdCI6MCwiY2FyYnMiOjUuNiwiZmliZXIiOjAuMX1dfQ== ``` ## Converting a table the user already has If the user pastes or points to an existing macro breakdown (a table you produced earlier, a label, another tool's output): - **Do not re-estimate.** Keep their numbers as given. Only fill in a value yourself if a required field is missing. - Decide `type`: one row / one item → `food`; several component rows → `dish`. - For a `dish`, map **one table row to one schema ingredient**. Use the row's food name for `name`, and the row's portion / serving / weight column for `grams`. If a row lists per-100 g values, multiply by `grams / 100` to get the portion totals the schema wants. - For a `food`, take the single row's portion column as `grams` and its values as the top-level totals. - `fiber` missing → omit it (defaults to `0`). `pieces` only if the user gave a count. - **If the source uses a US "Total Carbohydrate" figure that includes fiber** (US labels, USDA), subtract the fiber row so `carbs` is net: `carbs = total_carbohydrate − fiber`. EU labels and Open Food Facts already list carbs net of fiber — use those as-is. - Then encode and emit the `noshery1:` line as above. If the table has per-100 g numbers and no portion/serving column at all, ask the user for the portion weight rather than guessing. ## Output rules - Emit exactly one `noshery1:` line. Nothing after it — no trailing sentence, no "let me know if…", no closing fence text on that line. - Do not put commentary on the `noshery1:` line itself. All prose (assumptions, the estimate, the table) goes above it. - A bare line is preferred; a line wrapped in a plain triple-backtick fence is also accepted by the app. - If you cannot estimate with any real confidence, say that plainly and ask for the missing detail instead of emitting a code with invented numbers.