Loading Botifex…

How We're Improving the Botifex Deal Scorer (And What That Number Actually Means)

If you source across Facebook Marketplace, Craigslist, eBay, Mercari, and Poshmark in the same week, you already know the problem: there are more listings than anyone can research by hand. A $90 laptop can be a steal, a trap, or a six-week hold depending on the comps, the condition, and whether anyone actually wants that model right now. Speed without judgment just helps you buy the wrong inventory faster.

That is why Botifex has a deal scorer. It is not a magic "buy this" button. It is a triage layer that compresses market context into a 0–100 number so you can decide, in a couple of seconds, whether a listing is worth five minutes of real research. When founder and CEO Rhev Williams started building Botifex, the goal was never to replace a reseller's eye. It was to stop wasting that eye on listings that were never going to clear a profit after fees, shipping, and time.

This post is a look at how that scorer actually works at a high level, what we are improving, and why some of the hardest work is not "smarter AI" — it is making the score stay honest when marketplace data is incomplete, inconsistent, or just plain weird. We will show a few simplified snippets from our own code so the engineering is concrete. We will not publish the production formula, the model internals, or the training recipe. Those are the parts that make the score useful. They are also the parts we do not hand to competitors.

Cheap Is Not the Same Thing as a Deal

Most sourcing mistakes start with a feeling. The asking price looks low. The photos are decent. Someone in a Facebook group sold "one like this" last month for more. You message the seller. Then the item sits, the comps you remembered were for a different year or a different condition, and the "deal" becomes a lesson in working capital.

A deal is not a low price. A deal is a price that is low relative to what similar items actually sell for, in a market that absorbs inventory on a timeline you can live with, after the costs of getting paid. That is a different question from "does this look inexpensive." Inexpensive furniture that takes 90 days to sell is not the same product as an inexpensive phone that turns in a week.

The deal scorer exists because that second question is expensive to answer at volume. Pulling sold comps, checking how crowded the active listings are, estimating fees, and reading whether the listing even has enough information to trust — that is real work. Doing it for every alert is how people burn out. Skipping it is how people stack dead inventory. A score is the compromise: enough signal to sort the feed, not a substitute for knowing your category.

What the Number Is For

On Botifex, a deal score is a 0–100 rating attached to a listing. Higher generally means the listing looks more interesting relative to recent market context. Lower generally means one or more of the ingredients that make a flip work are weak: the price is not actually discounted against sold comps, demand is soft, the item takes too long to move, the category is crowded, or the listing itself is thin.

The product surface is deliberately simple. You see a badge, and in more detailed views you also see supporting context: roughly how fast similar items sell, a sell-through read, a resale range, and an estimated profit figure when we have enough data to estimate one. The point of that extra context is to keep the score from becoming a black box. A number without a story trains people to either ignore it or obey it. Neither is a good sourcing habit.

We also map scores into plain-language verdicts for the machine-learning path — useful shorthand when you are scanning, not a moral judgment of the item. A "skip" verdict does not mean the object is junk. It means, given the data we have, this listing is not where we would spend your attention first.

A score is triage, not a purchase order.

Use a high score to decide what to research next. Use a low score as a reason to slow down. Do not use either as a replacement for condition checks, authenticity, local pickup risk, or your own category knowledge.

The Five Families of Signal

Under the badge is a rule-based engine we still treat as the backbone of the product. Machine learning sits on top of it. The rules are what keep the score explainable when a trained model is not available, and they are what we fall back to if inference fails. At a high level — without the production weights or the cutoff tables — the engine looks at five families of signal.

  • Price versus the market. How the asking price sits against recent sold comps for similar items. A listing only looks "cheap" if the reference market is real. This is the heaviest family of signal, which should not surprise anyone who has lost money on a pretty photo.
  • Demand. Whether similar items actually sell, not just whether they get listed. Sell-through is the difference between a busy search page and a liquid category.
  • Speed. How long comparable inventory tends to sit. A fat spread that takes two months to realize is a different business than a thinner spread that clears in a few days, especially if you are turning a small bankroll.
  • Competition. How much active supply is already out there. A discount in a flooded category is not the same as a discount in a tight one.
  • Listing quality / resale readiness. Whether the listing gives a buyer (and a reseller) enough to work with: a usable title, photos, a condition, a description. Thin listings are not automatically scams, but they are harder to underwrite.

Those five are combined into one integer, then clamped so a bug or a missing field cannot push the number off the 0–100 scale users see. We also attach a coarse risk label derived from the score, and — when sold comps exist — an estimated resale, net profit, and ROI after platform-specific fees. The fee math is not the same on eBay as it is on a local Facebook pickup, and pretending otherwise is how "$150 profit" turns into $40.

