A synthetic pricing bandit that never saw race priced one cohort 26% higher. Equity finds the proxies, gates on EEOC four-fifths, and seals a CID-ready pack.
AI GovernanceDynamic PricingAlgorithmic Fairness

"We removed race and income from the model" is the sentence that loses the dynamic-pricing case

Ashutosh SinghalAshutosh SinghalJuly 22, 202614 min read

In December 2025 the FTC settled with Instacart for $60 million over the Eversight pricing experiments, and the complaint documented prices on identical items varying by as much as 23%. The FTC never had to establish what the algorithm was told; the complaint rested on the prices different shoppers were actually shown for the same item.

The sentence every pricing team reaches for is the opposite of an outcome argument: we removed race and income from the model. I believe the teams who say it. I also think it is the sentence that loses the case, and rather than argue that in the abstract I built a pricing engine that can truthfully say it about itself, then audited it. The engine is a LinUCB contextual bandit with seven price arms, seeded at 42, trained to be revenue-optimal on a synthetic population of 10,000 shoppers I generated with the proxy structure planted on purpose. I called the fictional retailer ShopMart. No real retailer was audited and no real shopper was overcharged, which is exactly why I know the ground truth and can score the audit against it. The layer that sits on top is a console I named Equity, designed to run on whatever pricing engine a retailer already has; in this build the engine under audit is my own bandit. The walkthrough of the full run is at veriprajna.com/demos/ai-pricing-compliance.

Same earbuds, matched demand, 26% more

I gave the bandit nine inputs and not one of them is a protected attribute: ZIP income index, device tier, referral source, dwell time, cart size, repeat rate, session count, premium membership and hour of day, plus referral-by-dwell cross-terms, because many real engines carry cross-terms or trees and I wanted mine to be able to do whatever theirs can do. Then I let it price one SKU, the Aura Wireless Earbuds, list $79.00, across all 10,000 decisions.

The hero card on the console is the first thing on screen before anything runs. Two cohorts, one product, matched demand signals. Decision Set A is the high-income-ZIP, new-device cohort, 2,880 decisions, represented on the card by an Upper East Side ZIP and a new iPhone: displayed price $74.06. Decision Set B is the majority-minority-ZIP, old-device cohort, 3,037 decisions, represented by a South Bronx ZIP and an old Android: $93.47. Measured variance, +26.2%. Those are cohort means over roughly 3,000 decisions each, so this is the systematic behaviour of the policy rather than one unlucky shopper, and the ZIP and phone on each card are the cohort's representative profile, not its definition.

The Equity Pricing Outcome Comparison card: Decision Set A, the reference cohort of 2,880 decisions represented by ZIP 10021 and a new iPhone, at $74.06; Decision Set B, the exposed cohort of 3,037 decisions represented by ZIP 10456 and an old Android, at $93.47; Measured Variance +26.2% on the same Aura Wireless Earbuds, list $79.00.
The hero card before the audit runs. Same SKU, matched demand signals, $74.06 against $93.47, a 26.2% gap across cohort means of roughly 3,000 synthetic decisions each.

What unsettled me is that I had written nothing that could be called a discriminatory rule. I wrote a revenue-maximizer and handed it signals that predict willingness to pay. ZIP predicts income, income predicts price sensitivity, an old Android predicts less comparison shopping. None of those is race. All of them move with it, and a bandit rewarded for revenue will find every one. The 26% is what revenue-optimality looks like when the signals carry the cohort.

The proxy that hides between two clean inputs

I designed the audit to score every input three ways, because every single test I could think of had a hole in it. Absolute Pearson correlation against the protected cohort, a label the audit itself never receives. Normalized mutual information. And a counterfactual price replay: overwrite that one input for the protected cohort with the advantaged cohort's reference value, hold every other input constant, re-run the engine's own policy, and measure how far the protected cohort's price moves. The replay is model-agnostic. It varies the input and watches the output, so it does not care whether the engine is a bandit, a tree or a client's black box.

The first two rows of the Input Audit table are the ones anyone would predict. Zip Income Idx: |r| 0.95, MI 1.00, counterfactual +7.8%, VIOLATION. Geography fully encodes the protected class in this population and the engine prices on it. Device Tier: 0.32, 0.08, +2.9%, VIOLATION, a facially neutral signal that clears the 0.30 correlation line and the 0.05 MI line I set as thresholds in code.

The third VIOLATION is the one a per-feature audit cannot see. Referral on its own: |r| 0.00, MI 0.00, PASS. Dwell Time on its own: 0.17, 0.03, PASS. A per-feature audit clears both and moves on. The interaction row, Referral × Dwell Time, reads |r| 0.00, joint MI 0.73, counterfactual +1.7%, VIOLATION. That is an interaction gain of 0.71 over the best single component.

