Captured from the discovery pass on RecipeInheritanceStage.mergeQualifyingContributions
(the scanner's own multi-value merge) after fixing B1/B2/B3 — the load-bearing
fix that stops contested recipe mass from being silently dropped. These two
items were deliberately scoped out of that fix and are not scheduled.
Part A — Ingredient-provenance plumbing (diagnostics only, no functional payoff yet)
What it would do: wire both RecipeInheritanceStage's 2-arg
resolve(item, lookup) call sites (line 58, the scanner path; line 101,
mergeTagAndRecipeScores) through the existing but currently dead 4-arg
overload — resolve(item, lookup, traceOut, classificationTraceOut) —
which already correctly captures real, distinct per-ingredient IDs (verified
via resolveRecursive; not the parent item's ID repeated).
Why it's deferred: confirmed via discovery that this is purely additive
diagnostics. MultiValueAnalysisPipeline.analyze only ever reads
r.scores()/r.tagClassified() — it has no channel for per-ingredient
data at all. Wiring the 4-arg overload today would produce richer trace
output (real ingredient IDs instead of raw_onion_calzone repeated three
times) with zero effect on multi-value/single-value classification
counts, because nothing downstream consumes it.
What's needed before this has payoff:
RecipeInheritanceStage.apply's signature would need a trace parameter threaded fromItemClassifier.classify(plumbing target already exists, flows toItemScanner'straceMap).SourceCollector.toClassificationResultcurrently discardssignalsentirely (signals = List.of()) — would need a newClassificationResultfield or an itemId-keyed side map to carry provenance through that path.buildIndexreduces eachIngredienttoitems[0]— acceptable for provenance identity as-is, but worth a explicit decision on whether to widen to all candidate items later (bounds how accurate the provenance ends up being even after this is wired).
Bottom line: worth doing once there's an actual consumer for the data
(e.g. showing real ingredient breakdowns in ambiguous_sources.txt for
human review) — not before.
Part C — "Ingredients disagree" as its own multi-value trigger
The idea: right now, multi-value detection is entirely driven by the
shape of the merged score vector (does a secondary category clear the
0.15 absolute / 0.35 relative thresholds). A separate, independent trigger
could instead ask: "do this item's distinct, confidently-classified
ingredients disagree with each other?" — e.g. if a recipe's ingredients
resolve to {grains, dairy, vegetables} with no single category dominating
the ingredient list, that disagreement itself could be grounds for flagging
multi-value, even in cases where the current score-based math doesn't
clear the threshold.
Why it's deferred: this needs real threshold design, not a quick addition — "how many ingredients need to disagree, how confidently, by how much" is a genuine tuning question with no obvious default. It's also architecturally a third component:
Existing: merged score vector → threshold check → multi-value?
Part C: distinct ingredient classifications → disagreement check → multi-value?
Both could independently flag an item as multi-value; Part C would need to either merge with the existing check or run as a parallel, separately- reported signal.
Depends on: Part A landing first (or at least the ingredient-provenance capture, since this trigger needs to know what each ingredient actually resolved to, not just the aggregated map).
Priority note
Neither of these blocks anything currently shipping. The load-bearing fix (B1/B2/B3 — stop dropping contested mass, honest confidence, unconditional scanner surfacing) is what actually finds more real multi-value items like the calzone case. These two are refinements on top, worth revisiting once that fix has been run against real data and its output reviewed.