Offer a spelling when a search finds nothing

The corrector landed in fa0f6d7 unwired, pending a week of zero-result logs to
say what it should be aimed at. Attaching it now instead: the guards that make
it safe here are principled rather than tuned to any particular data, so the
logs would confirm the shape of the problem without changing the answer.

ONLY ON ZERO RESULTS. A search that finds something pays for none of this - no
vocabulary build, no scoring, no extra query.

The vocabulary is harvested from columns people actually search - asset numbers
and names, vendors, models, applications, asset types - cached ten minutes and
capped per column. Long enough that someone mistyping twice does not rebuild it,
short enough that a vendor entered this morning is correctable before lunch. The
cap is there because this exists to catch a typo against the names in use, not
to mirror the database into memory to answer one query.

SUGGESTED, NEVER APPLIED. The response carries the better spelling and the
caller decides. Searching for something else on a person's behalf is how
somebody orders the wrong cartridge.

FAILS SOFT. A missing column on a lean install, or anything else, returns no
suggestion and the search answers normally. No vocabulary is worse than the
alternative; it is not fatal.

Three tests through the endpoint rather than only the unit: a misspelling gets a
suggestion, a search that succeeds gets none, and CSF18 against an existing
CSF17 returns no results AND no suggestion - the digit guard holding across the
whole stack, which is the one that would cost somebody a wrong bay.

The zero-result logging stays. It answers "what can people not find", which the
corrector does not: a typo is only one of the reasons a search comes back empty,
and the others are vocabulary nobody has entered and records that do not exist.

NOT DONE: the search page does not render the suggestion yet, so this is
invisible to a person until that Vue change lands.
This commit is contained in:
cproudlock
2026-08-21 10:41:14 -04:00
parent fa0f6d7ebc
commit 1593ee8204
2 changed files with 126 additions and 1 deletions

View File

@@ -107,3 +107,56 @@ def test_the_vocabulary_comes_from_the_data():
assert fresh.suggest('renishw') is None
fresh.add('Renishaw')
assert fresh.suggest('renishw') == 'renishaw'
# ------------------------------------------------------- wired into search
def test_a_search_that_finds_nothing_offers_a_spelling(client, db, auth_headers):
"""End to end: the vocabulary is harvested from real rows, and a misspelled
query that returns no results comes back with a better spelling."""
from shopdb.core.models import Vendor
from shopdb.extensions import cache
db.session.add(Vendor(vendor='Keyence'))
db.session.commit()
cache.delete('search_vocabulary')
resp = client.get('/api/search?q=keyecne', headers=auth_headers)
assert resp.status_code == 200
data = resp.get_json()['data']
assert data['results'] == []
assert data.get('suggestion') == 'keyence'
def test_a_search_that_finds_something_offers_nothing(client, db, auth_headers):
from shopdb.core.models import Vendor
from shopdb.extensions import cache
db.session.add(Vendor(vendor='Keyence'))
db.session.commit()
cache.delete('search_vocabulary')
resp = client.get('/api/search?q=keyence', headers=auth_headers)
assert 'suggestion' not in resp.get_json()['data']
def test_a_mistyped_asset_number_is_not_corrected_to_a_real_one(
client, db, auth_headers):
"""The guard, through the endpoint. CSF17 exists; CSF18 does not; and the
answer to "CSF18" must be no results and NO suggestion, not a different
bay."""
from shopdb.core.models import Asset, AssetType
from shopdb.extensions import cache
assettype = AssetType.query.first() or AssetType(assettype='machine')
if not assettype.assettypeid:
db.session.add(assettype)
db.session.flush()
db.session.add(Asset(assetnumber='CSF17', assettypeid=assettype.assettypeid))
db.session.commit()
cache.delete('search_vocabulary')
resp = client.get('/api/search?q=CSF18', headers=auth_headers)
data = resp.get_json()['data']
assert data['results'] == []
assert 'suggestion' not in data