localis

Describe a service in plain words, or type a CPT/HCPCS code.

6 min read

A missing price is not one kind of missing

Search used to answer with a code, a category label, and a blurb. It never said what Medicare pays. For a rates product, that's a strange thing to withhold from its own search box — someone typing "99213" usually wants to know what it's worth, not just that it exists.

Adding a price under each result sounds like formatting. It wasn't. A search hit isn't a code page with the surrounding context stripped away — it's a list of up to fifteen codes. Each one can be payable, carrier-priced, bundled, retired, or not even in the current release, and each has to resolve to the right kind of answer instead of a blank guess.

One query budget, not one price per result

The code page already had a way to compute a national price: pull the current release's RVU line, pull the national conversion factor, run both through the rate engine once for each setting. Doing that per search result would mean a query per hit — fine for one code page, not for a dropdown that reruns on every keystroke, or a results page that can hold up to fifteen codes at once.

So the search-side version fetches everything once, keyed by code, and computes from the shared release and conversion factor:

$lines = RvuLine::query()
    ->where('release_id', $release->id)
    ->where('modifier', '')
    ->whereIn('hcpcs', $codes)
    ->get()
    ->keyBy('hcpcs');

$cf = ConversionFactor::query()
    ->where('release_id', $release->id)
    ->where('type', ConversionFactor::TYPE_PHYSICIAN)
    ->whereNull('locality_id')
    ->first();

That's a fixed number of queries — release, RVU lines, conversion factor — regardless of whether the page holds one result or fifteen. The per-result work that follows — building a RateInput and running it through the engine — is pure computation from there, the same formula the code page already trusted.

A blank price has more than one cause

The rate engine already refuses to turn a blank RVU into a fake $0.00 — that's a hard invariant, not new. What search adds is more ways to land on "no price," and each one means something different:

  • the code isn't in the current release's RVU lines at all — a concept-search hit that isn't a billable code today, or one that's aged out;
  • it's in the release, but the RVU for the requested setting is blank;
  • its status isn't separately payable under the PFS (bundled, anesthesia, technical-component-only);
  • it's carrier-priced (status C) and we hold no contractor fee data for it.

The first three all resolve the same way the code page already resolves them — the engine's payability check does the work, unchanged:

if (in_array($status, $this->carrierPricedStatuses, true)) {
    return new RateResult(
        payable: false,
        amount: null,
        // ...
        reason: 'Carrier-priced: no national payment amount.',
    );
}

The fourth case is the one search needed something new for.

A range computed once has to agree with a range computed a hundred times

For a carrier-priced code, the code page already shows a range — the min/max of published contractor amounts on file, computed per code when someone lands on that page. Search needed the same range for however many carrier-priced codes land in one result list, without a query per code.

The risk in writing a second, batched version of that computation is silent drift: if the batch query's filtering doesn't match the single-code version exactly, the same code could show one range on its own page and a different range in search — two numbers for one fact, which is worse than showing neither.

So the batched lookup keeps the same exclusions the single-code version uses — skip modifier-specific rows, skip the contractor's facility-setting flag — just applied across every candidate code in one query instead of one code at a time:

$rows = DB::table('mac_fees')
    ->join('mac_fee_schedules', 'mac_fee_schedules.id', '=', 'mac_fees.schedule_id')
    ->whereNull('mac_fee_schedules.superseded_at')
    ->whereIn('mac_fees.hcpcs', $hcpcsList)
    ->where('mac_fees.modifier', '')
    ->whereRaw("trim(mac_fees.note) != '#'")
    ->selectRaw('mac_fees.hcpcs as hcpcs, coalesce(mac_fees.par_amount, mac_fees.non_par_amount) as amount')
    ->get()
    ->filter(fn (object $row): bool => $row->amount !== null);

PHP reduces those rows to a min, max, and locality count, per code. One query answers every carrier-priced code on the page, the same way the code page would have answered each of them one at a time.

A curated pick earns a different default than a typed one

The last piece wasn't about pricing at all. Opening search with nothing typed now shows a small, already-priced shortlist of popular codes instead of an empty box. That reuses the same fallback the code lane already had for a typed code with no clean-room description yet: show the bare code and a generic label rather than nothing.

That fallback earns its keep on a typed query — the visitor asked for that specific code, so confirming it exists and pointing at its page is an honest answer even without prose. A curated list is a different case. Nobody asked for "99213" specifically; we chose to show it. If a popular code has nothing on record at all — no description, no RVU line, nothing — showing a stub for it asserts data we don't have, on a pick the visitor didn't request. So the curated path checks before it falls back:

if ($description !== null) {
    $results[] = self::fromDescription($description, 'code');
    continue;
}

if (RvuLine::query()->where('hcpcs', $code)->exists()) {
    $results[] = new CodeSearchResult(/* ... */);
}

Same fallback logic, same underlying data, different policy about when silence is more honest than a stub — because the two callers are asking different questions.

The pattern

Nothing here is a new formula. The rate engine, the MAC range, the description fallback all already existed on the code page; search just needed to reach them without a query per result:

  • a batched computation across many results has to cost what one release lookup costs, not one query times the result count;
  • a blank price isn't one fact — "not in this release," "blank for this setting," "not separately payable," and "carrier-priced with nothing on file" are different reasons, and the answer should say which;
  • a range computed once for a code page and a range computed for a hundred search hits have to filter identically, or the same code disagrees with itself depending on which page you asked; and
  • a curated shortlist and a typed query deserve different defaults for the same missing data — one is a promise we made, the other is a question the visitor asked.
← All posts