We are intentionally not publishing the exact mix or the internal scoring bands. Publishing those would make it easier for copycat tools to mimic the number without doing the market-data work, and it would make it easier for noisy listings to be tuned against a public rubric. What matters for you as a user is the shape of the question: is this price actually low, will it move, and what is left after costs?

The Unglamorous Work: Messy Marketplace Data

If you have ever built anything on top of marketplace listings, you already know the first boss fight is not the model. It is the data. Prices arrive as numbers, as strings, as blanks, and occasionally as values that would make a spreadsheet cry. Titles are truncated. Market metrics may be sitting on the listing from an earlier research pass, or they may not exist yet. A scorer that assumes a clean schema will either crash or, worse, invent confidence.

A large part of the current improvement work is making the scorer boring in the best way: it should degrade gracefully. Invalid input returns a conservative empty result instead of a fake 92. Negative prices get treated as unusable. Missing market data does not get hallucinated into a "great deal." When we lack comps, several of the rule factors score at a neutral midpoint rather than pretending we have evidence we do not.

Here is a simplified version of two helpers from our deal-scoring service. This is the kind of code that never shows up in a product screenshot and still decides whether the number on your screen is trustworthy.

def _to_float(value, default=0.0, minimum=None): """Marketplace fields are not always numeric.""" try: numeric = float(value) except (TypeError, ValueError): return default if minimum is not None and numeric < minimum: return minimum return numeric def _market_data_from_listing(listing): """Use comps already stored on the listing when we can.""" market_json = listing.get("market_metrics_json") if not market_json: return None try: return json.loads(market_json) if isinstance( market_json, str ) else market_json except Exception: return None

That second helper matters more than it looks. Live market fetches are useful, but they are also slow, rate-limited, and easy to overuse if every card in a hunt view kicks off a fresh research call. The scorer prefers metrics that were already attached to the listing. Only if those are missing — and only when the caller asked for a live fetch — do we go out and research. When the machine-learning path scores a listing, it explicitly turns live fetching off and works from what is already known. That keeps scoring fast enough to use in a feed, and it keeps us from turning "show me my listings" into an accidental denial-of-service against our own research pipeline.

We test this path with the kind of input production actually sees. One of the regression tests feeds the scorer an iPhone listing whose price and every market field are strings — "120", "200", "75" — because that is how scraped and cached data often arrives. The test does not care about a golden score. It cares that the result is an integer on the 0–100 scale, not an exception. That is the standard: the scorer should survive the internet.

Profit After Fees, Not the Sticker Spread

A second improvement theme is refusing to treat "buy price versus sold average" as profit. Resellers already know this, and software still gets it wrong all the time. If you buy at $100 and the median sold is $180, you do not have $80. You have $180 minus platform fees, payment fees, shipping, and the time the money is tied up. On some platforms those costs are negligible because the sale is local and cash. On others they are a serious percentage of the ticket.

Our profit calculator keeps a per-platform fee structure and a rough shipping estimate by category so the deal scorer can attach a net profit and ROI figure when a resale reference exists. We are not going to itemize the internal tables here. Fee schedules also change, and a blog post is a bad place to freeze them. The design principle is what we want to be public: if we show you a dollar estimate, it should be trying to represent money you could actually keep, not a screenshot-friendly spread.

That estimate is still an estimate. It does not know that your particular laptop is missing a charger, or that you can ship it cheaper than our category default, or that you plan to sell locally and skip the platform fee the comps assumed. Treat it as a first cut. If the first cut is ugly, the listing probably is not a secret winner. If the first cut is beautiful, you still owe the listing a real look.

Rules First, Then a Model That Knows Its Lane

A rule-based scorer is honest, fast, and debuggable. It is also rigid. It cannot notice that in one category a slightly incomplete title is normal, while in another it is a warning. It cannot pick up on phrasing patterns that experienced resellers feel immediately. And it cannot improve just because hundreds of real buying decisions have happened since last month.

That is why we added a machine-learning layer on top of the rules. The important architectural choice is not "we use AI." Plenty of products say that. The important choice is category isolation with a fallback ladder. A good score for a used sedan is not a good score for a pair of sneakers, and mixing those training worlds is how you get a model that is confidently wrong in both.

When we score a listing, we first ask whether a model exists for that listing's category. If it does, we use that model and only that model. If it does not, we try a global model trained across categories. If that is missing or inference fails, we fall back to the rule-based engine. Users should get a score either way. The source of the score can change; the product should not go blank.

