Engine Design · Deep Walkthrough · Your Go Before I Touch It

Never-Blank Scores

Your rule: a score should never show up as a blank dash. This is the full walkthrough — how a grade actually reaches the screen, exactly where the blanks come from, the fix for each one with real numbers, and the three calls I want from you before I move a single grade.

2026-07-13 · engine change · design gate · reads → gradeToInput.ts · overall → score.ts · bands from constants.ts
The rule, in one line

Every read (Is the price fair? · Will you love it? · Holds its value?) and the overall grade always resolve to a letter — a fact-based grade when there are no reviews yet, upgraded to real opinion once a set has been mined. The honesty moves to the small confidence line underneath ("Early read"), never to a missing number.

68%
of sets show a price letter today — the other 32% go blank
2%
show a love letter — it's wired to the wrong signal, so 98% blank
some
sets show a blank overall grade — the engine deliberately abstains

01What a blank looks like

When the engine doesn't have enough to say something, it shows a dash — — — instead of a letter. It does this on the individual reads and, for some sets, on the big overall grade too.

You've been clear about why that's wrong, and I want to restate it precisely because it drives every decision below. A blank fails on two counts at once:

It reads as broken
A shopper doesn't think "they're being careful" — they think "this app is half-finished." A dash next to a polished grade card looks like a bug, not a decision. It's the one thing on the card with no information in it.
It isn't even honest
A blank says nothing. That's not restraint — it's absence. We always have facts: pieces, minifigs, price, part-out value, theme, year. Staying silent when we have real facts is a choice to under-serve, not to be truthful.
Today — the blank
—
Will you love it?
Reads asBROKEN
→
The rule — always a letter
B
Will you love it?
Confidence lineEarly read · sharpens with reviews
Why this deliberately overrides the old spec

The original spec's principle #4 said "honest blank — withhold when there isn't enough." You've since reversed it: the blank isn't honesty, it's a hole. Honesty now lives in the confidence line, which can say "Early read" out loud without hiding the grade. This doc is that reversal, made concrete and testable.

02How a grade actually reaches the screen

To see where the blanks come from — and why one fix is nearly free and the other is real surgery — you have to see the assembly line. Here it is in plain language.

  1. Facts come in.For a set we gather objective facts: piece count, minifig count, price (street or MSRP), part-out value, theme, release year, and whatever reviews exist.
  2. The engine scores 15 signals, each 0–10.Things like "What You Get," "Build Length," "Display Appeal." Some are pure math from facts (build length from piece count). Some come only from reviews (display appeal). If a signal has no basis, it's marked insufficient — the engine's word for "not enough to score this one."
  3. Signals roll up into 7 clusters, then into one overall number.Signals group into clusters (Money & Worth, The Build, Finished Model…); clusters are weighted by lens (Everyone / Display / Builder / Gifting / Value) into a single 0–10.
  4. The 0–10 becomes a letter.Fixed bands: 8.5–8.9 = A−, 7.0–7.4 = B−, 6.0–6.4 = C, and so on down to F. A 7.0 is a B−, exactly.
  5. The whole grade is saved to the database.Overall letter, every signal's 0–10, the confidence, the hold-value badge — all written to a drop_scores row per set and lens.
  6. The page reads the SAVED row and draws it.This is the crucial part: opening a set page does not re-run the engine. It loads the saved numbers and maps them onto the card — the big grade, and the three reads.
The one sentence that reorganizes everything

The three reads are decided at step 6 — a mapping applied when the page is drawn, on top of numbers we already saved. The overall grade is decided at step 3 — inside the engine's math. That's why fixing the reads never moves a grade, and fixing the overall does.

03The key finding: two very different fixes hide inside one rule

"Never-blank" is really two separate jobs with very different risk — and that's good news, because one is nearly free and totally safe.

 The 3 reads (price / love / hold)The overall grade
Decided at Step 6 — the draw-time mapping (gradeToInput.ts) Step 3 — the engine's math (score.ts)
Where the blank comes from Each read is mapped to one saved signal; blank when that signal is insufficient An "abstain" rule hides the letter when evidence is thin
Does fixing it move any saved grade? No pure re-draw from data we already saved Yes it changes the saved letter
Database re-write needed? None — changes on the next page load Offline recompute — the same safe pattern that activated investor value
Risk Low · display only · reversible in one file Core engine · test-driven · re-grade gated on your go
A plain analogy

The saved grade is a printed nutrition label on a box. Fixing the reads is like re-reading the same label and filling in a blank line using numbers already printed on it — the box doesn't change. Fixing the overall is like re-running the lab test and printing a new label — so we do it carefully, test it, and only re-print the whole catalog once you say go.

