Confidence is data. Make it travel with the value, not live in a status bar.
TLDR: When a value passes through several processing stages, its trustworthiness changes at each one. If confidence lives in a UI badge instead of on the value itself, the next stage cannot reason about it. Attach a score to the value and let it travel, so downstream code can tell an extracted guess from a validated fact.
The problem class
You have a multi-stage pipeline. Stage one produces a value with some uncertainty. Stage two refines or checks it. Stage three consumes the result and has to decide how much to trust it.
The naive design computes a confidence at stage one, renders it in that stage's UI, and moves on. By stage three the number is gone. The consuming stage sees a value with no idea whether it was a clean measurement or a low-quality guess. So it either trusts everything equally, which is wrong, or it re-derives confidence from scratch, which is wasteful and often impossible.
This shows up everywhere: OCR into a form, a scraper into a database, a model's extraction into a downstream analysis. The value flows; the confidence does not.
The naive model
Confidence as a display concern:
function extractTable(region) {
const table = parse(region);
ui.showBadge(region.confidence); // 82%
return table; // just the data
}
The 82% is real and useful, and it dies the instant extractTable returns. The table that flows to the next stage is a bare grid. Everything the extractor knew about how reliable it was is now unrecoverable.
Why it fails
Downstream stages make decisions that depend on upstream confidence. A validation stage should treat a 0.6 extraction more skeptically than a 0.95 one. A reporting stage should flag results assembled from low-confidence inputs. An execution stage should refuse to run on values below a threshold.
None of that is possible if the score is not attached to the value. The information existed, at the exact moment it was cheapest to capture, and the design discarded it. Recomputing trust three stages later means re-doing the work the first stage already did, if you even can.
There is a second, subtler failure. Without a traveling score, you cannot distinguish "this value has not been checked yet" from "this value was checked and passed." Those are completely different states, and a bare value collapses them into one.
The better model: a two-stage trust handoff
Make confidence a first-class property that rides with the value. We model it as a small tag:
{ kind, value, score, source, tool, stage, ts }
Every derived value carries one. The extractor stamps a low, honest score and marks the source:
tag('candidate', tableName, {
score: extractionConfidence, // 0.7 — extracted, not yet verified
source: 'pdf-extract',
});
The word candidate is doing real work. It says this value is a proposal, not a fact. The receiving stage reads that and routes accordingly, instead of treating the incoming grid as finished.
Then the validation stage checks it and emits its own tag that supersedes the candidate:
tag('validated', columnName, {
score: 1.0, // passed every check
source: 'measured',
});
Now the value has a history. It was extracted at 0.7, then validated to 1.0. A downstream consumer can read that chain and know exactly how trustworthy the value is and how it got there. The two stages are explicit: extracted-uncertain becomes validated-trusted, and the transition is recorded, not implied.
Implementation notes
Three things make this work in practice.
Keep the tag shape identical everywhere. If the extractor, the validator, and the consumer each invent their own confidence format, you are back to re-deriving. One shape, emitted by every stage, is what lets a value accumulate a coherent history.
Score honestly at the source. The temptation is to stamp 1.0 because the value looks fine. Resist it. If a stage did not actually verify something, its score should reflect uncertainty. A default of "unverified" (say 0.7) is more useful than a false 1.0, because it tells the next stage there is work to do.
Let later stages supersede, not append blindly. A validated tag should replace the candidate claim about the same value, so the current trust level is unambiguous. Keep the prior score in the lineage for auditing, but the active score should be the latest stage's verdict.
Tradeoffs
Every value now carries metadata, which is more memory and more serialization. For high-volume numeric data this is not free, and you may tag at the column or table level rather than per cell to keep it bounded.
There is also discipline cost. The pattern only pays off if every stage participates. One stage that drops the tag breaks the chain, and a half-populated lineage is nearly as useless as none. This is a whole-pipeline commitment, not a local optimization.
The one line to keep
A confidence score shown once and discarded is a decision you forced the next stage to make blind. Attach the score to the value, keep the shape uniform, and let trust accumulate as the value travels. Then "how much do I trust this" has an answer instead of a guess.