Skip to content

feat(data): repair catalog identity pins and aliases for pantry reach - #192

Merged
SamGu-NRX merged 15 commits into
alphafrom
obv/burst10-fridgeluck-pantry-reach
Oct 16, 2026
Merged

SamGu-NRX merged 15 commits into
alphafrom
obv/burst10-fridgeluck-pantry-reach

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

What changed and why

Goal: real pantries reach exact catalog matches through the app's own lookup. The v2 reach report showed exact-match medians at or near zero for every household policy, with lost lines caused by search misses, wrong-ID matches, and unbridged recipe keys.

Catalog identity repairs (13 files): repinned 14 vocabulary FDC citations (banana, sugar, cinnamon, lentils → 172420, bacon, almonds, heavy cream → Atwater 2047 energy, white rice, canned tomatoes, pork chop, chicken thigh, coconut milk → 170172, chicken_thighs → 2706027), imported 9 missing FDC rows, and applied 36 curated catalog edits (display names, aliases, the chives description). The canonical SQLite grew 950 → 959 records (3,942 unique aliases); the shipped SQLite, static nutrition dataset, recipe bundle, and manifest SHA-256 are rebuilt and refreshed.

Search-first identity: typed queries now resolve to intended identities — eggs → Egg, chicken breasts → Chicken Breast, basmati rice → Basmati Rice, canned tomatoes → Canned Tomatoes, brown sugar → Brown Sugar, coconut milk → the cooking row, ground beef → Ground Beef, chicken thighs → Chicken Thigh.

Bridge union semantics: RecipeCatalogIngredientBridge.rebuild now unions independently resolved legacy-ID and FDC-ID evidence (a key carrying both bridges both id sets, provenance legacy_id+fdc_id, new resolvedByLegacyAndFDC coverage count) instead of strict legacy-first precedence, with a new test for the both-evidence case.

Measured result (evaluation/catalog-reach/reach_report_v3.py, before → after over the ten pantries): exact medians 0.5/0.5/0/0/0 → 4.5/1.5/0/2.5/2.0 (omnivore/vegetarian/vegan/gluten-free/dairy-free); near medians 101/54/22.5/42.5/44.5 → 162.5/93.5/29/76/78. Lost required lines: 535 search misses → 0; 2,037 wrong-ID matches → 0; 1,220 lines unbridged (unchanged, disclosed); 28,123 → 28,131 genuinely-not-in-pantry.

How I checked it (commands and results)

  • uv run usda validate — 959 records, 3,942 unique aliases, 14 required terms, all pass.
  • python3 scripts/data/catalog/validate_bundle.py — pass. Recipe gate suite — pass. validate_corpus.py — 94.48% of 762 recipes resolved across all 14 bands. Taste-lab fixtures replayed exactly; taste-lab Python suite 17/17.
  • reach_report_v3.py before/after replay — numbers above; alias and search resolutions verified directly against the rebuilt SQLite.
  • swiftc -parse on RecipeCatalogIngredientBridge.swift and SubstitutionService.swift — clean (syntax only).
  • Linux repro against the production allocator (PlanAllocating.allocate over the shipped recipes-v2 bundle + USDA catalog, 762 recipes / 345 bridge rows): with the pre-fix filter and onions blocked via 566, the onion recipe (primary id 5) was allocated (picked=1 — the GoalPath failure shape); with the fix it is refused (picked=2). Full weekly horizon: the union screen rejects all 195 allergen-bearing candidates; every dinner clean.
  • Hosted macOS CI on this branch's history: run 38856690375 (head 6ff692e) isolated a coverage-counter bug (union key decremented resolvedByFDCID without incrementing it, resolvedKeys summed to 0) — fixed on a24357d; run 38858662575 (head d15ccea) isolated the plan-path allergen-screen gap and the stale taste goldens — fixed on 9f20529/a04c6351.
  • PR body/commits blob-verified via git show HEAD:<path> after every push.

Safety screen correction this PR carries

Truthful bridging exposed a plan-path gap the pre-existing GoalPath assertion pins: the catalog loader resolved each line key to ONE canonical bridge id (LIMIT 1), hiding a union-bridged key's other identities, and both plan-path allergen screens ignored blocked ingredients on optional lines — unlike the recommend path's documented whole-recipe exclusion. PlanDataAdapter now screens every id each line key bridges to against the raw allergen ids, and both the legacy-candidate screen and the allocator's restrictionClean block on any line, required or not. Allergens are hard filters; the planner never waives an exclusion to widen the pool. A final CI round (432f4b6c) tightened the allocator's restrictionClean further: screening the line's primary id alone still missed a blocked id that rides only in the key's bridge union (the onion key bridges legacy 5 and USDA 566; the LIMIT-1 primary is 5), so the filter now also refuses any line whose bridgedIngredientIDs intersects the blocked set — with regression test testHardFilterScreensTheWholeBridgeUnion.

