Skip to content

Search & verification

Debugging a search result

5 min readUpdated Verified against aced1a0 on 2026-09-27

Page actions

ChatGPT and Claude are third-party services. The link gives them this page's public address and a short instruction. This site records nothing.

A playbook for a search that returns the wrong line, no line, or a surprising one: reproduce it, read which tier answered, find the cause, fix it in the right place, and prove the fix corpus-wide.

A report usually reads “I searched for X and got the wrong line”, or no line at all. Search is a waterfall of tiers tuned across the whole Granth, so the fix is rarely where the symptom is. This playbook finds which tier answered, why, and where a fix belongs, then proves the fix did not cost recall elsewhere.

The debugging loop for a search resultReproduce the query against the API and note the mode, which names the tier that answered. Inspect that tier in process: the fold, the variants and the lexicon for the words. Decide where the fix belongs: the lexicon in the platform, or spellings in sggs-data. Measure the harnesses before and after, add the query to the golden contract, and open a pull request only if recall holds corpus-wide.a phrase people typea spelling of a wordtier order or ranking

Reproduce: the API,note mode

Which tier answered,and why?

Where doesthe fix belong?

Platform:the seeker lexicon

sggs-data:variant rules or lists

Platform:the waterfall, with care

Harnesses beforeand after, a goldencase, make contract

The debugging loop for a search resultReproduce the query against the API and note the mode, which names the tier that answered. Inspect that tier in process: the fold, the variants and the lexicon for the words. Decide where the fix belongs: the lexicon in the platform, or spellings in sggs-data. Measure the harnesses before and after, add the query to the golden contract, and open a pull request only if recall holds corpus-wide.a phrase people typea spelling of a wordtier order or ranking

Reproduce: the API,note mode

Which tier answered,and why?

Where doesthe fix belong?

Platform:the seeker lexicon

sggs-data:variant rules or lists

Platform:the waterfall, with care

Harnesses beforeand after, a goldencase, make contract

The debugging loop for a search result

1. Reproduce it

Ask the API exactly what the site asked. Production works; locally, run serve.py:

Terminal window
curl -s 'https://gurbanisoul.com/api/search?q=waheguru&mode=auto&limit=5' | python3 -m json.tool | head -30

Note four things: the mode field, the number of results, the ang and id of the first few, and whether the report was made with a specific mode chip (the site passes mode). /api/search is never cached, so what you see is what the code does now.

2. Read which tier answered

mode names the tier that produced the results. It is a debugging aid, not an API contract (modes and tiers):

modeWhat happenedLook at
gurmukhi, gurmukhi-skeletonGurmukhi input, matched exactly or on the consonant skeletonthe query’s own characters (a stray Latin letter makes it mixed-script)
romanthe transliteration index matched every wordusually right; if the wrong line, it is ranking (refrains, repeated lines)
first-letterstwo or more words of three letters or fewer were read as first lettersa short query that was meant as words
seeker-lexicon (<term>)a whole-query entry in SEEKER_LEXICONthe entry’s terms (variants and the lexicon)
variant-matchthe variant index matched, possibly all words but onethe variants for each word, below
english-translation, englishthe English layerwhether the line has English at all (the translation layer)
passage-match (…)the words span consecutive lineswhether the query crosses a line break
roman-spelling-tolerantthe Roman fold matchedfold collisions, below
themea theme name, or the single-word theme fallbackthe concept block in the response
… (honorifics dropped)“ji”, “sahib” and similar were removed and the rest retriedthe words that remained

Zero results. When nothing matches, mode is simply the last tier tried (often english-translation); it does not mean English was the right place.

3. Find the cause

Inspect the pieces in process, against the pinned database (make dataset first):

Terminal window
cd webapp && SGGS_DB=../db/sggs.sqlite python3 -c "
from sggs.search import do_search
from romannorm import roman_norm
r = do_search('waheguru', 'auto', 5, 0)
print(r['mode'], [x['id'] for x in r['results']])
print(roman_norm('waheguru'))
"
sqlite3 'file:db/sggs.sqlite?mode=ro' "SELECT variant, translit, freq, score, rtype FROM variants WHERE variant = 'waheguru'"
sqlite3 'file:db/sggs.sqlite?mode=ro' "SELECT 1 FROM canon_tokens WHERE token = 'waheguru'"

The usual causes, in the order they are met:

  • An earlier tier answered. A lexicon key that is also a word of the transliteration is answered by roman first; a word that is also a theme name is answered as a theme. Short queries of two or more words of three letters or fewer are read as first letters.
  • The spelling is unknown. No row in variants, not a word in canon_tokens, and the fold finds nothing useful. The fix is a spelling (sggs-data) or a lexicon entry (platform).
  • The fold collides. The fold maps different spellings to one key on purpose; a very short fold matches many lines. Even nonsense can return a line: xqzv folds to gjv, a real key, and gets a variant-match. A single-character fold is ignored as too weak.
  • English beat the transliteration, or the reverse. English is tried only after the lexicon and variants, so an English word that is also a variant spelling never reaches the translation. A line with no English (headings, 332 verse lines) cannot be found by English search.
  • Ranking, not matching. Refrains are printed several times with only their numbering changed; the round-trip harness counts most of its rank-2 results there. That is expected.
  • Input limits. A query over 300 characters is a 400; the variant tier ignores queries of more than ten words; limit is capped at 200.

/api/word is a separate lookup of one Gurmukhi word in word_freq; a Roman word always returns zero there.

4. Fix it in the right place

The causeThe fixWhere
a phrase people type maps to the wrong or no linea SEEKER_LEXICON entry (check first that it is not already a transliteration word)platform, webapp/sggs/search.py
a word’s spelling is missinga reviewed typo or loanword spelling, or a rulesggs-data, then a pin bump (variants and the lexicon)
the fold is too eager or too stricta fold change, which must stay byte-identical in its three homesplatform and sggs-data together (the Roman fold)
a tier order or ranking weighta waterfall change, the riskiest kindplatform, with the harness numbers in the PR

Never fix a search problem by changing the text: the Gurmukhi and its transliteration are verbatim and proven (the integrity chain).

5. Prove it

  1. Run make harnesses before and after, and compare with where the numbers stand. For a change that could affect odd input, run the chaos harness too (it needs serve.py on port 7777).
  2. Add the query to the search cases in tools/gen_golden_vectors.py (search_vectors(), a (query, mode) pair), run make contract, and review the diff of contract/golden_search.ndjson: every changed record is a behaviour change you are signing for.
  3. Open the pull request with the before-and-after harness summaries and the contract diff. After release, the app re-vendors the contract and its Swift port must match.