04Part 1 — the three reads always land

All three reads get a guaranteed answer. Two keep their current signal and gain a fallback; one — "Will you love it?" — gets rewired to the right signal entirely. Here's each, with the reasoning and real numbers.

Is the price fair?  keep + fallback

Keeps Smart Price whenever it can score. Smart Price answers "for what this costs, is the value there?" — ideally by comparing the set's part-out value to its price, and ranking that against similar sets.

Why 32% go blank: those are sets where we have no part-out value and no cohort of comparable sets to rank against. The value math has nothing real to stand on, so — correctly — it refuses to invent a number. It won't fabricate a neutral 6.0.

The fallback: fall back to "What You Get" — our content-value-for-the-money signal (non-figure prints + element variety, minus filler), which we compute on virtually every set. It's the honest sibling of "is the price fair," and it's carried at lower confidence so the line underneath stays truthful.

The hard line this respects

The fallback is emphatically not a price-per-piece or cost-per-brick number. That stays banned — in the copy and enforced in code. "What You Get" is about content richness, not dividing dollars by bricks.

Will you love it?  rewire

This is the big one, and it's the source of the 98% blank. Today "love" is wired to a signal the engine itself describes as "a capped nudge, not a real sub-dimension." It only fires when a reviewer literally voices "worth it" — so even the X-Wing, which has been mined, still shows blank for love. It was never a real home for the question.

"Will you love it?" is really an enjoyment question. So I'd back it with an enjoyment proxy in two layers: a fact baseline that's always there, upgraded by real opinion once reviews exist.

Fact baseline — always present
What You Get — how much set your money buys Minifig lineup — figures for the price Build length — how much building there is Every set has these, so the read is never blank
Opinion upgrade — when mined
Display appeal — does the finished model look great Fidelity / scale — does it nail the subject Build fun — is the build itself enjoyable Blended over the baseline once reviews exist

Here's the same set, worked all the way through, so the mechanic is concrete. Recommended weights: fact baseline = What You Get 50% · Minifig lineup 25% · Build length 25%; then once mined, blend opinion over facts 60/40.

StageThe math0–10Letter
Fact baseline
un-mined set
What You Get 7.0 × 0.50  +  Minifigs 6.0 × 0.25  +  Build Length 8.0 × 0.25
= 3.5 + 1.5 + 2.0
7.0 B−
Opinion upgrade
after mining
Opinion mean (Display 8.7, Fidelity 8.0, Build Fun 7.8) = 8.17
Blend: 8.17 × 0.60  +  baseline 7.0 × 0.40 = 4.9 + 2.8
7.7 B
Read what just happened

The un-mined set shows a real, defensible B− for "will you love it" today — no blank. The same set, once mined, quietly sharpens to a B built on actual reviewer sentiment. Mining upgrades the read instead of filling it from empty — which is exactly the seam the review-mining half of this session plugs into. Figure-less sets simply reweight the remaining two facets so the baseline still holds.

Holds its value?  already fine + floor

Already lands on a letter about 98% of the time, because its main input — appreciation outlook (age progress toward retirement × theme desirability) — always scores, even for a brand-new set (age 0 still produces a number, just at lower confidence). The remaining ~2% are edge cases with no part-out value at all. I'll add a small floor so those can't slip to a blank either. This one is a safety net, not a rewire.

Where all of Part 1 lives

Everything above is a change to one small mapping file (gradeToInput.ts) that runs at step 6 — when the page is drawn. It reads signals we already computed and saved. No engine re-run, no re-grade, no database write. The reads simply fill in on the next page load, and it's reversible by reverting one file.

05Part 2 — the overall grade stops abstaining

This is the real engine move. For some sets the engine looks at the evidence, decides it's too thin, and hides the overall letter. The change: if we have facts for a set — and we always do — it always bands to a letter.

The exact rule today, in plain words: the engine withholds the overall grade when "there's no strong value anchor and fewer than two lens areas managed to score." That "no value anchor + too few areas" condition is the main thing hiding overall grades. (There are two other conditions — "literally nothing scored" and "the overall math came back empty" — but every real set has fact signals like build length and appreciation that always score, so those two effectively never fire.)

The change: retire the "no value anchor" abstention for any real set. Because fact-based signals always produce something, there's always a legitimate fact-derived letter to show. The grade goes from "we won't say" to "here's our best read from the facts, labeled as early."

Today
—
The Brick Drop Score
LineNot enough to grade yet
→
After
C+
The Brick Drop Score
LineEarly read — sharpens as reviews land
The honest tradeoff, stated plainly

This is a genuine shift in what the app claims. Sets that showed a dash will now show a letter — a real read, not a placeholder. I think it's the right call and it's the one you asked for; I want it on the record before I move a single grade. And note the ripple: the phrase "Not enough to grade yet" effectively retires for real sets — a visible copy change, not just a numeric one.

06Where the honesty goes instead

We don't lose honesty — we relocate it. The confidence line under every grade already knows how thin the evidence is and says so in plain words. That line does the work the blank used to do, without looking broken.

SituationThe line the shopper seesWhat it signals
Not released yet"Early read — sharpens as reviews land"Facts-only prediction, openly labeled
Just launched (≤5 reviews)"Early read · 3 reviews"A letter, but hold it loosely
Well reviewed"Based on 28 reviews · solid"Firm, sentiment-backed grade

So a brand-new set with no reviews still shows a letter, clearly flagged as an early, facts-only read. A set with 30 reviews shows the same letter styling but a confident line. The shopper always gets a grade and an honest sense of how firm it is — which is strictly more informative than a dash that says nothing.

07Edge cases & guardrails

The things I'd make sure can't go wrong, stated up front.

No facts row at all → not a real set page Figure-less set → love baseline reweights Price-per-piece → still banned in code Old saved grades → keep validating Low confidence → shown, never hidden
Edge caseHow it's handled
A set with genuinely no facts (no piece count, no price)That's not a real product page — the app already returns "not found" upstream, so there's no card to blank. Never-blank applies to real sets, which always have facts.
A figure-less set (no minifig lineup)The love baseline drops the minifig facet and reweights What You Get / Build Length so it still resolves — no divide-by-missing.
The price fallback drifting toward price-per-pieceImpossible by construction — it reuses "What You Get" (content richness), and the existing code guard rejects any per-brick phrasing in copy.
Old grades saved before this changeThe reads re-derive at draw time from saved signals, so old rows light up too with no migration. The overall change is applied by the offline recompute, which re-reads each saved row.
A grade resting on thin evidenceShown as a letter with an honest "Early read / thin" line — the whole point. Confidence is disclosed, never used to hide.

08Three calls I want from you

These are the only real judgment calls. My recommendation is first in each, with the alternative I considered and why I set it aside. Say the word and I run with all three, or redirect any of them.

1 · What should the price read fall back to when Smart Price can't run?

Recommendation: fall back to "What You Get," at low confidence.

It's the honest cousin of "is the price fair," we have it on ~100% of sets, and it never touches price-per-piece.

Alternative considered: let price stay the one read that can still say "can't price this yet." Set aside — it violates the never-blank rule and reintroduces a dash on the read shoppers care about most.

2 · How should the "love" enjoyment proxy be blended?

Recommendation: baseline = What You Get 50% · Minifigs 25% · Build Length 25%; opinion blended over facts 60/40 once mined.

These are tunable constants — easy to dial after you see them on real sets. The worked example in §04 uses exactly these.

Alternative considered: have opinion fully replace the baseline when mined. Set aside — a hard swap makes a set visibly jump when its first review lands; a 60/40 blend sharpens smoothly instead of lurching.

3 · Should the overall grade ever still abstain?

Recommendation: no — any set we have facts for always shows a letter. "Not enough to grade yet" retires.

The confidence line carries all the honesty. The only thing that could still show no grade is a set with no facts row at all — which isn't a real set page.

Alternative considered: keep abstaining for pre-release sets with zero reviews. Set aside — those are exactly the sets a shopper is researching before buying; a facts-based "Early read" serves them far better than a dash.

09How I'd build it — and the re-grade

  1. PR 1 — the three reads.Test-driven change to the draw-time mapping file. No grade moves, no database write. Safe to eyeball live the moment it merges.
  2. PR 2 — the overall grade.Test-driven change to the abstain rule in the core engine, both type-check passes green. Merges the behavior; doesn't yet move saved data.
  3. The re-grade — gated on your go.Offline recompute across all 5,762 sets from their saved signals — the same pure-math pattern that activated investor value. No mining, no external calls, minutes not hours. Writes the universal grade + 4 lens grades. I do not run this until you say go.
  4. Then: review mining.With never-blank done, mining the ~1,945 sets that have reviews upgrades fact-based reads into real opinion — instead of filling from empty. The love read in §04 is the exact thing it sharpens.
The gate

This is the plan you asked to see before I touch the scoring engine. Nothing is built yet. Give me a go — or redirect any of the three calls in §08 — and I'll start with the safe reads PR, then bring you the overall-grade change and the re-grade for a final green light.

Brick Drop · Never-Blank · Design Gate