Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,11 @@ The agent must not translate by inventing nutrition values or silently substitut

The parser accepts kilograms, grams, millilitres, pieces, fractions, and explicit per-piece grams.
English and Russian size words such as `small` and `небольшой` remain valid syntax, but a size word
does not supply a packaged estimate. Piece grams come only from explicit user input or serving data
on the resolved cached/pinned/API food record. Assumptions identify the provider, source field, and
returned value. When serving data is absent, structured `piece_weight_unknown` asks for exact
grams. Explicit grams always win, including `3 pieces FOOD at 38g` → `114g`.
does not make nomnom guess a weight. By default, piece grams come only from explicit user input or
serving data on the resolved cached/pinned/API food record. Assumptions identify the provider,
source field, and returned value. When serving data is absent, the default `strict` portion policy
keeps the existing `piece_weight_unknown` response and writes no log. Explicit grams always win,
including `3 pieces FOOD at 38g` → `114g`.

```sh
nomnom log --parse "bread 2 pieces at 40g" --json
Expand All @@ -268,6 +269,57 @@ Supported dish prefixes split only the ingredients the user stated. nomnom never
or another ingredient. Millilitres use a resolved density when available and otherwise retain the
documented 1 g/ml conversion.

### Opt-in external portion estimates

An external agent may supply masses for unresolved counts, fractions, and size descriptions. This
is opt-in: use `--portion-policy estimate` together with inline `--portion-estimates` JSON. nomnom
does not contain an LLM, generate a mass, bundle portion weights, or treat the supplied mass as
measured or source-backed. It uses each supplied central `grams` value in the existing deterministic
nutrition calculator; the lower/upper range is provenance only and is not used to calculate a
nutrition interval.

The payload has exactly one top-level `items` array. Every entry has exactly these fields:

- `item_index`: zero-based position after nomnom splits the original `--parse` text;
- `input`: the exact trimmed item phrase from that text, matched byte-for-byte;
- `grams`, `lower_grams`, `upper_grams`: finite nonnegative numbers satisfying
`lower_grams <= grams <= upper_grams`;
- `confidence`: finite number from 0 through 1;
- `method`: the literal string `agent_estimate`;
- `assumption`: a nonempty human-readable explanation.

For example:

```sh
nomnom log --parse \
"3 small fried eggs, half small tomato, half small onion, whole wheat bread 180 g, milk 110 g, 15 dates" \
--portion-policy estimate \
--portion-estimates '{"items":[
{"item_index":0,"input":"3 small fried eggs","grams":135,"lower_grams":120,"upper_grams":150,"confidence":0.72,"method":"agent_estimate","assumption":"Three small fried eggs estimated at 45 g each."},
{"item_index":1,"input":"half small tomato","grams":35,"lower_grams":25,"upper_grams":45,"confidence":0.65,"method":"agent_estimate","assumption":"Half of a small tomato estimated at 35 g."},
{"item_index":2,"input":"half small onion","grams":30,"lower_grams":20,"upper_grams":40,"confidence":0.63,"method":"agent_estimate","assumption":"Half of a small onion estimated at 30 g."},
{"item_index":5,"input":"15 dates","grams":120,"lower_grams":90,"upper_grams":150,"confidence":0.58,"method":"agent_estimate","assumption":"Fifteen dates estimated at 8 g each."}
]}' --json
```

Every unresolved fuzzy item must have exactly one matching entry. Missing, extra, duplicate, or
non-exact mappings and malformed/invalid values reject the complete meal without a log write;
entries for explicit gram items are also rejected. Only inline JSON is supported. `ask` returns a
structured `portion_estimate_required` response with the exact index/input and writes nothing.
`strict` remains the default. Set a persistent default in user config or override it in the
environment:

```toml
[resolution]
portion_policy = "ask" # or "estimate"; default is "strict"
```

`NOMNOM_PORTION_POLICY` accepts `strict`, `ask`, or `estimate`; the CLI flag has highest precedence.
Estimated log items persist `approximate=true`, `portion_provenance=agent_estimate`, and the full
`portion_estimate` object next to food-resolution provenance. Log JSON and date stats expose these
fields. Human output gives one correction prompt toward scale grams, a photo, or a barcode. Existing
logs without these additive fields remain readable.

### Adding a new language

Provide an agent-side translator that emits the canonical contract and, when desired, create
Expand Down Expand Up @@ -300,6 +352,10 @@ an `error` object and exit with status 2. Important codes include:
- `invalid_source_note` / `invalid_nutrition`: correct the extracted label facts; failed captures
write nothing.
- `piece_weight_unknown`: ask for grams or add a verified `--piece-grams` value.
- `portion_estimate_required` / `portion_estimate_missing`: supply exactly matched external estimate
metadata or use explicit grams; nothing is partially logged.
- `portion_estimates_malformed` / `portion_estimate_invalid` /
`portion_estimate_mismatch`: correct the complete inline payload and retry.
- `alias_target_not_found`: add/resolve the exact cached target or remove the stale alias.
- `openfoodfacts_unavailable` / `usda_unavailable`: retry later or use a manual label.

Expand Down
57 changes: 57 additions & 0 deletions docs/plans.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
# Plans

## Issue #31 Source
- Task: Add opt-in externally agent-estimated fuzzy portions with explicit provenance.
- Canonical input: GitHub issue #31 latest product decision and the user's strict TDD, atomicity, smoke, commit, and no-push requirements.
- Repo context: free-text parsing, provider resolution, nutrition scaling, log JSON persistence, stats, config/CLI policy, README, and agent skill.
- Last updated: 2026-07-21

## Issue #31 Assumptions
- The estimate payload is inline JSON with an `items` array; each entry identifies one fuzzy phrase by both zero-based `item_index` and exact parser `input`, preventing similarity matching.
- Every estimate carries finite nonnegative `grams`, `lower_grams`, and `upper_grams`, confidence in `0..1`, the literal method `agent_estimate`, and a nonempty human-readable assumption; `lower_grams <= grams <= upper_grams`.
- Portion policy defaults to `strict`; `ask` returns an actionable structured request without writing, and `estimate` is the only policy that accepts a complete valid external payload.
- Existing `items_json` can carry additive portion provenance without a schema migration; nutrition continues to use only central `grams` through `scale_food`.

## Issue #31 Milestone Order
| ID | Title | Depends on | Status |
| --- | --- | --- | --- |
| M28 | Freeze fuzzy estimate, mapping, policy, and atomicity contracts | M27 | [x] |
| M29 | Implement external estimate parsing and additive provenance | M28 | [x] |
| M30 | Document, validate, smoke, audit, and commit | M29 | [x] |

## M28. Freeze issue #31 contracts `[x]`
### Goal
- Focused tests specify strict compatibility, exact all-or-nothing estimate mapping, full breakfast persistence/stats, explicit-gram precedence, config policy, and legacy-log readability before production changes.

### Validation
```sh
PYTHONPATH=. pytest -q tests/test_portions.py tests/test_cli.py tests/test_config.py tests/test_db.py
```

### Stop-and-Fix Rule
- Record the expected RED failures before changing production parser, model, config, CLI, or persistence behavior.

## M29. Implement external estimate parsing and additive provenance `[x]`
### Goal
- `strict|ask|estimate` policy and exact structured payload validation allow only complete agent-supplied fuzzy masses, persist explicit approximate provenance, and preserve central deterministic nutrition calculation.

### Validation
```sh
PYTHONPATH=. pytest -q tests/test_portions.py tests/test_parser.py tests/test_cli.py tests/test_config.py tests/test_db.py
```

### Stop-and-Fix Rule
- Any partial write/application, imprecise match, locally generated weight, explicit-gram regression, or issue #29 identity regression blocks documentation work.

## M30. Document, validate, smoke, audit, and commit `[x]`
### Goal
- README and agent guidance publish the exact schema, correction route, and non-measured semantics; all focused/full tests, Ruff, disposable mocked-provider CLI smoke, diff audit, and one scoped conventional commit pass.

### Validation
```sh
PYTHONPATH=. pytest -q
ruff check .
git diff --check
```

### Stop-and-Fix Rule
- Do not commit until full validation passes, the exact breakfast smoke uses only a temporary database and mocked providers, and the diff is scoped to issue #31.

## Issue #29 Source
- Task: Ensure unbranded foods resolve only to truthful generic proxies, never arbitrary branded exact products.
- Canonical input: GitHub issue #29 and the user's strict TDD, smoke, commit, and no-push requirements.
Expand Down
16 changes: 13 additions & 3 deletions docs/status.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Status

## Snapshot
- Current phase: issue #29 implemented and independently verified
- Current phase: issue #31 implemented and verified
- Plan file: `docs/plans.md`
- Status: green
- Last updated: 2026-07-21

## Done
- Completed issue #31 with strict-by-default external portion policy, exact index-plus-input estimate mapping, full range/confidence/assumption validation, central-grams nutrition, and additive approximate provenance.
- Recorded the required RED run (16 expected failures, 1 strict-path pass), then passed 114 focused tests, 224 full tests, Ruff, diff checks, and the exact six-item checkout CLI smoke.
- Verified four breakfast items persist as `agent_estimate`, bread 180 g and milk 110 g remain non-approximate, date stats expose all portion fields, and the temporary smoke database is removed.
- Completed issue #29 with intent-aware exact identity, USDA Foundation/SR Legacy preference, strict OFF proxy type matching, branded-proxy provenance, and cache intent isolation.
- Recorded RED runs for 16 primary regressions plus two cache edges and missing-category evidence, then passed 114 focused tests, 204 full tests, and Ruff.
- Ran all six literal translated inputs against mocked providers in a fresh temporary data directory; all six were explicit `generic_proxy` rows with source, brand, barcode, and assumption.
Expand All @@ -27,12 +30,15 @@
- Passed 177 tests, Ruff, checkout-import guard, shell syntax, and the disposable checkout-built installer/provider-stub smoke.

## In Progress
- No implementation work pending; issue #29 is verified and committed locally.
- No implementation work pending; issue #31 is ready for the scoped local commit.

## Next
- Do not push or open a pull request unless the user requests it separately.
- Do not push, merge, or open a pull request unless the user requests it separately.

## Decisions Made
- Issue #31: require zero-based `item_index` plus exact `input` in every estimate entry; never fuzzy-match estimate metadata to parsed foods.
- Issue #31: require central/lower/upper grams, confidence, literal `agent_estimate`, and a nonempty assumption for every unresolved fuzzy portion.
- Issue #31: keep log provenance inside additive item JSON so schema v4 and old log readability remain unchanged.
- Issue #29: resolution mode follows user identity intent plus source identity, never candidate confidence alone.
- Issue #29: generic USDA searches send the documented `dataType` array for Foundation and SR Legacy and rank eligible non-branded provenance ahead of branded payload rows.
- Issue #29: a branded OFF candidate needs product-name token coverage plus category/type evidence to serve only as a labelled generic proxy.
Expand Down Expand Up @@ -89,6 +95,8 @@ ruff check .
| 2026-07-21 | M24 | README, skill, changed code/tests, disposable installed checkout | focused/full pytest; Ruff; diff check; literal temp-DB smoke | 40/189 pass; clean; smoke 150 kcal | local commit |
| 2026-07-21 | M25 | resolver/provider regression tests | focused pytest | RED: 16 primary failures; 2 cache failures; 1 category-evidence failure | M26 |
| 2026-07-21 | M26–M27 | resolver, USDA, docs, skill, tests | `pytest -q`; `ruff check .`; temp-data mocked-provider smoke | 114 focused; 204 full; clean; 6/6 generic proxies | local commit |
| 2026-07-21 | M28 | issue #31 tests and planning records | targeted pytest before production edits | RED: 16 expected failures; 1 strict-path pass | M29 |
| 2026-07-21 | M29–M30 | portion validation/parser/model/CLI/stats, docs, skill, tests | focused/full pytest; Ruff; diff check; exact temp-DB checkout smoke | 114 focused; 224 full; clean; 4 estimates + 2 explicit grams | local commit |

## Smoke / Demo Checklist
- [x] Fresh temp DB: help/version, capture label, alias, log, and invalid structured capture error.
Expand All @@ -109,3 +117,5 @@ ruff check .
- [x] Safe no-key miss returns `food_needs_source` with photo/barcode/label/cache options and optional USDA.
- [x] Literal parsed/direct `2026-07-20` logs persist local noon and date stats return only that local day.
- [x] Issue #29 literal six-item temp-data smoke returns only explicit generic proxies with audited OFF candidate identity.
- [x] Issue #31 exact breakfast logs atomically with four explicit agent estimates and two unflagged explicit-gram items.
- [x] Issue #31 date stats preserve the four portion provenance objects and one concise correction route.
28 changes: 28 additions & 0 deletions docs/test-plan.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Test Plan

## Issue #31 Validation
- In scope: `strict|ask|estimate` policy precedence, exact inline-JSON schema/mapping, fuzzy descriptor/fraction/bare-count parsing, all-or-nothing validation and writes, portion provenance in log/stats/text, explicit grams, old logs, docs, and agent guidance.
- Fixtures: temporary SQLite databases and monkeypatched deterministic generic provider foods only; no live traffic, user database, bundled weights, or repository food data.
- Exact acceptance literal: `3 small fried eggs, half small tomato, half small onion, whole wheat bread 180 g, milk 110 g, 15 dates`.
- Every fuzzy estimate entry must contain exact `item_index` and `input`, finite nonnegative `grams`, `lower_grams`, `upper_grams`, confidence in `0..1`, `method: agent_estimate`, and a nonempty assumption.

### Issue #31 Negative / Edge Cases
- Default strict behavior remains `piece_weight_unknown` and produces no log write.
- Malformed JSON, missing/extra/duplicate/mismatched entries, fuzzy estimate attached to explicit grams, invalid method/assumption, non-finite/negative values, invalid range ordering, and confidence outside `0..1` all fail atomically.
- `ask` exposes exact unresolved phrase identifiers and correction routes without applying estimates.
- Explicit grams and per-piece grams stay non-fuzzy; legacy item JSON without portion fields remains readable.

### Issue #31 Acceptance Gates
- [x] Focused tests observed RED before production changes — 16 expected failures, 1 pass.
- [x] Focused parser/config/CLI/database tests pass — 114 passed.
- [x] Full `PYTHONPATH=. pytest -q` passes — 224 passed.
- [x] `ruff check .` and `git diff --check` pass.
- [x] Disposable exact-breakfast CLI smoke passes with four estimated items and two explicit-gram items.
- [x] One scoped conventional commit exists; base remains `055e3d2`; nothing is pushed.

### Issue #31 Command Matrix
```sh
PYTHONPATH=. pytest -q tests/test_portions.py tests/test_parser.py tests/test_cli.py tests/test_config.py tests/test_db.py
PYTHONPATH=. pytest -q
ruff check .
git diff --check
```

## Issue #29 Validation
- In scope: resolver intent, OFF safe generic proxy semantics, USDA request/ranking provenance, exact barcode/brand/pin/alias paths, cache isolation, generic policy writes, CLI/API JSON, and literal translated inputs.
- Fixtures: mocked OFF and USDA payloads only, temporary SQLite user databases, no live traffic or personal data.
Expand Down
Loading
Loading