The Equity Audit Results dialog, Input Audit: Which Features Are Demographic Proxies? Zip Income Idx 0.95, 1.00, +7.8%, VIOLATION; Device Tier 0.32, 0.08, +2.9%, VIOLATION; the Referral × Dwell Time interaction row 0.00, 0.73, +1.7%, VIOLATION; Premium Member 0.24, 0.04, ABSTAIN; Referral, Dwell Time, Cart Size, Repeat Rate, Session Count and Hour all PASS.
The Input Audit table with the cursor on the interaction row. Referral and Dwell Time each pass on their own; together their joint mutual information with the protected class is 0.73.

I planted that structure XOR-style. An organic referral with a long dwell marks the protected cohort. A comparison referral with a short dwell also marks it. Organic-and-short and comparison-and-long mark the advantaged cohort. Neither how the shopper arrived nor how long they lingered separates the groups alone; the pair does. Correlation is a one-column instrument and cannot see this. Mutual information over the pair can. A compliance programme that audits inputs one at a time is auditing the wrong object, and I could not find a way to prove that without building a population where the one-at-a-time answer is confidently wrong.

The defence I would have given

I ran the defence I would once have offered a regulator myself, which is fairness through unawareness: remove the ZIP and device inputs, retrain the engine, report the result. I knew the compound proxy was in there. What I did not know was how much of the 0.43 was ZIP and device alone, and how much was the pair I had hidden. Dropping ZIP and device and retraining moved the four-fifths ratio from 0.43 to 0.59. Still a fail, and the remaining gap is the part that survives every per-feature check. The benchmark card labels the reason in four words: Compound Proxy Still Leaks.

The Equity Benchmark Results dialog, Validation Benchmark: Labeled Evaluation Set. Precision 100%, recall 100%, 0 legitimate signals incorrectly flagged, 1/1 dual-use signals correctly abstained. Baseline: Fairness Through Unawareness, remove ZIP and device inputs, four-fifths ratio 0.59, Fails, reason Compound Proxy Still Leaks. Equity Constraint 0.82, Pass, revenue cost -1.3%, Hard-Cap Baseline 0.59, Fails.
The labeled benchmark on the same seeded synthetic set. Dropping ZIP and device moves the gate from 0.43 to 0.59 and it still fails; the audit recovered 3 of 3 planted proxies with 0 false flags.

That result changed how I hear the sentence. The engine found the cohort anyway, through two signals that pass every single-column test, and it will keep finding it, because finding it is what a revenue-maximizer is rewarded for. The unawareness baseline costs the team nothing to say and hands them a number they will later have to explain.

The 100% on that card has a narrow meaning. The benchmark rebuilds the same seed-42 population and scores the audit against the labels I planted, so precision and recall of 100% mean 3 of 3 planted proxies recovered with 0 false flags on that labeled synthetic set. It is a self-check against known ground truth, and I would not quote it as an accuracy rate on anyone's real pricing logs, which are confounded in ways I cannot plant.

What 0.43 means, and why I did not let a model decide it

I refused to invent a house fairness number, because a number I made up is one a regulator has no reason to respect. The gate in Equity is an external legal standard: the EEOC four-fifths rule, 29 CFR 1607.4(D), adapted from selection rates to price tiers. Favorable means the not-top price tier. In the engine as shipped, 36% of the protected cohort receives the favorable tier against 83% of the advantaged cohort. 0.36 over 0.83 is 0.43, the threshold is 0.80, and the dial is red and says Fail. Beside it: 64% of the protected cohort in the top price tier, and a mean overcharge of +15.6% against the advantaged cohort.

The Equity Audit Results dialog scrolled to the Disparate-Impact Gate: EEOC Four-Fifths Rule. A red dial reads 0.43, Four-Fifths Ratio (Threshold 0.80), Fail; tiles read 36% Protected Cohort Receiving The Favorable Price Tier, 83% Advantaged Cohort Receiving The Favorable Price Tier, 64% Protected Cohort In The Highest Price Tier, +15.6% Mean Overcharge: Protected Versus Advantaged. Above it, Feature Auditor and Adversarial Challenger notes.
The gate. 36% of the protected cohort in the favorable tier against 83% of the advantaged cohort is 0.43, under the 0.80 line, so the dial is red. The Adversarial Challenger's note above it argues the loyalty case for the abstained Premium Member input.

The verdict logic runs in plain numpy, outside the agent layer, in a fixed order I can read back to a lawyer. The dual-use signal is tested before the violation test: Premium Member correlates at 0.24 with the protected cohort and is also a legitimate loyalty signal, so the code returns ABSTAIN and routes it to legal review rather than flagging it or clearing it, and nothing downstream is allowed to promote it to a violation. Then the violation thresholds, then the abstain band, then PASS. Cart size, repeat rate, session count and hour all pass. The audit does not flag everything, and an audit that flags everything is as useless in front of a Civil Investigative Demand as one that flags nothing.