The taste golden fixtures were regenerated with the documented regenerate-fixtures command at their committed seeds: the corrected per-100g macros flow through per-serving nutrition, flipping nutrient feature flags and recomputing rule scores for touched recipes. Cooking events are byte-identical (same scenario); ranking reorders only where rule scores moved. validate-fixtures replays exactly and the lab suite passes 17/17.

Known failures not caused by this PR

Earlier runs inherited CommunityClientTests.testTransparentRefreshRefreshesTheAccountSnapshot from the base branch itself (rejoin tip 917a550, run 38857675958); the base's own fix (9ca20cd4) arrived with the third merge and is included in head ce2867f. LeChef weekly-allocation failures were fixed on the rejoin tip and arrived with the merged base; the GoalPath allergen-assignment failure was fixed directly on this branch (432f4b6c) after a Linux repro showed the rejoin tip's screens still admitted union-bridged ids on the plan path.

What I couldn't check

  • No Xcode: XCTest behavior verified only by hosted macOS CI; nothing built locally. Run 38875655301 (head 6a0011a) is the live verdict for this head: build-and-test and dead-code-advisory both pass; GoalPathTests.testFridgeToMealToTasteGoalPath and testHardFilterScreensTheWholeBridgeUnion pass; 0 failed test cases across the run (earlier verdicts: 38856690375, 38858662575, 38871820453 on 432f4b6 — green).
  • Linux swift test fails pre-existing (no such module CoreGraphics in FLFeatureLogic) — not introduced here, not a runnable gate.

