Match

Text-to-Concept resolution. Default normalize + exact lookup; replaceable with fuzzy, locale-aware, or server-side matchers.

Match

isa.zyins.medications.match(), isa.zyins.conditions.match(), and
isa.zyins.concepts.match() resolve free text to a single canonical
Concept. The default MatchAlgorithm is _makeKey + exact lookup. It
normalizes the text the same way the server does, so the SDK and server always
agree on a match. Unknown text never throws — it flows through as an
UnknownConcept with inputText preserved.

For a deeper tour of the three accessors and the Concept shape, see
Reference matching. This page documents the
MatchAlgorithm adapter itself.

The MatchAlgorithm interface

import type { Concept, UnknownConcept } from 'isa-sdk';

interface MatchAlgorithm {
  match(query: string, candidates: readonly Concept[]): Concept | UnknownConcept;
}

Implementations MUST be pure and synchronous. Never throw on unknown input; return an UnknownConcept instead.

_makeKey — the default normalization

Both query and candidate id flow through _makeKey, which:

  1. Uppercases the input.
  2. Strips every character that is not ASCII alphanumeric.

So "high blood pressure", "High Blood Pressure!", and
"HIGHBLOODPRESSURE" all normalize to "HIGHBLOODPRESSURE". The
server-side MakeKey does the same thing — that's the parity contract.

Concept | UnknownConcept

A successful match returns the canonical Concept:

import type {
  ConditionConcept,
  MedicationConcept,
  ReferenceSort as Sort,
} from 'isa-sdk';

interface Concept {
  readonly id: string | null;
  readonly name: string;
  readonly kind: 'condition' | 'medication' | 'nicotine' | 'unknown';
  readonly isKnown: boolean;
  readonly inputText: string;

  conditions(sort?: Sort): readonly ConditionConcept[];
  medications(sort?: Sort): readonly MedicationConcept[];
  equals(other: Concept): boolean;
}

An UnknownConcept has the same shape with id: null, kind: 'unknown',
isKnown: false, and empty accessors:

import { Isa } from 'isa-sdk';

const isa = await Isa.withBearer();
const unknown = isa.zyins.conditions.match('Postural orthostatic tachycardia syndrome');

unknown.id;        // null
unknown.kind;      // 'unknown'
unknown.isKnown;   // false
unknown.inputText; // 'Postural orthostatic tachycardia syndrome'
unknown.medications(); // []

You can still pass this straight into prequalify. The engine canonicalizes
server-side when it can, and treats inputText as raw user input when it
cannot.

Symmetric traversal

Every Concept exposes both medications(sort?) and conditions(sort?):

CallReturns
condition.medications(Sort.MostCommonFirst)Drugs that treat the condition, prescription-count desc.
medication.conditions(Sort.MostCommonFirst)Conditions the drug treats, prescription-count desc.
condition.medications(Sort.Alphabetical)Same set, alphabetical asc.

The accessors read the inline treated_with[] / used_for[] arrays from
the dataset row. Empty until the SDK has fetched the bundle once — call
await isa.zyins.datasets.get() first.

Typo-tolerant matching (built-in)

The default matcher does exact, normalized, word-order-invariant lookup — fast
and predictable, but a misspelling misses. The SDK also ships an opt-in
FuzzyMatchAlgorithm that recovers typos for you, so you don't have to build
one. Pass it at construction time:

import { Isa, FuzzyMatchAlgorithm } from 'isa-sdk';

const isa = await Isa.withBearer(undefined, undefined, {
  matchAlgorithm: new FuzzyMatchAlgorithm(),
});
await isa.zyins.datasets.get();

// A misspelling that the default (exact) matcher would miss now resolves:
const cond = isa.zyins.conditions.match('corhns');     // recovers Crohn's
const med = isa.zyins.medications.match('sertaline');  // recovers sertraline