Here is a simplified version of that ladder. The production code has more caching, more logging, and more care around loading weights — none of which belongs in a public blog post. The control flow is the part that is fair to show, because it is a product promise: we will not let a missing model take the number off your listings.

def score_listing(listing): """Category model, then global model, then rules.""" category = detect_category(listing) if category and category_model_is_ready(category): try: return ml_score(listing, model="category") except Exception: pass # try the next rung if global_model_is_ready(): try: return ml_score(listing, model="global") except Exception: pass return rule_based_score(listing)

The machine-learning path reads both the listing text and a small set of numeric market features, then returns a 0–100 score, a verdict, and a confidence value. We are not describing the network, the embedding model, or the feature vector. Those details are how the scorer gets better without becoming a public blueprint. What you should take away is the design: language and market context together, category-specific when we can, globally competent when we cannot, and never blocking on ML being available.

How a Model Learns Without Getting Poisoned

Training data is the quiet graveyard of deal-scoring products. If you only train on a handful of hand-labeled examples, the model never sees enough of the world. If you autogenerate thousands of labels from comps without quality control, you teach the model to repeat the biases in your research pipeline. If you mix those sources as equals, the synthetic volume drowns the human judgment you actually wanted to learn from.

Our training dataset builder treats that as a first-class problem. There are human ratings, and there are labels derived from market analysis when the comps are strong enough to justify one. Those two sources are not weighted the same. Human labels are trusted more. Market-derived labels are down-weighted and gated. If the comps are thin, the confidence is low, or the listing price is unusable, we do not quietly mint a training row and hope.

There is also a boring, essential guard: rows where the numeric features are effectively all zero get dropped. A model trained on empty rows learns to shrug. In practice that looks like a scorer that clusters around a bland middle score no matter what you show it. We would rather train on fewer clean examples than a mountain of nothing.

Admins can choose a training mode depending on where we are in the data lifecycle: human labels only when we want the conservative path, a hybrid mix when the market-derived labels have earned their keep, or a synthetic-only pass when we are bootstrapping a category and need something to even start from. Hybrid is the interesting one in production because it is how you grow a model without pretending that generated labels are as good as a person who actually knows whether a listing was a deal.

We version the market-derived scoring logic so that when the labeler improves, old under-placed ratings can be rebuilt before the next train. That sounds like plumbing. It is plumbing. It is also the difference between a model that slowly fossilizes around last quarter's mistakes and a model that can absorb a better definition of "deal" without a full reset.

The Score Has to Keep Up With the Market

Resale is not a static dataset. A category that was easy money in March can be crowded in August. A phone generation ages. Seasonal gear moves, then it does not. A scorer that is trained once and left alone becomes a museum exhibit of last season's prices.

That is why deal-score training is on a daily cadence, not a "we will retrain when someone remembers" cadence. A worker claims a once-per-day lock, refreshes market comps, rebuilds eligible labels, and trains if there is enough data to justify it. If a run crashes, it can retry. If two app instances would otherwise both decide it is noon, the lock keeps them from double-training and stepping on each other. None of that is visible in the hunt view. All of it is why the number on a listing in October is not just a reprint of the number from June.

After a successful train, the scoring service can reload weights without pretending the process is magic. Reload the global model, or reload a single category model, and the next listing through the ladder uses the new one. That sounds obvious. It is surprisingly easy to ship a world where training writes a file and inference keeps using the copy it loaded at boot. We do not want that world.

What You Actually See While You Hunt

Engineering only matters if it changes the two seconds you spend on a card. In the hunt feed, deal scores load per listing so the page can render before every research call has finished. You get a badge on the 0–100 scale, a tooltip with more detail when it is available, and the ability to sort or filter toward stronger scores when you want the feed to work like a shortlist instead of a firehose.

On the best-deals surface, the same score sits next to the context that keeps it honest. If we know roughly how many days similar items take to sell, we show it. If we know a resale range, we show it. If we know a sell-through rate, we show it. If we can estimate net profit, we badge that too. A high score with a long days-to-sell read is a different decision than a high score that also says the category turns quickly. The badge starts the conversation. The intel is what lets you finish it without opening ten extra tabs.

Saved searches can also take a deal-score threshold, alongside profit and ROI thresholds, so alerts are not just "this matched your keyword." Keyword matches are how you get noise. Thresholds are how you get a work queue. We would rather email you fewer times with listings that cleared a bar you set than congratulate ourselves on volume.

A Deal Score Is Not a Trust Score