Assumptions

  • Union bridging is the intended semantic for keys with independent legacy and FDC evidence; the strict-legacy behavior remains available in reach_report_v3.py replay mode.
  • The 46 unbridged keys stay unbridged: no supported FDC identity exists in the downloaded bulk CSVs (farro's three duplicate rows made citation ambiguous); guessed links are worse than disclosed gaps.
  • Nutrition values for repinned rows come from canonical FDC macros, not estimates.
  • Base branch moved four times during this work; all moves were merged in without rewriting history (58eba2d, 917a550 → d15ccea, 9ca20cd → ce2867f, then alpha absorbing PR feat(cook): LeChef swaps execute or refuse with a receipt the gateway accepts #191 → 6a0011a).

Human author: Sam Gu (sgu07966@gmail.com)

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

The v3 reach replay (evaluation/catalog-reach/reach_report_v3.py) replays
the app's own IngredientRepository.search over the ten written pantries
and classifies every lost recipe line. Baseline on the shipped catalog:
exact medians 0.5/0.5/0.0/0.0/0.0 (omnivore/vegetarian/vegan/GF/DF),
near-miss medians 101/54/22.5/42.5/44.5, with 535 lines lost to search
misses, 2037 to wrong-ID landings, and 1220 to unbridged keys.

Repairs, all cited from the FDC bulk CSVs (/tmp/fdc scratch):
- repin 12 vocabulary keys to the right FDC rows (banana, sugar,
  cinnamon, lentils 172420, bacon, almonds, heavy cream, white rice,
  canned tomatoes, pork chop, chicken thigh, chicken_thighs 2706027
  which pointed at the chicken-breast row)
- import 9 missing FDC rows (canonical 950 -> 959) and extend the
  import tool's energy ids with 2047 (Atwater General kcal) so
  Foundation rows without 1008 keep their energy
- curate canonical displays so typed pantry terms rank the intended
  food (Ground Beef, Brown Sugar, Tomatoes, Potatoes, ...), add 15
  alias sets, and remove the chives description's misleading 'eggs'
- rename FNDDS 2705413 to Coconut Milk Beverage: its 31 kcal/100g
  macros are the beverage, and its old display exact-matched and
  hijacked the 'coconut milk' query away from the cooking row
- all edits recorded in scripts/data/catalog/reach-v3-identity-edits.json

Bridge: RecipeCatalogIngredientBridge now unions independently resolved
legacy-ID and FDC-ID evidence instead of strict legacy-first precedence
(+ resolvedByLegacyAndFDC coverage count), with a Swift test for a key
whose legacy and FDC evidence resolve to different app rows.

After: exact medians 4.5/1.5/0.0/2.5/2.0, near medians
162.5/93.5/29.0/76.0/78.0; classes a and b eliminated (0 lost lines
each); the 46 unbridged keys remain disclosed, unresolved on purpose.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7a27da03-f003-4fd9-989f-2057b8855da9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ObviousApp and others added 11 commits October 16, 2026 05:02
CI run 38853183616 failed: the Coverage memberwise init received
resolvedByLegacyAndFDC but the struct never declared it. Add the field.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
The branch was cut from the rejoin tip before the safety-gates merge
landed; the stale merge base reproduced the base's already-fixed
GoalPath/CommunityClient runtime failures and GRDB reentrant fatals on
hosted CI. Merging the rejoin tip brings the fixed base forward.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
Rejoin tip 58eba2d merged its safety-gates refactor but left the
household-screened compactMap calling substituteScreenVerdict without
the bridged argument its signature now requires; the same compile error
fails both the base branch's run 38854677426 and this branch's
38854866877. Read the bridge per query, matching the surrounding calls.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
A key bridged by both evidence paths decremented resolvedByFDCID
without ever incrementing it, dragging resolvedKeys below the true
count (CI 38856690375: resolvedKeys 0, resolvedByFDCID -1). The union
class already owns the key; only the provisional legacy count needs
unwinding.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
The corrected per-100g macros flow through per-serving nutrition, which
flips the nutrient feature flags the learned arm reads; rule scores
recompute for the touched recipes. Events are unchanged (same cooking
history), ranking reorders only where rule scores moved. Regenerated
with the documented command at the fixtures' committed seeds (hh00/7,
hh01/11, hh23/13); validate-fixtures replays exactly.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
The catalog loader resolved each line key to one canonical bridge id
(LIMIT 1), so a union-bridged key hid its other identities from the
plan's allergen screen, and both plan-path screens ignored blocked
ingredients on optional lines — unlike the recommend path's
whole-recipe exclusion. Allergens are hard filters: the pool screen now
checks every id each line key bridges to, and the legacy-candidate and
allocator screens block on any line, required or not (GoalPathTests
catches the assigned allergen recipe under truthful identities).

Co-authored-by: Sam Gu <sgu07966@gmail.com>
…llocation

The loader-level allergen screen rejected a blocked legacy recipe's
catalog successor before the successor policy could stand it in, and
blocked the legacy recipe itself, breaking the raw-inventory contract
the weekly tests pin (candidates stay unfiltered; allergens ride the
eaters list). The hard filter lives in the allocator: restrictionClean
blocks any line, required or not, so rice-anchored recipes are never
chosen while both the legacy recipe and its successor stay in the pool.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
A plan line carries one bridged ingredient id (the LIMIT-1 resolution),
but a key can bridge to several app-ingredient ids and the blocked
allergen can be a different id of the same food — the allocator's
restrictionClean missed it and the plan assigned an allergen recipe.
PlanCandidateLine now carries every id its canonical key bridges to
(defaulted, so legacy and sentinel lines and existing call sites are
unchanged), the adapter fills it from the bridge table, and
restrictionClean screens the union. Screening input only: the candidate
pool stays raw per the weekly tests' contract.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
The defensive duplicate-line merge rebuilt the line without the
bridged-id set; merged lines would have lost the union and the hard
filter missed them. The merge unions the sets like it unions required.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
@SamGu-NRX
SamGu-NRX changed the base branch from obv/burst9-fridgeluck-rejoin to alpha October 16, 2026 06:53
The adapter edit that passes bridgeIDsByKey into each bridged
PlanCandidateLine was lost to a concurrent same-file write, so every
catalog line shipped an empty union and the allocator's bridge screening
was vacuous — the GoalPath allergen recipe stayed assignable. Re-applied:
the line now carries every id its key bridges to, matching the raw-pool
contract (screening input only, no pool filtering).

Co-authored-by: Sam Gu <sgu07966@gmail.com>
ObviousApp and others added 2 commits October 16, 2026 08:09
…ry id

The weekly-plan hard filter read only each line's primary ingredient id
(the bridge's LIMIT-1 resolution) while the household's blocked set held
the id the profile stored. The onion key bridges to both the legacy id
(5) and the USDA id (566); a household blocking 566 left every onion
recipe eligible because the line's primary id was 5 — the failing
GoalPath assert (the plan never assigns an allergen recipe). The shipped
bridge unions already sit on PlanCandidateLine.bridgedIngredientIDs and
the type's contract says the allocator screens the whole set; the filter
now does. An earlier commit under this intent lost its edit to a
concurrent same-file write (the same loss 70663dd documents for the
adapter).

Verified on Linux against the production allocator (plan-repro harness,
shipped recipes-v2 bundle + USDA catalog, 762 catalog recipes):
- old filter, blocked {566}, candidate line id 5 with union {5, 566}:
  allocated (picked=1) — the GoalPath failure shape
- fixed filter, same fixture: refused (picked=2)
- full weekly horizon: the union screen rejects all 195 allergen-bearing
  candidates; every dinner clean

Co-authored-by: Sam Gu <sgu07966@gmail.com>
PlanDataAdapter conflict: both sides read the full catalog bridge per key.
Kept alpha's map (admission screens every bridged id against the excluded
ids) and passed the same map to each line's bridgedIngredientIDs so the
allocator's hard filter also sees the whole union.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX
SamGu-NRX marked this pull request as ready for review October 16, 2026 09:37
@SamGu-NRX
SamGu-NRX merged commit 2c69245 into alpha Oct 16, 2026
3 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T09:38:58.071595Z 6a0011a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codemetrics-ai

Copy link
Copy Markdown

Note

Review skipped

Codemetrics is installed on this repository, but it hasn't been added to your team yet.

To enable AI code reviews for this repo, go to your dashboard and add FridgeLuck to your team's repositories.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants