{"version":3,"sources":["../src/fuzzy-match.ts"],"names":["jaro","a","b","matchWindow","aMatches","bMatches","matches","i","start","end","j","transpositions","k","jaroWinkler","opts","norm","s","x","y","prefix","fuzzyMatchList","query","candidates","threshold","topK","scored","candidate","score"],"mappings":"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","file":"fuzzy-match.cjs","sourcesContent":["/**\n * Zero-dependency fuzzy string matching — Jaro-Winkler similarity. For approximate\n * name matching against a list (sanctions / KYC / watchlist screening, dedup,\n * entity resolution) WITHOUT an LLM call: deterministic, auditable, and cheap.\n *\n * ```ts\n * import { fuzzyMatchList } from '@agentskit/core'\n * const hits = fuzzyMatchList('Vladimir Putin', sanctionsList, { threshold: 0.9 })\n * if (hits.length) block() // never auto-clear an approximate match\n * ```\n */\n\n/** Jaro similarity (0..1) of two strings. */\nfunction jaro(a: string, b: string): number {\n  if (a === b) return 1\n  if (a.length === 0 || b.length === 0) return 0\n  const matchWindow = Math.max(0, Math.floor(Math.max(a.length, b.length) / 2) - 1)\n  const aMatches = new Array<boolean>(a.length).fill(false)\n  const bMatches = new Array<boolean>(b.length).fill(false)\n  let matches = 0\n  for (let i = 0; i < a.length; i++) {\n    const start = Math.max(0, i - matchWindow)\n    const end = Math.min(i + matchWindow + 1, b.length)\n    for (let j = start; j < end; j++) {\n      if (bMatches[j] || a[i] !== b[j]) continue\n      aMatches[i] = true\n      bMatches[j] = true\n      matches++\n      break\n    }\n  }\n  if (matches === 0) return 0\n  // Count transpositions.\n  let transpositions = 0\n  let k = 0\n  for (let i = 0; i < a.length; i++) {\n    if (!aMatches[i]) continue\n    while (!bMatches[k]) k++\n    if (a[i] !== b[k]) transpositions++\n    k++\n  }\n  transpositions /= 2\n  return (matches / a.length + matches / b.length + (matches - transpositions) / matches) / 3\n}\n\n/**\n * Jaro-Winkler similarity (0..1) — Jaro with a bonus for a shared prefix (up to 4\n * chars). Case- and whitespace-insensitive by default. 1 = identical, 0 = nothing\n * in common.\n */\nexport function jaroWinkler(a: string, b: string, opts: { caseSensitive?: boolean } = {}): number {\n  const norm = (s: string) => (opts.caseSensitive ? s : s.toLowerCase()).trim().replace(/\\s+/g, ' ')\n  const x = norm(a)\n  const y = norm(b)\n  const j = jaro(x, y)\n  if (j === 0) return 0\n  let prefix = 0\n  for (let i = 0; i < Math.min(4, x.length, y.length); i++) {\n    if (x[i] === y[i]) prefix++\n    else break\n  }\n  return j + prefix * 0.1 * (1 - j)\n}\n\nexport interface FuzzyMatch {\n  candidate: string\n  score: number\n}\n\n/**\n * Score `query` against every candidate and return the matches at or above\n * `threshold` (default 0.85), highest score first, capped at `topK` (default 10).\n */\nexport function fuzzyMatchList(\n  query: string,\n  candidates: readonly string[],\n  opts: { threshold?: number; topK?: number; caseSensitive?: boolean } = {},\n): FuzzyMatch[] {\n  const threshold = opts.threshold ?? 0.85\n  const topK = opts.topK ?? 10\n  const scored: FuzzyMatch[] = []\n  for (const candidate of candidates) {\n    const score = jaroWinkler(query, candidate, { caseSensitive: opts.caseSensitive })\n    if (score >= threshold) scored.push({ candidate, score })\n  }\n  scored.sort((a, b) => b.score - a.score)\n  return scored.slice(0, topK)\n}\n"]}