One more distinction we are careful about, because mixing these two ideas is how tools get people hurt. A listing can look like a statistically excellent buy and still be a bad idea to engage with. Off-platform payment pressure, "must sell today" theater, as-is language that does not match the price, or a discount so extreme it stops looking like a deal and starts looking like bait — those are trust problems, not comp problems.

Botifex keeps seller-trust scoring as a separate system on purpose. The deal scorer asks "does the economics make sense." The trust scorer asks "does this listing behave like a listing you should take at face value." Combining them into one number would make the badge impossible to interpret. Was this a 40 because the comps are weak, or because the description is full of payment red flags? Those are different next actions. One means keep scrolling. The other means do not send money.

We are not going to publish the trust patterns, either. Public pattern lists become a checklist for the exact listings you do not want more of. The user-facing idea is enough: cheap plus sketchy is not a deal. It is a story people tell after they got burned.

What "Improving" Means for the Next Quarter

When we say we are improving the deal scorer, we do not mean we are chasing a leaderboard of model-benchmark vanity. We mean the number on the card should get harder to fool and easier to use. In practice that is a pile of unglamorous projects, most of which look like quality control from the inside.

  • Better comps in, better scores out. A scorer cannot outthink a bad research pass. Tighter matching, clearer confidence, and refusing to label listings that did not earn a label will do more for accuracy than a fancier network diagram.
  • More category models where they earn their keep. Isolation only helps if the category has enough clean examples. We would rather keep a strong global fallback than ship a half-trained specialist that is worse than the rules.
  • Human ratings remaining the north star. Generated labels are how you scale. People who actually buy and pass are how you stay calibrated. The weighting will keep reflecting that.
  • Clearer breakdowns without exposing the recipe. Users should understand why a listing scored the way it did — price, demand, speed, competition, listing quality — without us publishing a spec that a clone can paste into a weekend project.
  • Scores that fail closed. If the data is junk, the score should look uncertain or conservative, not lucky. False confidence is more expensive than a blank badge.

As Rhev Williams has been fond of saying internally, the scorer should make a good reseller faster, not make a careless reseller feel certain. That is a product constraint, not a slogan. Every time we add a new signal, we ask whether it helps someone decide, or whether it just makes the badge more decorative. Decorative numbers are how people stop looking at photos. We would like you to keep looking at photos.

How to Use the Score Without Letting It Use You

If you take one practical habit from this post, take this workflow. When an alert hits, glance at the score and the supporting intel first. If the score is weak and the days-to-sell read is long, you can usually keep moving. If the score is strong, open the listing anyway. Confirm the model, the condition, the included accessories, and whether the photos match the title. Then look at the estimated profit as a sanity check, not as a promise.

Category expertise still wins edge cases. A score cannot know that a particular revision is about to get a cult following, or that a local campus emptying out will dump a specific desk for two weeks. It also cannot know that you already have three of these in the garage. The scorer does not have your warehouse. You do.

If you want the longer version of the research habit the scorer is automating — comps, sell-through, and after-fee math done slowly and on purpose — we already wrote that guide: How to Research Resale Value Before You Buy. The scorer is that guide, compressed, running on every listing we can attach market context to. The guide is still worth reading, because the day the score and your gut disagree, you will want to know how to break the tie yourself.

We are going to keep improving this system in public enough that you can trust the direction, and in private enough that the work stays ours. If the badge on your next hunt run feels a little more aligned with what you would have concluded after twenty minutes of research, that is the win. Not a viral model demo. Not a leaked formula. Just fewer bad buys, and a little more time back for the listings that deserve it.

FAQs

What is a Botifex deal score?

It is a 0–100 rating that summarizes whether a listing looks like a stronger or weaker flip candidate based on price versus sold comps, demand, how fast similar items sell, how crowded the category is, and listing quality. It is a triage tool, not a guarantee.

Does the deal scorer use AI?

Yes, as a layer on top of a rule-based engine. When a trained model is available — especially a category-specific one — we use it. If it is not, or if inference fails, we fall back to the rules so listings still get a score.

Why would two similar listings score differently?

Usually because the market context is different: one has tighter comps, better sell-through, less active competition, a more complete listing, or a better after-fee spread. Titles that look similar to a human can still map to different sold-comp sets.

Should I buy every high-scoring listing?

No. A high score means the listing is worth researching first. You still need to verify condition, authenticity, logistics, and seller trust. A separate trust score exists specifically because a statistically cheap listing can still be a bad interaction.

Will Botifex publish the exact deal-score formula?

No. We explain the signal families and the product behavior — including fallbacks, after-fee estimates, and category isolation — because that helps you use the number. The production weights, model internals, and training recipe stay private so the scorer remains useful and harder to clone.