The agents in the crew, a Feature Auditor and an Adversarial Challenger, write prose; the third role the design names, a Regulatory Mapper, is in this build a fixed lookup table rather than a model. The Challenger's job is to argue the loyalty-benefit case for the abstained input as hard as it can, and on screen it does. It cannot change the 0.24, cannot move a verdict, and cannot decide whether 0.43 is under 0.80. I made that split at the start and never revisited it: agents advise, code decides, and the model is free to be as persuasive as it likes about a number it is not permitted to touch.

The fix I wanted to work

The first fix I tried was a cap, because it is the one every pricing team defaults to: never price more than 15% above the fair reference. I built it as the Hard-Cap Baseline and expected it to be the boring, adequate answer. It cost 0.2% of revenue and failed the gate at 0.59. The gaming signature on the card explains why: 29% of the protected cohort ended up priced within 1% of the cap. The verdict reads Gamed To The Boundary. The cap is cheap precisely because the engine re-priced the exposed cohort to just under it, and cheap-and-failing is the combination that looks attractive in a budget meeting and does not survive discovery.

A revenue-maximizer treats a ceiling as a target.
The Equity Remediation Results dialog, Fairness Constraint: From Detection To Remediation. Four-Fifths Before 0.43, Fail, Engine As Shipped; Four-Fifths After 0.82, Pass, Revenue Cost -1.3%. Fairness-Aware Reward Shaping, Recommended Control: 0.82 Pass, revenue cost -1.3%, gaming signature 2%, pull weight 0.65. Hard-Cap Baseline (115% Of Fair Price): 0.59 Fail, revenue cost -0.2%, gaming signature 29% Priced At The Cap, verdict Gamed To The Boundary.
Two fixes side by side. Reward shaping passes at 0.82 for 1.3% of revenue with a 2% gaming signature; the 115% hard cap costs 0.2%, fails at 0.59, and prices 29% of the protected cohort to the cap's edge.

The control that held is Fairness-Aware Reward Shaping. Instead of a boundary, it pulls every price continuously toward a proxy-free reference price, a second bandit trained with the proxy inputs dropped and the legitimate signals kept, and binary-searches the smallest pull weight that clears 0.80. On this engine that weight is 0.65; one grid step below, 0.625, reached only 0.778. Four-fifths after: 0.82, Pass. Revenue cost: 1.3%. Gaming signature 2%, because there is no edge to game. The console's own fine print says the revenue impact is specific to this synthetic engine and scenario, and I will say the same thing louder. 1.3% is a measurement on one seeded population, and I will not sell it as a general price of fairness.

What the record is for

I built the evidence pack last, because everything before it only matters if a lawyer can file it. When a Civil Investigative Demand arrives, the company that never logged the right data spends months in forensic extraction. Equity seals what the audit produced into a JSON pack with a printable HTML version: the engine name and version, the 10,000 decisions audited, the gate, the representative disparity, every input's finding and rationale, the remediation result, and a SHA-256 digest of the pack body as its tamper-evident hash. Each VIOLATION is mapped to five regimes: the EEOC four-fifths rule, the New York Algorithmic Pricing Disclosure Act (effective November 10, 2025, civil penalty up to $1,000 per violation), the Colorado AI Act (SB 24-205, effective June 30, 2026), EU AI Act Articles 13 and 14 (high-risk obligations from August 2, 2026), and FTC Act Section 5. The abstained input maps to a single legal-review line.

The regulatory mapping is a fixed table. It would have been easy to let a model write it, and I decided against it: every VIOLATION attaches the same five regime lines, the ABSTAIN attaches one legal-review line, and no prose is generated for the mapping at all. A model that reasons about which statute applies is a model whose reasoning changes with the next release, and a pack whose mapping drifts with the model is a pack a regulator's expert can pick apart. The narration in the pack is model-written and cached; the numbers, the verdicts and the mapping are code. The pack stays filable whatever the model underneath turns out to be. The full breakdown, with the pack walked through on screen, is at veriprajna.com/demos/ai-pricing-compliance.

Veriprajna does not set prices, does not replace Pricefx, PROS, Zilliant or Competera, and does not issue a legal verdict. The client's engine keeps pricing. I hand over the evidence; their legal team makes the call.

And if you would rather watch the audit run than read me describe it, here is the whole thing, end to end.

I keep returning to the pricing team's sentence, because it is true. Race and income are not in the model. On the engine I built, that true sentence sits beside a 26% gap on the same earbuds, a gate at 0.43, and a cap that failed while looking like it worked. Nobody told the engine who the shopper was. A revenue reward was enough, because guessing the shopper was the most profitable guess available, and the audit measured exactly how far it went. The sentence that holds up is a longer one: we knew the engine could find the cohort without being told, we measured how far it did, and this is what it cost us to stop it.

Related Research

Also Published On

Build Your AI with Confidence.

Partner with a team that has deep experience in building the next generation of enterprise AI. Let us help you design, build, and deploy an AI strategy you can trust.

Veriprajna Deep Tech Consultancy specializes in building safety-critical AI systems for healthcare, finance, and regulatory domains. Our architectures are validated against established protocols with comprehensive compliance documentation.

AI Pricing Audit: Why Removing Race From the Model Fails