It matches in tiers — exact → prefix → Optimal String Alignment (OSA) → Double Metaphone →
synonym. It picks the first tier that has any hit (no blended scoring across
tiers), then ranks within that winning tier by lower edit distance first, and
only breaks a remaining tie by how common the term is:

  • Optimal String Alignment (OSA) catches single-edit typos including transpositions a
    plain edit-distance misses (corhn's → Crohn's).
  • Double Metaphone catches phonetic misses edit-distance can't
    (sertaline → sertraline, tylonol → tylenol).
  • synonym matches on a concept's aliases. It is inert today — the SDK
    Concept surface does not expose aliases — but the tier is wired for when
    the catalog surfaces them, so no signature change is needed later.

Like the default, it never throws: an unrecoverable string returns an
UnknownConcept with the original text preserved, so freeform input always
passes through to the engine. Pass frequencies (a concept id → count map —
keyed by Concept.id, e.g. TS ReadonlyMap<string, number> — from your own
usage data) to bias ranking toward the terms your users actually pick; keying it
by name instead has no effect. Omit it and ties break deterministically by name.
FuzzyMatchAlgorithm ships in all five SDKs (TypeScript, Python, Go, C#, PHP)
and is held to a shared cross-language vector fixture, so a given input resolves
identically in every language.

Replacing the matcher

For needs beyond the built-in fuzzy matcher — a different normalization
(locale-aware casefolding, transliteration), an embedding model, or a
server-side matching service — plug your own in at constructor time:

import { Isa, type MatchAlgorithm, type Concept, type UnknownConcept } from 'isa-sdk';

// Your similarity function and unknown-result builder — the two pieces a
// custom matcher supplies. `match` MUST be pure and synchronous, and MUST
// return an UnknownConcept (never throw) on a miss.
declare function trigramSimilarity(a: string, b: string): number;
declare function makeUnknown(query: string): UnknownConcept;

class TrigramMatcher implements MatchAlgorithm {
  match(query: string, candidates: readonly Concept[]): Concept | UnknownConcept {
    let best: { c: Concept; score: number } | null = null;
    for (const c of candidates) {
      const s = trigramSimilarity(query, c.name);
      if (!best || s > best.score) best = { c, score: s };
    }
    if (best && best.score >= 0.8) return best.c;
    return makeUnknown(query);
  }
}

const isa = await Isa.withBearer(undefined, undefined, { matchAlgorithm: new TrigramMatcher() });

const cond = isa.zyins.conditions.match('hyprtension');

Substitute when:

  • The product needs fuzzy matching (Levenshtein, trigram, embedding).
  • The catalog uses a different normalization (locale-aware casefolding, transliteration of Greek/Cyrillic).
  • You want to replace the lookup strategy while keeping the traversal accessors (medications() / conditions()) — they read the bundle's inline rows, not the matcher.

Direct access — isa.zyins.matcher

The matcher is exposed as isa.zyins.matcher for callers that want to
match against an arbitrary candidate pool (e.g. a subset filtered by
state):

import { Isa } from 'isa-sdk';

const isa = await Isa.withBearer();
await isa.zyins.datasets.get();

const conditions = isa.zyins.conditions.list();
const cardiac = conditions.filter((c) => c.name.includes('HEART'));

const hit = isa.zyins.matcher.match('chest pain', cardiac);

Code samples

TypeScript

import { Isa } from 'isa-sdk';

const isa = await Isa.withBearer();
await isa.zyins.datasets.get();

const hbp = isa.zyins.conditions.match('high blood pressure');
hbp.id;        // 'cond_01KSR2WVAGC05ZGR6FA4QYEA8X'
hbp.isKnown;   // true
hbp.kind;      // 'condition'

Python

await isa.zyins.datasets.get()

hbp = isa.zyins.conditions.match('high blood pressure')
print(hbp.id, hbp.is_known, hbp.kind)

Go

ctx := context.Background()
isa.Zyins.Datasets.Get(ctx)

hbp := isa.Zyins.Conditions.Match("high blood pressure")
fmt.Println(hbp.ID, hbp.IsKnown, hbp.Kind)

PHP

$isa->zyins->datasets->get();
$hbp = $isa->zyins->conditions->match('high blood pressure');
echo $hbp->id, ' ', var_export($hbp->isKnown, true), ' ', $hbp->kind, PHP_EOL;

curl

match is an SDK-side operation; there is no dedicated REST endpoint. The
canonical data flows through GET /v3/datasets.

See also


Did this page help you?