A tool that drafts an offer range where every figure on it resolves to a dated comparator row, and refuses the role outright when the rows are not there. Built in full; never run on a live requisition.
Prototype
Role
Product manager, build owner
Team
1 people operations partner, 1 data engineer, with finance as reviewer
Stage
Prototype — proposed by people operations and built end to end on synthetic comparator data. It never went to users: no live requisition was ever run through it, because priorities shifted before the pilot was scheduled.
Live demo: press any figure and its citation opens
A figure with no dated source does not render at all
The floor refuses a role; it never widens a band
“An unanswerable number gets renegotiated by whoever is most confident.”
Problem. A salary range came out of a spreadsheet somebody had built, and whoever could say where its numbers came from had usually left. Of the last 26 closed requisitions, 6 agreed ranges resolved to a dated source; the other 20 resolved to an older range, to “market data”, or to nothing.
What I did. People operations asked for it; I drew the line the tool is built on — every figure computed by rule from dated comparator rows, the model writing the argument and never a number — then added an evidence floor that refuses a role rather than widening a band to cover thin data.
Result. It never went to users: the pilot was never scheduled after priorities shifted. What is structural still holds in the demo: a figure with no dated source does not render, the floor refuses a role rather than widening a band, and no figure comes from a model.
01Why this, and why now
Compensation was not the loudest request; it was the one where being wrong stayed quiet longest. A range nobody can trace still produces an offer, and the cost surfaces a year later in an equity review nobody can reconstruct.
Sizing exercise with people operations and the recruiting lead, estimated from how each error had actually been caught in the previous year. The scheduler is the control: everybody wanted it, and nothing it gets wrong survives the afternoon.
02The problem as people experienced it
I read the last 26 closed requisitions, tracing every number in each agreed range to something with a date on it, and timed four offer conversations for how long each spent on where a figure came from.
What people did
Where it broke
Evidence
Opened last year’s sheet for the family
It cited the sheet before it
11 of 26 ranges resolved to a previous range
Quoted “market data” in the approval
No source, date or sample behind the phrase
9 of 26 ranges, none traceable
Asked a recruiter what the market was doing
A real signal, undated and never written down
Raised in 14 of 26, recorded in 2
Rebuilt a range inside the meeting
The argument became one about memory
Median 19 minutes per offer conversation
Agreed a number above the range
No recorded reason, so it set a precedent
5 of 26, with a reason recorded in 1
Provenance coding of every figure in 26 agreed ranges. The first bar is the one that decided the design: the most common citation was another document that cited another document, which is a chain with no end and reads exactly like a chain with one.
The reframe: this was never an accuracy problem. Nobody could show the old ranges were wrong; what nobody could do was answer a question about a number, and an unanswerable number gets renegotiated by whoever is most confident.
03My role and approach
I owned the build scope, the evidence rules, and the line between what a rule computes and what a model may write.
Key decisions
Numbers are computed; only the argument goes to a model. A model wrong about a salary figure ends the tool the day it is noticed; one vague about a market costs an edit.
A figure with no source does not render at all. No estimate fallback and no “approximately”: a soft number is read as a number, and pasted like one.
The floor refuses a role; it never widens a band. A wide band with a caveat under it was built first, and read as a band. The number reaches the approval; the caveat does not.
It drafts a range and stops at the market median. Above the median is a decision with a name on it. Defaulting past it would make the tool the thing deciding pay.
04What was built
One pipeline per requisition: comparator rows are pulled and weighted by collection method, the four floor rules run, and the role gets a band or a refusal. A model is called once at the end for an argument written around numbered slots, and the renderer substitutes every slot. Nothing is stored.
1The output is a range, and it stops at the market median. Everything above is reserved and needs a named reason and a second approver. A point figure would be quoted as the offer and the range forgotten by the second email.
2Every figure carries its sources in superscript, and every one of them opens. The click ends at rows with a date, an observation count and a method — never at another figure in the same document, which is how the old spreadsheets cited themselves.
3The rationale is badged model-written and contains no number the model chose. It was given the computed band and asked for an argument with slots in it. Press “show the raw model output” in the demo and the slots are still sitting there, unfilled.
4Base salary only, on the tile rather than in a footnote. A blended total-compensation figure is the point where a defensible band quietly becomes an indefensible one, and it is too useful a number to leave ambiguous.
Recreated as a working demo. The weighting rule, the four floor rules, the refusals and the citation behaviour are the real ones; every source, observation count, band and figure shown is illustrative, and no employee or candidate data exists in it to show.Open this draft and follow a figure to its source ↗
Open the comparator rows ↗Five sources, and what each method is worth.Weights are fixed by how a figure was collected, not chosen per role — employer-reported survey data highest, self-reported aggregators lowest and never dropped, our own accepted offers treated as a source rather than as the answer. This screen exists so a band can be argued with, and the argument is always about a row.Open the comparator rows ↗Open a refusal ↗The output nobody asked for.Four rules, each able to refuse a role on its own, and a refusal that names which one failed and what would clear it. Design verification failed only the last rule — four sources whose medians sit 56k apart are not measuring the same job, and averaging them would have invented a market.Open a refusal ↗Open the band and press a figure ↗Six figures, six citations.The band strip, the drafted range inside it and the reserved region above it — then every figure on the screen in a table with the rule that produced it. There is no seventh figure, because a number that could not carry a citation would not have rendered.Open the band and press a figure ↗Open a suppressed band ↗Suppressed, not summarised.Four people hold this band, so the distribution is not shown at all. The band edges stay, because they are a policy rather than a person. This is the screen that had to refuse the most, and the only one where refusing costs a question people genuinely need answered.Open a suppressed band ↗Open the cross-market refusal ↗Held, shown, and carrying no weight.The Bengaluru requisition keeps its San Jose rows on screen at zero weight rather than hiding them, because the tempting thing to do with them is exactly what the location rule forbids. A labelled cross-market adjustment was deferred, so this role has no band and says so.Open the cross-market refusal ↗
Every screen above is the state its own link opens, captured from the running demo on synthetic comparator data. It exists so the argument on this page can be checked rather than taken on trust: switch requisition, press any underlined figure and follow it to the rows and the arithmetic, open one of the four refusals, or ask the offer screen to show you what the model actually wrote.
05What we learned
Seven synthetic requisitions ran through it, three banded and four refused; no live one ever did. Enough to test the citation rule and the floor, and nothing about pay.
No outcome figure appears on this page; nothing was measured with users.
7 synthetic
Requisitions ever run through the tool; live requisitions: none
4 of 7
Refused at the evidence floor rather than given a wider band
6 of 26
Agreed ranges that resolved to a dated source before build, the number the tool was meant to move
0
Percentiles or offer figures produced by a model (guardrail)
Traceable share per family: the 26 requisitions read before build on the left, the demo’s seven synthetic requisitions on the right. The two flat rows are families the floor refuses: the tool produces nothing, so the range would be assembled the old way. No live requisition ever moved a row; this is the shape the design produces on synthetic data, not a result.
The stops were agreed before the first draft: one published figure that cannot be resolved to a dated row, or one figure written by the model rather than substituted into it, ends the tool. Neither was ever tested against a live round.
A tool that refuses is only useful when someone is obliged to act on the refusal; without a scheduled pilot, nobody was.
How we counted. A figure carries a source when the draft resolves it to a named comparator row with a date, an observation count and a method. A figure sourced to “market data”, to a previous range, or to another figure in the same document counts as untraceable.
Build configuration — five comparator source types weighted by collection method · rule-based weighted percentiles, band edges and draft range · four evidence-floor rules evaluated per role, any one of them refusing · gpt-4o-mini for the rationale only, called once with the computed figures and a slotted template · the renderer substitutes every numeral, so no figure passes through the model · no individual compensation record held anywhere in the system · internal distribution suppressed below eight holders
06What I'd do differently
I built the weighting table as a constant and treated its numbers as obvious; the weight on self-reported data is an afternoon judgement I never tested, and the band moves with it.
I would have measured provenance time before there was a tool to fix it; the 19 minutes comes from four conversations I chose.
And I would have tied the build to a named hiring round; a tool with no requisition waiting is the first thing a priority shift shelves.