How I built an AI valuation engine for Nigerian used cars, and what it taught me about why you should never let a language model price anything on its own.
In the US, if you want to know what a 2018 Toyota Camry is worth, you check Kelley Blue Book. There is no Kelley Blue Book for Nigeria. Used cars here trade through an informal economy: dealer clusters, WhatsApp groups, and listing sites where the asking price is an opening move in a negotiation, not a market value. Two identical cars can be listed ₦8 million apart on the same site, on the same day.
I'm building AutoValue, a platform that gives Nigerian sellers a data-backed valuation from photos of their car. The AI parts you'd expect worked early: a vision model identifies the car from a photo, reads the odometer, and scores the condition. The part that nearly sank the product was the one that looked easiest. Asking an LLM: "what is this car worth in Nigeria?"
The first version did the obvious thing. Collect the car's details, hand them to a fast model (Claude Haiku), and ask for a Nigerian market price. In testing it seemed plausible. Then a pattern emerged that no prompt tweak would kill:
Almost every car priced out between ₦20 and 25 million, regardless of what it was.
A 2020 Land Cruiser Prado, a ₦70 to 95 million car in today's market, came back at ₦21 million. Budget sedans and luxury SUVs drifted toward the same band. The model wasn't hallucinating randomly. It was being wrong in a suspiciously consistent way, and consistent wrongness always has a cause.
Here's the cause, and it's the most useful thing in this article if you're building LLM products for any volatile economy.
The model's training data was full of Nigerian car prices from when the naira traded around ₦450 to the dollar. By the time I was building, the street rate was around ₦1,500. The naira had lost roughly 70% of its dollar value, and nearly every used car in Nigeria is an import whose price tracks dollars. So the model wasn't guessing badly. It was recalling accurately from a world that no longer existed, and quoting it into one where every number had tripled.
No prompt fixes this. You can tell the model "the exchange rate is now ₦1,500/$" and it will acknowledge the fact and then keep leaning on thousands of memorized price examples denominated in the old world. Training data isn't a fact the model can bracket off. It's the water the model swims in.
This generalizes beyond Nigeria and beyond cars. If your product asks an LLM for prices, salaries, rents, or costs in any economy with meaningful inflation or currency movement, the model is answering from the past, with total confidence, in fluent and convincing prose.
The fix was not better prompting. It was changing the model's job description.
The current architecture works like this:
The model went from source of truth to arithmetic-with-judgment, which is what it's actually good at. Depreciation for high mileage, a discount for a rough interior, a premium for a clean one: those relative adjustments are stable knowledge that doesn't expire the way absolute prices do.
Accuracy went from unusable to defensible in one architectural change.
Live search costs time and API calls, so every resolved price gets saved to a car_price_anchors
table in Supabase. Next time anyone prices a 2018 Lexus RX 350, the anchor is already there. The table is effectively a self-training price database: the more cars users price, the less searching the system needs.
Then prices went wrong again, and this bug was self-inflicted.
Early on, when search returned nothing, I let the model's own estimate be saved as an anchor, labeled claude_estimate
. It felt harmless, a placeholder until real data arrived. But on the next request for that car, the pipeline found an existing anchor, skipped the live search, and injected the model's old guess back into the prompt as if it were market data. The model was seasoning its own hallucination and being told it was ground truth. Wrong prices weren't just persisting. They were compounding.
The fix was one rule with a big lesson inside it: provenance beats existence. Every anchor carries its source, and model-generated anchors are now bypassed at read time, which forces a fresh live search to overwrite them. Trust ranking: manually verified > live search > nothing. A model's own output never re-enters its context labeled as data.
If you're building anything where LLM outputs get cached and might be fed back in, label the provenance of every stored value and decide explicitly what the model is allowed to see again. Feedback loops in LLM systems don't announce themselves. They just slowly replace your data with the model's opinion of itself. Search results have their own failure modes: a scam listing, a clickbait price, a snippet quoting the wrong trim. So the final guard is dumb and effective: a table of price floors by brand and year. If an anchor claims a 2018 Range Rover Velar costs less than ₦35 million, the anchor is rejected as implausible, because no real Velar in Nigeria does. It's the kind of rule a human dealer applies without thinking, encoded as the system's sanity check.
The next challenge is pricing cars with almost no local listing data at all: Chinese brands like Changan and Chery are entering Nigeria faster than resale data about them exists, so the anchor itself has to be synthesized from new-price and import-cost signals. Thin-data pricing is its own problem, and probably its own article.
I'm building AutoValue in the open at autovalue.tech. If you're working on LLM products for markets the training data forgot, I'd genuinely like to compare notes.