Autocomplete
Ranked Suggestion lists for picker UIs. Six-bucket priority with frequency boost; replaceable with embedding or server-side rankers.
Autocomplete
Call isa.zyins.medications.autocomplete(), isa.zyins.conditions.autocomplete(),
or isa.zyins.concepts.autocomplete() to get ranked suggestions for any free-text query.
The default ranker uses a six-bucket priority scheme with frequency boost. This is a direct port of the bpp2.0 picker hook (useAutocomplete.js), now built into the SDK so every project uses the same algorithm instead of re-implementing it.
The AutocompleteAlgorithm interface
AutocompleteAlgorithm interfaceimport type { Concept, ConceptKind, Suggestion } from 'isa-sdk';
interface AutocompleteAlgorithm {
rank(
query: string,
candidates: readonly Concept[],
options: AutocompleteOptions,
): Promise<Suggestion[]>;
}
interface AutocompleteOptions {
readonly limit: number;
readonly kinds: readonly ConceptKind[];
readonly frequencies: ReadonlyMap<string, number>;
}The default implementation resolves synchronously (no I/O). The Promise wrapper lets future implementations add server-side reranking or embedding lookup without breaking the API.
Quick start
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
await isa.zyins.datasets.get();
const top5 = await isa.zyins.medications.autocomplete('lisi', { limit: 5 });
for (const s of top5) {
console.log(s.rank, s.name, s.score);
}
// 0 LISINOPRIL 4121
// 1 LISDEXAMFETAMINE 2241
// 2 LISTERIA VACCINE 5Bucket priority (highest → lowest)
The ranker sorts candidates into buckets and walks them in order. All candidates in bucket 1 rank higher than bucket 2, regardless of frequency. Within each bucket, the frequency boost applies.
| # | Bucket | Match | Sub-sort |
|---|---|---|---|
| 1 | startsWith | Candidate name starts with the literal input. | option.wordCount asc. |
| 2 | sameWords | Same word set AND same word count. | Frequency boost. |
| 3 | wordCountNoTolerance[d] | Every input word is in the candidate; d extra words. | d asc, then frequency. |
| 4 | independentWordIntersection | Every input word appears in candidate (any order). | Frequency boost. |
| 5 | sameNumWithTolerance | Same word count, different word sets. | Frequency boost. |
| 6 | wordCountWithTolerance[d] | d words differ or extra. | d asc, then frequency. |
Candidates are bucketed once for primary categorization (buckets 1–3, 5–6). The independentWordIntersection bucket (4) is an additional pass — a candidate in bucket 1 may also match bucket 4. Deduplication by ID keeps the first occurrence only.
Frequency boost
Within each bucket, the score is:
scaleFactor = max(1, totalGroups - groupIndex) // 6, 5, 4, 3, 2, 1
score = (frequency + 1) * scaleFactor
Bucket order always wins over frequency: a rare word in bucket 1 ranks higher than a common word in bucket 2.
If no candidate has a frequency entry, the boost is skipped and candidates sort by the bucket's intrinsic rule (alphabetical for ties).
The default frequency map comes from buildFrequencyMap(bundle), which sums prescription_count across each concept's treated_with[] / used_for[] relationships. The SDK builds and caches this map. You can override it if needed:
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
const top5 = await isa.zyins.medications.autocomplete('lisi', {
limit: 5,
frequencies: new Map([['LISINOPRIL', 99_999]]),
});The Suggestion shape
Suggestion shapeimport type { Concept } from 'isa-sdk';
interface Suggestion extends Concept {
readonly score: number;
readonly rank: number;
readonly matchedSpan: readonly [number, number];
}Suggestion extends Concept, so you can call .medications(sort) and .conditions(sort) directly without a separate match() call:
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
for (const s of await isa.zyins.conditions.autocomplete('diabetes', { limit: 3 })) {
console.log(s.rank, s.name);
for (const med of s.medications(isa.zyins.reference.Sort.MostCommonFirst)) {
console.log(' ', med.name);
}
}matchedSpan is the [start, end] offset of the query inside the
suggestion's name — feed it to your picker's highlighter for inline
match emphasis. [0, 0] when no literal substring matched.
Options
| Option | Default | Meaning |
|---|---|---|
limit | 25 (max 250) | Maximum suggestions to return. |
kinds | [] (all kinds) | Restrict to 'condition', 'medication', 'nicotine'. |
frequencies | bundle-derived buildFrequencyMap() map | Per-id frequency override. |
A kinds filter applied to concepts.autocomplete() is the only way to
narrow concepts cross-kind results. For medications.autocomplete() and
conditions.autocomplete() the kind is already pinned.
Replacing the ranker
Plug an embedding-based ranker, a server-side reranker, or a different bucketization at constructor time:
import {
Isa,
type AutocompleteAlgorithm,
type AutocompleteOptions,
type Concept,
type Suggestion,
} from 'isa-sdk';
import { buildSuggestion } from 'isa-sdk/zyins';
// Your embedding service — whatever returns candidates in relevance order.
interface EmbeddingService {
rank(query: string, candidates: readonly Concept[]): Promise<readonly Concept[]>;
}
class EmbeddingRanker implements AutocompleteAlgorithm {
constructor(private readonly embeddingService: EmbeddingService) {}
async rank(
query: string,
candidates: readonly Concept[],
options: AutocompleteOptions,
): Promise<Suggestion[]> {
const ranked = await this.embeddingService.rank(query, candidates);
return ranked.slice(0, options.limit).map((c, i) => buildSuggestion(c, {
score: ranked.length - i,
rank: i,
matchedSpan: [0, 0],
}));
}
}
// Register the ranker at construction; every autocomplete() call routes
// through it instead of the default six-bucket scheme.
declare const myEmbeddingService: EmbeddingService;
const isa = await Isa.withBearer(undefined, undefined, {
autocompleteAlgorithm: new EmbeddingRanker(myEmbeddingService),
});
const top5 = await isa.zyins.medications.autocomplete('blood thinner', { limit: 5 });The async signature is required even for synchronous implementations so the contract stays stable across SDK releases.
Why Promise<Suggestion[]> even when sync
Promise<Suggestion[]> even when syncThe default resolves synchronously (no I/O). The Promise wrapper lets future implementations add server-side reranking, embedding lookup, or other async work without breaking the API. Consumers can always await unconditionally.
Code samples
TypeScript
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
await isa.zyins.datasets.get();
const top5 = await isa.zyins.medications.autocomplete('lisi', { limit: 5 });
for (const s of top5) console.log(s.rank, s.name, s.score);Python
await isa.zyins.datasets.get()
top5 = await isa.zyins.medications.autocomplete('lisi', limit=5)
for s in top5:
print(s.rank, s.name, s.score)Go
ctx := context.Background()
isa.Zyins.Datasets.Get(ctx)
top5, _ := isa.Zyins.Medications.Autocomplete(ctx, "lisi", reference.AutocompleteOptions{Limit: 5})
for _, s := range top5 {
fmt.Println(s.Rank, s.Name, s.Score)
}PHP
$isa->zyins->datasets->get();
$top5 = $isa->zyins->medications->autocomplete('lisi', ['limit' => 5]);
foreach ($top5 as $s) {
echo $s->rank, ' ', $s->name, ' ', $s->score, PHP_EOL;
}curl
Autocomplete is SDK-side; canonical data flows through
GET /v3/datasets.
See also
- Match — single-concept text resolution.
- Autocorrect — pre-match typo correction.
- Reference catalog — the dataset bundle.
- Migrating from v2 to v3 — replacing
useAutocomplete.js.
Updated about 10 hours ago