Skip to content

[ note · 2026-08-01 ]

Per-field fusion with learned source reputation

TruthFinder / Accu-style conflict resolution weighted by learned Beta-Bernoulli source trust. 756 lines under services/api/src/lib/fusion/. Resolution is per-field; reputation is per-host.

Per-field resolution, weighted by per-host learned trust. Six files, 756 lines under services/api/src/lib/fusion/. TruthFinder / Accu-style resolution, Beta-Bernoulli reputation clamped to [0.05, 0.95].

Per-field resolution, weighted by per-host learned trust — 756 lines across six files under services/api/src/lib/fusion/. Beta-Bernoulli reputation on each source host, clamped to [0.05, 0.95] so no host is ever fully trusted or fully written off, feeding a TruthFinder / Accu-style resolver where the value whose supporters accumulate the highest combined weight wins.

Every product-data enrichment task eventually collides with the same nuisance: three sources disagree about the same field, all three sources are useful for other fields, and no single source is right often enough to blindly trust. The standard move is to pick a “winner” source per query and take its whole record. That approach loses everything the other two sources knew. Per-field fusion loses much less.

The fusion layer is six files under services/api/src/lib/fusion/resolve.ts, resolve-store.ts, reputation.ts, observations.ts, gaps.ts, and confidence.ts — 756 lines together. For each field where sources genuinely disagree, we collect the observations, weight each one by its host’s learned trust times the observation’s own confidence, and resolve: the value whose supporters accumulate the highest combined weight wins, with ties broken by specificity, then by the strongest single supporter. Trust is a Beta-Bernoulli posterior per source host — wins and losses accumulate as counts, the working trust is the mean of the resulting Beta distribution under a flat prior, and the number is clamped to [0.05, 0.95] so no host is ever fully trusted or fully written off. An unknown host starts at 0.5.

Two design decisions are worth naming.

First: resolution is per-field, reputation is per-host. Those are different grains, and keeping them straight is load-bearing. Each conflicted field is resolved on its own — a distributor’s site can lose on material composition and still win on pack size in the same pass, because nothing about one field’s outcome drags another’s. The trust that weights those resolutions is coarser: one learned number per host. What keeps the coarse grain honest is the arithmetic above it — a value’s score sums trust times confidence across every source that agrees, so independent agreement on a field can outvote a single well-reputed host. The reputation table’s key carries a second dimension for segmenting trust later, because a host that is authoritative for electrical spec tables may be noise elsewhere; today the working number is per-host.

Second: the resolver is pure, and its return value closes the learning loop. Given a slate of observations plus a trust lookup, resolve.ts returns the winning value, its aggregate score, the hosts that supported it, and the hosts it overruled. The supporters and losers are not decoration — they feed straight back into the reputation update, so every conflict the layer settles adjusts the trust the next conflict is weighed with. And only genuine conflicts are touched: an attribute with a single distinct value across sources passes through untouched. Resolution picks among grounded values; it never invents one.

The layer is boring in the good way. It runs at cache-write time, between the append-only observation store below it and the served record above it. We did not invent the technique — TruthFinder dates to 2007, the Accu family of source-accuracy models followed close behind, and Beta-Bernoulli reputation is undergraduate statistics. We built the plumbing that lets these techniques run on live product data with a clean audit surface.

Boring plumbing that composes with everything is the foundation you build on.

The code is published for reading: packages/fusion in bargo-lv/primitives — source and tests, all rights reserved.

Correction (2026-08-20): an earlier version of this note described reputation as kept per (source, field) pair and a machine-readable “reason” field on the resolver’s return. Neither is what the code does — resolution is per-field, reputation is per-host, and the resolver returns value, score, supporters, and losers. The text above now matches the code.