goldenmatch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +140 -0
  2. package/dist/cli.cjs +6079 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.d.cts +1 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +6076 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/index.cjs +8449 -0
  9. package/dist/core/index.cjs.map +1 -0
  10. package/dist/core/index.d.cts +1972 -0
  11. package/dist/core/index.d.ts +1972 -0
  12. package/dist/core/index.js +8318 -0
  13. package/dist/core/index.js.map +1 -0
  14. package/dist/index.cjs +8449 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +2 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +8318 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/node/backends/score-worker.cjs +934 -0
  21. package/dist/node/backends/score-worker.cjs.map +1 -0
  22. package/dist/node/backends/score-worker.d.cts +14 -0
  23. package/dist/node/backends/score-worker.d.ts +14 -0
  24. package/dist/node/backends/score-worker.js +932 -0
  25. package/dist/node/backends/score-worker.js.map +1 -0
  26. package/dist/node/index.cjs +11430 -0
  27. package/dist/node/index.cjs.map +1 -0
  28. package/dist/node/index.d.cts +554 -0
  29. package/dist/node/index.d.ts +554 -0
  30. package/dist/node/index.js +11277 -0
  31. package/dist/node/index.js.map +1 -0
  32. package/dist/types-DhUdX5Rc.d.cts +304 -0
  33. package/dist/types-DhUdX5Rc.d.ts +304 -0
  34. package/examples/01-basic-dedupe.ts +60 -0
  35. package/examples/02-match-two-datasets.ts +48 -0
  36. package/examples/03-csv-file-pipeline.ts +62 -0
  37. package/examples/04-string-scoring.ts +63 -0
  38. package/examples/05-custom-config.ts +94 -0
  39. package/examples/06-probabilistic-fs.ts +72 -0
  40. package/examples/07-pprl-privacy.ts +76 -0
  41. package/examples/08-streaming.ts +79 -0
  42. package/examples/09-llm-scorer.ts +79 -0
  43. package/examples/10-explain.ts +60 -0
  44. package/examples/11-evaluate.ts +61 -0
  45. package/examples/README.md +53 -0
  46. package/package.json +66 -0
  47. package/src/cli.ts +372 -0
  48. package/src/core/ann-blocker.ts +593 -0
  49. package/src/core/api.ts +220 -0
  50. package/src/core/autoconfig.ts +363 -0
  51. package/src/core/autofix.ts +102 -0
  52. package/src/core/blocker.ts +655 -0
  53. package/src/core/cluster.ts +699 -0
  54. package/src/core/compare-clusters.ts +176 -0
  55. package/src/core/config/loader.ts +869 -0
  56. package/src/core/cross-encoder.ts +614 -0
  57. package/src/core/data.ts +430 -0
  58. package/src/core/domain.ts +277 -0
  59. package/src/core/embedder.ts +562 -0
  60. package/src/core/evaluate.ts +156 -0
  61. package/src/core/explain.ts +352 -0
  62. package/src/core/golden.ts +524 -0
  63. package/src/core/graph-er.ts +371 -0
  64. package/src/core/index.ts +314 -0
  65. package/src/core/ingest.ts +112 -0
  66. package/src/core/learned-blocking.ts +305 -0
  67. package/src/core/lineage.ts +221 -0
  68. package/src/core/llm/budget.ts +258 -0
  69. package/src/core/llm/cluster.ts +542 -0
  70. package/src/core/llm/scorer.ts +396 -0
  71. package/src/core/match-one.ts +95 -0
  72. package/src/core/matchkey.ts +97 -0
  73. package/src/core/memory/corrections.ts +179 -0
  74. package/src/core/memory/learner.ts +218 -0
  75. package/src/core/memory/store.ts +114 -0
  76. package/src/core/pipeline.ts +366 -0
  77. package/src/core/pprl/protocol.ts +216 -0
  78. package/src/core/probabilistic.ts +511 -0
  79. package/src/core/profiler.ts +212 -0
  80. package/src/core/quality.ts +197 -0
  81. package/src/core/review-queue.ts +177 -0
  82. package/src/core/scorer.ts +855 -0
  83. package/src/core/sensitivity.ts +196 -0
  84. package/src/core/standardize.ts +279 -0
  85. package/src/core/streaming.ts +128 -0
  86. package/src/core/transforms.ts +599 -0
  87. package/src/core/types.ts +570 -0
  88. package/src/core/validate.ts +243 -0
  89. package/src/index.ts +8 -0
  90. package/src/node/a2a/server.ts +470 -0
  91. package/src/node/api/server.ts +412 -0
  92. package/src/node/backends/duckdb.ts +130 -0
  93. package/src/node/backends/score-worker.ts +41 -0
  94. package/src/node/backends/workers.ts +212 -0
  95. package/src/node/config-file.ts +66 -0
  96. package/src/node/connectors/base.ts +57 -0
  97. package/src/node/connectors/bigquery.ts +61 -0
  98. package/src/node/connectors/databricks.ts +69 -0
  99. package/src/node/connectors/file.ts +350 -0
  100. package/src/node/connectors/hubspot.ts +62 -0
  101. package/src/node/connectors/index.ts +43 -0
  102. package/src/node/connectors/salesforce.ts +93 -0
  103. package/src/node/connectors/snowflake.ts +73 -0
  104. package/src/node/db/postgres.ts +173 -0
  105. package/src/node/db/sync.ts +103 -0
  106. package/src/node/dedupe-file.ts +156 -0
  107. package/src/node/index.ts +89 -0
  108. package/src/node/mcp/server.ts +940 -0
  109. package/src/node/tui/app.ts +756 -0
  110. package/src/node/tui/index.ts +6 -0
  111. package/src/node/tui/widgets.ts +128 -0
  112. package/tests/parity/scorer-ground-truth.test.ts +118 -0
  113. package/tests/smoke.test.ts +46 -0
  114. package/tests/unit/a2a-server.test.ts +175 -0
  115. package/tests/unit/ann-blocker.test.ts +117 -0
  116. package/tests/unit/api-server.test.ts +239 -0
  117. package/tests/unit/api.test.ts +77 -0
  118. package/tests/unit/autoconfig.test.ts +103 -0
  119. package/tests/unit/autofix.test.ts +71 -0
  120. package/tests/unit/blocker.test.ts +164 -0
  121. package/tests/unit/buildBlocksAsync.test.ts +63 -0
  122. package/tests/unit/cluster.test.ts +213 -0
  123. package/tests/unit/compare-clusters.test.ts +42 -0
  124. package/tests/unit/config-loader.test.ts +301 -0
  125. package/tests/unit/connectors-base.test.ts +48 -0
  126. package/tests/unit/cross-encoder-model.test.ts +198 -0
  127. package/tests/unit/cross-encoder.test.ts +173 -0
  128. package/tests/unit/db-connectors.test.ts +37 -0
  129. package/tests/unit/domain.test.ts +80 -0
  130. package/tests/unit/embedder.test.ts +151 -0
  131. package/tests/unit/evaluate.test.ts +85 -0
  132. package/tests/unit/explain.test.ts +73 -0
  133. package/tests/unit/golden.test.ts +97 -0
  134. package/tests/unit/graph-er.test.ts +173 -0
  135. package/tests/unit/hnsw-ann.test.ts +283 -0
  136. package/tests/unit/hubspot-connector.test.ts +118 -0
  137. package/tests/unit/ingest.test.ts +97 -0
  138. package/tests/unit/learned-blocking.test.ts +134 -0
  139. package/tests/unit/lineage.test.ts +135 -0
  140. package/tests/unit/match-one.test.ts +129 -0
  141. package/tests/unit/matchkey.test.ts +97 -0
  142. package/tests/unit/mcp-server.test.ts +183 -0
  143. package/tests/unit/memory.test.ts +119 -0
  144. package/tests/unit/pipeline.test.ts +118 -0
  145. package/tests/unit/pprl-protocol.test.ts +381 -0
  146. package/tests/unit/probabilistic.test.ts +494 -0
  147. package/tests/unit/profiler.test.ts +68 -0
  148. package/tests/unit/review-queue.test.ts +68 -0
  149. package/tests/unit/salesforce-connector.test.ts +148 -0
  150. package/tests/unit/scorer.test.ts +301 -0
  151. package/tests/unit/sensitivity.test.ts +154 -0
  152. package/tests/unit/standardize.test.ts +84 -0
  153. package/tests/unit/streaming.test.ts +82 -0
  154. package/tests/unit/transforms.test.ts +208 -0
  155. package/tests/unit/tui-widgets.test.ts +42 -0
  156. package/tests/unit/tui.test.ts +24 -0
  157. package/tests/unit/validate.test.ts +145 -0
  158. package/tests/unit/workers-parallel.test.ts +99 -0
  159. package/tests/unit/workers.test.ts +74 -0
  160. package/tsconfig.json +25 -0
  161. package/tsup.config.ts +37 -0
  162. package/vitest.config.ts +11 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/types.ts","../../../src/core/cluster.ts","../../../src/core/transforms.ts","../../../src/core/scorer.ts","../../../src/node/backends/score-worker.ts"],"names":["results"],"mappings":"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","file":"score-worker.js","sourcesContent":["/**\n * types.ts — GoldenMatch config interfaces and result types.\n * Edge-safe: no Node.js imports, no `process`.\n */\n\n// ---------------------------------------------------------------------------\n// Primitive types\n// ---------------------------------------------------------------------------\n\nexport type ColumnValue = string | number | boolean | null;\nexport type Row = Readonly<Record<string, unknown>>;\n\n/** A canonical pair key in the form \"minId:maxId\". Only produced by pairKey(). */\nexport type PairKey = string & { readonly __brand: \"PairKey\" };\n\n// ---------------------------------------------------------------------------\n// Matchkey field config\n// ---------------------------------------------------------------------------\n\nexport interface MatchkeyField {\n readonly field: string;\n readonly transforms: readonly string[];\n readonly scorer: string;\n readonly weight: number;\n readonly model?: string;\n readonly columns?: readonly string[];\n readonly columnWeights?: Readonly<Record<string, number>>;\n readonly levels?: number;\n readonly partialThreshold?: number;\n}\n\nexport interface ExactMatchkey {\n readonly name: string;\n readonly type: \"exact\";\n readonly fields: readonly MatchkeyField[];\n}\n\nexport interface WeightedMatchkey {\n readonly name: string;\n readonly type: \"weighted\";\n readonly fields: readonly MatchkeyField[];\n readonly threshold: number;\n readonly autoThreshold?: boolean;\n readonly rerank?: boolean;\n readonly rerankModel?: string;\n readonly rerankBand?: number;\n}\n\nexport interface ProbabilisticMatchkey {\n readonly name: string;\n readonly type: \"probabilistic\";\n readonly fields: readonly MatchkeyField[];\n readonly threshold?: number;\n readonly emIterations?: number;\n readonly convergenceThreshold?: number;\n readonly linkThreshold?: number;\n readonly reviewThreshold?: number;\n}\n\nexport type MatchkeyConfig =\n | ExactMatchkey\n | WeightedMatchkey\n | ProbabilisticMatchkey;\n\n// ---------------------------------------------------------------------------\n// Blocking config\n// ---------------------------------------------------------------------------\n\nexport interface BlockingKeyConfig {\n readonly fields: readonly string[];\n readonly transforms: readonly string[];\n}\n\nexport interface SortKeyField {\n readonly column: string;\n readonly transforms: readonly string[];\n}\n\nexport interface CanopyConfig {\n readonly fields: readonly string[];\n readonly looseThreshold: number;\n readonly tightThreshold: number;\n readonly maxCanopySize: number;\n}\n\nexport interface BlockingConfig {\n readonly strategy:\n | \"static\"\n | \"adaptive\"\n | \"sorted_neighborhood\"\n | \"multi_pass\"\n | \"ann\"\n | \"canopy\"\n | \"ann_pairs\"\n | \"learned\";\n readonly keys: readonly BlockingKeyConfig[];\n readonly maxBlockSize: number;\n readonly skipOversized: boolean;\n readonly autoSuggest?: boolean;\n readonly autoSelect?: boolean;\n readonly subBlockKeys?: readonly BlockingKeyConfig[];\n readonly windowSize?: number;\n readonly sortKey?: readonly SortKeyField[];\n readonly passes?: readonly BlockingKeyConfig[];\n readonly unionMode?: boolean;\n readonly maxTotalComparisons?: number;\n readonly annColumn?: string;\n readonly annModel?: string;\n readonly annTopK?: number;\n readonly canopy?: CanopyConfig;\n readonly learnedSampleSize?: number;\n readonly learnedMinRecall?: number;\n readonly learnedMinReduction?: number;\n readonly learnedPredicateDepth?: number;\n readonly learnedCachePath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Golden rules config\n// ---------------------------------------------------------------------------\n\nexport interface GoldenFieldRule {\n readonly strategy:\n | \"most_complete\"\n | \"majority_vote\"\n | \"source_priority\"\n | \"most_recent\"\n | \"first_non_null\";\n readonly dateColumn?: string;\n readonly sourcePriority?: readonly string[];\n}\n\nexport interface GoldenRulesConfig {\n readonly defaultStrategy: string;\n readonly fieldRules: Readonly<Record<string, GoldenFieldRule>>;\n readonly maxClusterSize: number;\n readonly autoSplit: boolean;\n readonly qualityWeighting: boolean;\n readonly weakClusterThreshold: number;\n}\n\n// ---------------------------------------------------------------------------\n// Standardization, validation, quality, transform\n// ---------------------------------------------------------------------------\n\nexport interface StandardizationConfig {\n readonly rules: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface ValidationRuleConfig {\n readonly column: string;\n readonly ruleType:\n | \"regex\"\n | \"min_length\"\n | \"max_length\"\n | \"not_null\"\n | \"in_set\"\n | \"format\";\n readonly params: Readonly<Record<string, unknown>>;\n readonly action: \"null\" | \"quarantine\" | \"flag\";\n}\n\nexport interface ValidationConfig {\n readonly rules: readonly ValidationRuleConfig[];\n readonly autoFix: boolean;\n}\n\nexport interface QualityConfig {\n readonly enabled: boolean;\n readonly mode: \"silent\" | \"announced\" | \"disabled\";\n readonly fixMode: \"safe\" | \"moderate\" | \"none\";\n readonly domain?: string;\n}\n\nexport interface TransformConfig {\n readonly enabled: boolean;\n readonly mode: \"silent\" | \"announced\" | \"disabled\";\n}\n\n// ---------------------------------------------------------------------------\n// LLM scorer & budget\n// ---------------------------------------------------------------------------\n\nexport interface BudgetConfig {\n readonly maxCostUsd?: number;\n readonly maxCalls?: number;\n readonly escalationModel?: string;\n readonly escalationBand?: readonly number[];\n readonly escalationBudgetPct?: number;\n readonly warnAtPct?: number;\n}\n\nexport interface LLMScorerConfig {\n readonly enabled: boolean;\n readonly provider?: string;\n readonly model?: string;\n readonly autoThreshold: number;\n readonly candidateLo: number;\n readonly candidateHi: number;\n readonly batchSize: number;\n readonly maxWorkers: number;\n readonly budget?: BudgetConfig;\n readonly mode: \"pairwise\" | \"cluster\";\n readonly clusterMaxSize?: number;\n readonly clusterMinSize?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Domain config\n// ---------------------------------------------------------------------------\n\nexport interface DomainConfig {\n readonly enabled: boolean;\n readonly mode?: string;\n readonly confidenceThreshold: number;\n readonly llmValidation: boolean;\n readonly budget?: BudgetConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Memory & learning\n// ---------------------------------------------------------------------------\n\nexport interface LearningConfig {\n readonly thresholdMinCorrections: number;\n readonly weightsMinCorrections: number;\n}\n\nexport interface MemoryConfig {\n readonly enabled: boolean;\n readonly backend: \"sqlite\" | \"postgres\";\n readonly path?: string;\n readonly trust: number;\n readonly learning: LearningConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Input & output config\n// ---------------------------------------------------------------------------\n\nexport interface InputFileConfig {\n readonly path: string;\n readonly idColumn?: string;\n readonly sourceLabel?: string;\n readonly sourceName?: string;\n readonly columnMap?: Readonly<Record<string, string>>;\n readonly delimiter?: string;\n readonly encoding?: string;\n readonly sheet?: string;\n readonly parseMode?: string;\n readonly headerRow?: number;\n readonly hasHeader?: boolean;\n readonly skipRows?: readonly number[];\n}\n\nexport interface InputConfig {\n readonly files: readonly InputFileConfig[];\n readonly fileA?: InputFileConfig;\n readonly fileB?: InputFileConfig;\n}\n\nexport interface OutputConfig {\n readonly path?: string;\n readonly format?: string;\n readonly directory?: string;\n readonly runName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Top-level config\n// ---------------------------------------------------------------------------\n\nexport interface GoldenMatchConfig {\n readonly matchkeys?: readonly MatchkeyConfig[];\n readonly matchSettings?: readonly MatchkeyConfig[];\n readonly blocking?: BlockingConfig;\n readonly threshold?: number;\n readonly goldenRules?: GoldenRulesConfig;\n readonly standardization?: StandardizationConfig;\n readonly validation?: ValidationConfig;\n readonly quality?: QualityConfig;\n readonly transform?: TransformConfig;\n readonly llmScorer?: LLMScorerConfig;\n readonly domain?: DomainConfig;\n readonly memory?: MemoryConfig;\n readonly input?: InputConfig;\n readonly output?: OutputConfig;\n readonly backend?: string;\n readonly llmAuto?: boolean;\n readonly llmBoost?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Result types\n// ---------------------------------------------------------------------------\n\nexport interface ScoredPair {\n readonly idA: number;\n readonly idB: number;\n readonly score: number;\n}\n\nexport interface ClusterInfo {\n readonly members: readonly number[];\n readonly size: number;\n readonly oversized: boolean;\n readonly pairScores: ReadonlyMap<PairKey, number>;\n readonly confidence: number;\n readonly bottleneckPair: readonly [number, number] | null;\n readonly clusterQuality: \"strong\" | \"weak\" | \"split\";\n}\n\nexport interface DedupeStats {\n readonly totalRecords: number;\n readonly totalClusters: number;\n readonly matchRate: number;\n readonly matchedRecords: number;\n readonly uniqueRecords: number;\n}\n\nexport interface DedupeResult {\n readonly goldenRecords: readonly Row[];\n readonly clusters: ReadonlyMap<number, ClusterInfo>;\n readonly dupes: readonly Row[];\n readonly unique: readonly Row[];\n readonly stats: DedupeStats;\n readonly scoredPairs: readonly ScoredPair[];\n readonly config: GoldenMatchConfig;\n}\n\nexport interface MatchResult {\n readonly matched: readonly Row[];\n readonly unmatched: readonly Row[];\n readonly stats: Readonly<Record<string, unknown>>;\n}\n\nexport interface FieldProvenance {\n readonly value: unknown;\n readonly sourceRowId: number;\n readonly strategy: string;\n readonly confidence: number;\n readonly candidates: readonly Readonly<Record<string, unknown>>[];\n}\n\nexport interface ClusterProvenance {\n readonly clusterId: number;\n readonly clusterQuality: string;\n readonly clusterConfidence: number;\n readonly fields: Readonly<Record<string, FieldProvenance>>;\n}\n\nexport interface BlockResult {\n readonly blockKey: string;\n readonly rows: readonly Row[];\n readonly strategy: string;\n readonly depth: number;\n readonly parentKey?: string;\n readonly preScoredPairs?: readonly ScoredPair[];\n}\n\n// ---------------------------------------------------------------------------\n// Valid enum sets\n// ---------------------------------------------------------------------------\n\nexport const VALID_SCORERS = new Set([\n \"exact\",\n \"jaro_winkler\",\n \"levenshtein\",\n \"token_sort\",\n \"soundex_match\",\n \"embedding\",\n \"record_embedding\",\n \"ensemble\",\n \"dice\",\n \"jaccard\",\n] as const);\n\nexport const VALID_TRANSFORMS = new Set([\n \"lowercase\",\n \"uppercase\",\n \"strip\",\n \"strip_all\",\n \"soundex\",\n \"metaphone\",\n \"digits_only\",\n \"alpha_only\",\n \"normalize_whitespace\",\n \"token_sort\",\n \"first_token\",\n \"last_token\",\n] as const);\n\nexport const VALID_STRATEGIES = new Set([\n \"most_recent\",\n \"source_priority\",\n \"most_complete\",\n \"majority_vote\",\n \"first_non_null\",\n] as const);\n\nexport const VALID_STANDARDIZERS = new Set([\n \"email\",\n \"name_proper\",\n \"name_upper\",\n \"name_lower\",\n \"phone\",\n \"zip5\",\n \"address\",\n \"state\",\n \"strip\",\n \"trim_whitespace\",\n] as const);\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ScoredPair guaranteeing idA <= idB (canonical order).\n * Always use this instead of constructing `{ idA, idB, score }` directly.\n */\nexport function makeScoredPair(\n a: number,\n b: number,\n score: number,\n): ScoredPair {\n const lo = a < b ? a : b;\n const hi = a < b ? b : a;\n return { idA: lo, idB: hi, score };\n}\n\n/** Create a MatchkeyField with sensible defaults. */\nexport function makeMatchkeyField(\n partial: Partial<MatchkeyField> & Pick<MatchkeyField, \"field\">,\n): MatchkeyField {\n return {\n transforms: [],\n scorer: \"jaro_winkler\",\n weight: 1.0,\n ...partial,\n };\n}\n\n/**\n * Shape accepted by `makeMatchkeyConfig`. All variant-specific fields are\n * optional; the factory picks the right variant based on `type`.\n */\nexport interface MakeMatchkeyConfigInput {\n readonly name: string;\n readonly type?: \"exact\" | \"weighted\" | \"probabilistic\";\n readonly fields?: readonly MatchkeyField[];\n readonly threshold?: number;\n readonly autoThreshold?: boolean;\n readonly rerank?: boolean;\n readonly rerankModel?: string;\n readonly rerankBand?: number;\n readonly emIterations?: number;\n readonly convergenceThreshold?: number;\n readonly linkThreshold?: number;\n readonly reviewThreshold?: number;\n}\n\n/** Create a MatchkeyConfig with sensible defaults. Produces the correct variant. */\nexport function makeMatchkeyConfig(\n partial: MakeMatchkeyConfigInput,\n): MatchkeyConfig {\n const type = partial.type ?? \"weighted\";\n const fields = partial.fields ?? [];\n if (type === \"exact\") {\n return { name: partial.name, type: \"exact\", fields };\n }\n if (type === \"probabilistic\") {\n const out: ProbabilisticMatchkey = {\n name: partial.name,\n type: \"probabilistic\",\n fields,\n ...(partial.threshold !== undefined\n ? { threshold: partial.threshold }\n : {}),\n ...(partial.emIterations !== undefined\n ? { emIterations: partial.emIterations }\n : {}),\n ...(partial.convergenceThreshold !== undefined\n ? { convergenceThreshold: partial.convergenceThreshold }\n : {}),\n ...(partial.linkThreshold !== undefined\n ? { linkThreshold: partial.linkThreshold }\n : {}),\n ...(partial.reviewThreshold !== undefined\n ? { reviewThreshold: partial.reviewThreshold }\n : {}),\n };\n return out;\n }\n // weighted (default)\n const out: WeightedMatchkey = {\n name: partial.name,\n type: \"weighted\",\n fields,\n threshold: partial.threshold ?? 0.85,\n ...(partial.autoThreshold !== undefined\n ? { autoThreshold: partial.autoThreshold }\n : {}),\n ...(partial.rerank !== undefined ? { rerank: partial.rerank } : {}),\n ...(partial.rerankModel !== undefined\n ? { rerankModel: partial.rerankModel }\n : {}),\n ...(partial.rerankBand !== undefined\n ? { rerankBand: partial.rerankBand }\n : {}),\n };\n return out;\n}\n\n/** Create a BlockingConfig with sensible defaults. */\nexport function makeBlockingConfig(\n partial?: Partial<BlockingConfig>,\n): BlockingConfig {\n return {\n strategy: \"static\",\n keys: [],\n maxBlockSize: 5000,\n skipOversized: false,\n ...partial,\n };\n}\n\n/** Create a GoldenRulesConfig with sensible defaults. */\nexport function makeGoldenRulesConfig(\n partial?: Partial<GoldenRulesConfig>,\n): GoldenRulesConfig {\n return {\n defaultStrategy: \"most_complete\",\n fieldRules: {},\n maxClusterSize: 10,\n autoSplit: true,\n qualityWeighting: true,\n weakClusterThreshold: 0.3,\n ...partial,\n };\n}\n\n/** Create a full GoldenMatchConfig with sensible defaults. */\nexport function makeConfig(\n partial?: Partial<GoldenMatchConfig>,\n): GoldenMatchConfig {\n return {\n threshold: 0.85,\n blocking: makeBlockingConfig(partial?.blocking),\n goldenRules: makeGoldenRulesConfig(partial?.goldenRules),\n ...partial,\n // Re-apply blocking/goldenRules after spread so partial overrides win\n ...(partial?.blocking !== undefined\n ? { blocking: makeBlockingConfig(partial.blocking) }\n : {}),\n ...(partial?.goldenRules !== undefined\n ? { goldenRules: makeGoldenRulesConfig(partial.goldenRules) }\n : {}),\n };\n}\n\n/**\n * Return matchkeys from config, checking both `matchkeys` and `matchSettings`.\n * Mirrors Python's `GoldenMatchConfig.get_matchkeys()`.\n */\nexport function getMatchkeys(\n config: GoldenMatchConfig,\n): readonly MatchkeyConfig[] {\n return config.matchkeys ?? config.matchSettings ?? [];\n}\n","/**\n * cluster.ts — Union-Find clustering with MST splitting.\n * Edge-safe: no Node.js imports, pure TypeScript only.\n */\n\nimport type { ClusterInfo, PairKey } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Canonicalize a pair key: always min:max. Sole producer of branded PairKey. */\nexport function pairKey(a: number, b: number): PairKey {\n const lo = a < b ? a : b;\n const hi = a < b ? b : a;\n return `${lo}:${hi}` as PairKey;\n}\n\n/** Parse a pair key back into [idA, idB]. */\nexport function parsePairKey(key: PairKey): readonly [number, number] {\n const idx = key.indexOf(\":\");\n return [Number(key.slice(0, idx)), Number(key.slice(idx + 1))];\n}\n\n// ---------------------------------------------------------------------------\n// UnionFind\n// ---------------------------------------------------------------------------\n\nexport class UnionFind {\n private parent = new Map<number, number>();\n private rank = new Map<number, number>();\n\n /** Add element as its own root. */\n add(x: number): void {\n if (!this.parent.has(x)) {\n this.parent.set(x, x);\n this.rank.set(x, 0);\n }\n }\n\n /** Batch add multiple elements. */\n addMany(ids: readonly number[]): void {\n for (const x of ids) {\n if (!this.parent.has(x)) {\n this.parent.set(x, x);\n this.rank.set(x, 0);\n }\n }\n }\n\n /** Find root with iterative path compression. */\n find(x: number): number {\n let root = x;\n while (this.parent.get(root) !== root) {\n root = this.parent.get(root)!;\n }\n // Path compression\n let current = x;\n while (this.parent.get(current) !== root) {\n const next = this.parent.get(current)!;\n this.parent.set(current, root);\n current = next;\n }\n return root;\n }\n\n /** Union by rank. */\n union(a: number, b: number): void {\n let ra = this.find(a);\n let rb = this.find(b);\n if (ra === rb) return;\n const rankA = this.rank.get(ra)!;\n const rankB = this.rank.get(rb)!;\n if (rankA < rankB) {\n [ra, rb] = [rb, ra];\n }\n this.parent.set(rb, ra);\n if (rankA === rankB) {\n this.rank.set(ra, rankA + 1);\n }\n }\n\n /** Return all clusters as arrays of sets. */\n getClusters(): Set<number>[] {\n const groups = new Map<number, Set<number>>();\n for (const x of this.parent.keys()) {\n const root = this.find(x);\n let group = groups.get(root);\n if (!group) {\n group = new Set<number>();\n groups.set(root, group);\n }\n group.add(x);\n }\n return Array.from(groups.values());\n }\n}\n\n// ---------------------------------------------------------------------------\n// MST (max-weight spanning tree via Kruskal)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a max-weight spanning tree using Kruskal's algorithm.\n * Returns edges as [idA, idB, score] sorted by descending weight.\n */\nexport function buildMst(\n members: readonly number[],\n pairScores: ReadonlyMap<PairKey, number>,\n): [number, number, number][] {\n // Collect and sort edges by score descending\n const edges: [number, number, number][] = [];\n for (const [key, score] of pairScores) {\n const [a, b] = parsePairKey(key);\n edges.push([a, b, score]);\n }\n edges.sort((x, y) => y[2] - x[2]);\n\n const uf = new UnionFind();\n uf.addMany(members);\n\n const mst: [number, number, number][] = [];\n const target = members.length - 1;\n for (const [a, b, s] of edges) {\n if (uf.find(a) !== uf.find(b)) {\n uf.union(a, b);\n mst.push([a, b, s]);\n if (mst.length === target) break;\n }\n }\n return mst;\n}\n\n// ---------------------------------------------------------------------------\n// Cluster confidence\n// ---------------------------------------------------------------------------\n\nexport interface ClusterConfidence {\n readonly minEdge: number | null;\n readonly avgEdge: number | null;\n readonly connectivity: number;\n readonly bottleneckPair: readonly [number, number] | null;\n readonly confidence: number;\n}\n\n/**\n * Compute confidence metrics for a cluster.\n * confidence = 0.4 * minEdge + 0.3 * avgEdge + 0.3 * connectivity\n */\nexport function computeClusterConfidence(\n pairScores: ReadonlyMap<PairKey, number>,\n size: number,\n): ClusterConfidence {\n if (size <= 1 || pairScores.size === 0) {\n return {\n minEdge: null,\n avgEdge: null,\n connectivity: size <= 1 ? 1.0 : 0.0,\n bottleneckPair: null,\n confidence: size <= 1 ? 1.0 : 0.0,\n };\n }\n\n let minEdge = Infinity;\n let sum = 0;\n let bottleneckKey: PairKey | null = null;\n\n for (const [key, score] of pairScores) {\n sum += score;\n if (score < minEdge) {\n minEdge = score;\n bottleneckKey = key;\n }\n }\n\n const avgEdge = sum / pairScores.size;\n const maxPossibleEdges = (size * (size - 1)) / 2;\n const connectivity =\n maxPossibleEdges > 0 ? pairScores.size / maxPossibleEdges : 0.0;\n\n const bottleneckPair: readonly [number, number] | null = bottleneckKey\n ? parsePairKey(bottleneckKey)\n : null;\n\n const confidence = 0.4 * minEdge + 0.3 * avgEdge + 0.3 * connectivity;\n\n return { minEdge, avgEdge, connectivity, bottleneckPair, confidence };\n}\n\n// ---------------------------------------------------------------------------\n// Split oversized cluster\n// ---------------------------------------------------------------------------\n\n/** Internal mutable cluster info used during building. */\ninterface MutableClusterInfo {\n members: number[];\n size: number;\n oversized: boolean;\n pairScores: Map<PairKey, number>;\n confidence: number;\n bottleneckPair: readonly [number, number] | null;\n clusterQuality: \"strong\" | \"weak\" | \"split\";\n _wasSplit?: boolean;\n}\n\n/**\n * Split a cluster by removing the weakest MST edge.\n * Returns sub-cluster infos.\n */\nexport function splitOversizedCluster(\n members: readonly number[],\n pairScores: ReadonlyMap<PairKey, number>,\n): MutableClusterInfo[] {\n if (members.length <= 1 || pairScores.size === 0) {\n return [\n {\n members: [...members].sort((a, b) => a - b),\n size: members.length,\n oversized: false,\n pairScores: new Map(pairScores),\n confidence: 1.0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n },\n ];\n }\n\n const mst = buildMst(members, pairScores);\n if (mst.length === 0) {\n return [\n {\n members: [...members].sort((a, b) => a - b),\n size: members.length,\n oversized: false,\n pairScores: new Map(pairScores),\n confidence: 1.0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n },\n ];\n }\n\n // Find weakest edge\n let weakestIdx = 0;\n let weakestScore = mst[0]![2];\n for (let i = 1; i < mst.length; i++) {\n if (mst[i]![2] < weakestScore) {\n weakestScore = mst[i]![2];\n weakestIdx = i;\n }\n }\n\n // Rebuild without weakest edge\n const uf = new UnionFind();\n uf.addMany(members as number[]);\n for (let i = 0; i < mst.length; i++) {\n if (i !== weakestIdx) {\n uf.union(mst[i]![0], mst[i]![1]);\n }\n }\n\n const result: MutableClusterInfo[] = [];\n for (const subMembers of uf.getClusters()) {\n const subList = [...subMembers].sort((a, b) => a - b);\n const subPairs = new Map<PairKey, number>();\n for (const [key, score] of pairScores) {\n const [a, b] = parsePairKey(key);\n if (subMembers.has(a) && subMembers.has(b)) {\n subPairs.set(key, score);\n }\n }\n const conf = computeClusterConfidence(subPairs, subList.length);\n result.push({\n members: subList,\n size: subList.length,\n oversized: false,\n pairScores: subPairs,\n confidence: conf.confidence,\n bottleneckPair: conf.bottleneckPair,\n clusterQuality: \"strong\",\n });\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// buildClusters options\n// ---------------------------------------------------------------------------\n\nexport interface BuildClustersOptions {\n readonly maxClusterSize?: number;\n readonly weakClusterThreshold?: number;\n readonly autoSplit?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// buildClusters\n// ---------------------------------------------------------------------------\n\n/**\n * Build clusters from scored pairs using Union-Find.\n *\n * Auto-splits oversized clusters via MST (iterative, not recursive).\n * Assigns cluster_quality: \"strong\", \"weak\" (avg-min > weakThreshold), or \"split\".\n * Downgrades confidence by 0.7 for weak clusters.\n */\nexport function buildClusters(\n pairs: readonly (readonly [number, number, number])[],\n allIds: readonly number[],\n options?: BuildClustersOptions,\n): Map<number, ClusterInfo> {\n const maxClusterSize = options?.maxClusterSize ?? 100;\n const weakClusterThreshold = options?.weakClusterThreshold ?? 0.3;\n const autoSplit = options?.autoSplit ?? true;\n\n // Build Union-Find from pairs\n const uf = new UnionFind();\n uf.addMany(allIds);\n for (const [idA, idB] of pairs) {\n uf.union(idA, idB);\n }\n\n const clusters = uf.getClusters();\n\n // Sort clusters by minimum member for deterministic IDs.\n // Use for-loop min — Math.min(...set) crashes on Sets with >65K elements.\n const minOf = (s: Set<number>): number => {\n let m = Infinity;\n for (const v of s) if (v < m) m = v;\n return m;\n };\n clusters.sort((a, b) => minOf(a) - minOf(b));\n\n // Map members to cluster IDs\n const memberToCid = new Map<number, number>();\n for (let i = 0; i < clusters.length; i++) {\n const cid = i + 1;\n for (const m of clusters[i]!) {\n memberToCid.set(m, cid);\n }\n }\n\n // Build mutable result\n const result = new Map<number, MutableClusterInfo>();\n for (let i = 0; i < clusters.length; i++) {\n const cid = i + 1;\n const memberArr = [...clusters[i]!].sort((a, b) => a - b);\n result.set(cid, {\n members: memberArr,\n size: memberArr.length,\n oversized: memberArr.length > maxClusterSize,\n pairScores: new Map(),\n confidence: 0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n });\n }\n\n // Assign pair scores to clusters (canonicalized keys)\n for (const [idA, idB, score] of pairs) {\n const cid = memberToCid.get(idA)!;\n const info = result.get(cid)!;\n info.pairScores.set(pairKey(idA, idB), score);\n }\n\n // Compute initial confidence\n for (const [, cinfo] of result) {\n const conf = computeClusterConfidence(cinfo.pairScores, cinfo.size);\n cinfo.confidence = conf.confidence;\n cinfo.bottleneckPair = conf.bottleneckPair;\n }\n\n // Auto-split oversized clusters (iterative)\n if (autoSplit) {\n const toSplit: number[] = [];\n for (const [cid, c] of result) {\n if (c.oversized) toSplit.push(cid);\n }\n\n while (toSplit.length > 0) {\n const cid = toSplit.pop()!;\n const cinfo = result.get(cid)!;\n result.delete(cid);\n\n const subClusters = splitOversizedCluster(\n cinfo.members,\n cinfo.pairScores,\n );\n let nextCid = 0;\n for (const [k] of result) {\n if (k > nextCid) nextCid = k;\n }\n nextCid += 1;\n\n for (const sc of subClusters) {\n sc.oversized = sc.size > maxClusterSize;\n sc._wasSplit = true;\n result.set(nextCid, sc);\n if (sc.oversized) {\n toSplit.push(nextCid);\n }\n nextCid++;\n }\n }\n }\n\n // Assign cluster_quality and apply confidence downgrade\n for (const [, cinfo] of result) {\n if (cinfo._wasSplit) {\n cinfo.clusterQuality = \"split\";\n } else if (cinfo.size > 1 && cinfo.pairScores.size > 0) {\n const scores = [...cinfo.pairScores.values()];\n let minE = Infinity;\n let sumE = 0;\n for (const s of scores) {\n if (s < minE) minE = s;\n sumE += s;\n }\n const avgE = sumE / scores.length;\n if (avgE - minE > weakClusterThreshold) {\n cinfo.clusterQuality = \"weak\";\n cinfo.confidence *= 0.7;\n } else {\n cinfo.clusterQuality = \"strong\";\n }\n } else {\n cinfo.clusterQuality = \"strong\";\n }\n delete cinfo._wasSplit;\n }\n\n // Freeze into ClusterInfo\n const frozen = new Map<number, ClusterInfo>();\n for (const [cid, c] of result) {\n frozen.set(cid, {\n members: c.members,\n size: c.size,\n oversized: c.oversized,\n pairScores: c.pairScores,\n confidence: c.confidence,\n bottleneckPair: c.bottleneckPair,\n clusterQuality: c.clusterQuality,\n });\n }\n return frozen;\n}\n\n// ---------------------------------------------------------------------------\n// addToCluster\n// ---------------------------------------------------------------------------\n\n/**\n * Add a new record to existing clusters based on matches.\n *\n * - No matches: new singleton cluster\n * - Single cluster match: join that cluster\n * - Multiple cluster match: merge all matched clusters\n *\n * Flags oversized but does NOT auto-split. Caller should call\n * splitOversizedCluster() if desired.\n */\nexport function addToCluster(\n recordId: number,\n matches: readonly (readonly [number, number])[],\n clusters: Map<number, ClusterInfo>,\n maxClusterSize = 100,\n): Map<number, ClusterInfo> {\n const makeSingleton = (): ClusterInfo => ({\n members: [recordId],\n size: 1,\n oversized: false,\n pairScores: new Map(),\n confidence: 1.0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n });\n\n if (matches.length === 0) {\n const nextCid = _nextCid(clusters);\n clusters.set(nextCid, makeSingleton());\n return clusters;\n }\n\n // Map members to cluster IDs\n const memberToCid = new Map<number, number>();\n for (const [cid, cinfo] of clusters) {\n for (const m of cinfo.members) {\n memberToCid.set(m, cid);\n }\n }\n\n const matchedCids = new Set<number>();\n for (const [matchedId] of matches) {\n const cid = memberToCid.get(matchedId);\n if (cid !== undefined) matchedCids.add(cid);\n }\n\n if (matchedCids.size === 0) {\n const nextCid = _nextCid(clusters);\n clusters.set(nextCid, makeSingleton());\n return clusters;\n }\n\n if (matchedCids.size === 1) {\n const cid = matchedCids.values().next().value!;\n const old = clusters.get(cid)!;\n const newPairs = new Map(old.pairScores);\n\n for (const [matchedId, score] of matches) {\n if (memberToCid.get(matchedId) === cid) {\n newPairs.set(pairKey(recordId, matchedId), score);\n }\n }\n\n const newMembers = [...old.members, recordId].sort((a, b) => a - b);\n const newSize = newMembers.length;\n const conf = computeClusterConfidence(newPairs, newSize);\n\n clusters.set(cid, {\n members: newMembers,\n size: newSize,\n oversized: newSize > maxClusterSize,\n pairScores: newPairs,\n confidence: conf.confidence,\n bottleneckPair: conf.bottleneckPair,\n clusterQuality: old.clusterQuality,\n });\n return clusters;\n }\n\n // Multiple clusters: merge all\n const mergedMembers: number[] = [recordId];\n const mergedPairs = new Map<PairKey, number>();\n\n for (const cid of matchedCids) {\n const cinfo = clusters.get(cid)!;\n mergedMembers.push(...cinfo.members);\n for (const [k, v] of cinfo.pairScores) {\n mergedPairs.set(k, v);\n }\n clusters.delete(cid);\n }\n\n for (const [matchedId, score] of matches) {\n mergedPairs.set(pairKey(recordId, matchedId), score);\n }\n\n const sortedMembers = mergedMembers.sort((a, b) => a - b);\n const size = sortedMembers.length;\n const conf = computeClusterConfidence(mergedPairs, size);\n const nextCid = _nextCid(clusters);\n\n clusters.set(nextCid, {\n members: sortedMembers,\n size,\n oversized: size > maxClusterSize,\n pairScores: mergedPairs,\n confidence: conf.confidence,\n bottleneckPair: conf.bottleneckPair,\n clusterQuality: \"strong\",\n });\n\n return clusters;\n}\n\n// ---------------------------------------------------------------------------\n// unmergeRecord\n// ---------------------------------------------------------------------------\n\n/**\n * Remove a record from its cluster and re-cluster remaining members.\n * The removed record becomes a singleton.\n */\nexport function unmergeRecord(\n recordId: number,\n clusters: Map<number, ClusterInfo>,\n threshold = 0.0,\n): Map<number, ClusterInfo> {\n // Find which cluster contains this record\n let sourceCid: number | null = null;\n for (const [cid, cinfo] of clusters) {\n if (cinfo.members.includes(recordId)) {\n sourceCid = cid;\n break;\n }\n }\n\n if (sourceCid === null) return clusters; // Not found\n const cinfo = clusters.get(sourceCid)!;\n if (cinfo.size <= 1) return clusters; // Already singleton\n\n // Extract pairs excluding the removed record, applying threshold\n const remainingMembers = cinfo.members.filter((m) => m !== recordId);\n const remainingPairs: [number, number, number][] = [];\n for (const [key, score] of cinfo.pairScores) {\n const [a, b] = parsePairKey(key);\n if (a !== recordId && b !== recordId && score >= threshold) {\n remainingPairs.push([a, b, score]);\n }\n }\n\n // Re-cluster remaining members\n const subClusters = buildClusters(remainingPairs, remainingMembers);\n\n // Remove the original cluster\n clusters.delete(sourceCid);\n\n // Assign new cluster IDs\n let nextCid = _nextCid(clusters);\n\n // Add the removed record as a singleton\n clusters.set(nextCid, {\n members: [recordId],\n size: 1,\n oversized: false,\n pairScores: new Map(),\n confidence: 1.0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n });\n nextCid++;\n\n // Add re-clustered groups\n for (const [, subInfo] of subClusters) {\n clusters.set(nextCid, subInfo);\n nextCid++;\n }\n\n return clusters;\n}\n\n// ---------------------------------------------------------------------------\n// unmergeCluster\n// ---------------------------------------------------------------------------\n\n/**\n * Shatter a cluster into individual singletons.\n * All members become their own cluster. Pair scores are discarded.\n */\nexport function unmergeCluster(\n clusterId: number,\n clusters: Map<number, ClusterInfo>,\n): Map<number, ClusterInfo> {\n const cinfo = clusters.get(clusterId);\n if (!cinfo) return clusters;\n\n const members = cinfo.members;\n clusters.delete(clusterId);\n\n let nextCid = _nextCid(clusters);\n for (const memberId of members) {\n clusters.set(nextCid, {\n members: [memberId],\n size: 1,\n oversized: false,\n pairScores: new Map(),\n confidence: 1.0,\n bottleneckPair: null,\n clusterQuality: \"strong\",\n });\n nextCid++;\n }\n\n return clusters;\n}\n\n// ---------------------------------------------------------------------------\n// getClusterPairScores\n// ---------------------------------------------------------------------------\n\n/**\n * Get pair scores for a specific set of cluster members from all pairs.\n * Call on-demand, not in hot path.\n */\nexport function getClusterPairScores(\n members: readonly number[],\n allPairs: readonly (readonly [number, number, number])[],\n): Map<PairKey, number> {\n const memberSet = new Set(members);\n const result = new Map<PairKey, number>();\n for (const [a, b, s] of allPairs) {\n if (memberSet.has(a) && memberSet.has(b)) {\n result.set(pairKey(a, b), s);\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction _nextCid(clusters: ReadonlyMap<number, unknown>): number {\n let max = 0;\n for (const k of clusters.keys()) {\n if (k > max) max = k;\n }\n return max + 1;\n}\n","/**\n * transforms.ts — Pure field transform utilities.\n * Edge-safe: no Node.js imports, no `process`.\n *\n * Ports goldenmatch/utils/transforms.py.\n */\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Apply a single named transform to a value. Returns null if input is null. */\nexport function applyTransform(\n value: string | null,\n transform: string,\n): string | null {\n if (value === null) return null;\n\n // Handle parameterized transforms (substring:start:end, qgram:n, bloom_filter:...)\n if (transform.startsWith(\"substring:\")) return applySubstring(value, transform);\n if (transform.startsWith(\"qgram:\")) return applyQgram(value, transform);\n if (transform.startsWith(\"bloom_filter\")) return applyBloomFilter(value, transform);\n\n switch (transform) {\n case \"lowercase\":\n return value.toLowerCase();\n case \"uppercase\":\n return value.toUpperCase();\n case \"strip\":\n return value.trim();\n case \"strip_all\":\n return value.replace(/\\s+/g, \"\");\n case \"digits_only\":\n return value.replace(/\\D/g, \"\");\n case \"alpha_only\":\n return value.replace(/[^a-zA-Z]/g, \"\");\n case \"normalize_whitespace\":\n return value.trim().replace(/\\s+/g, \" \");\n case \"token_sort\":\n return value\n .trim()\n .split(/\\s+/)\n .sort()\n .join(\" \");\n case \"first_token\":\n return value.trim().split(/\\s+/)[0] ?? \"\";\n case \"last_token\": {\n const tokens = value.trim().split(/\\s+/);\n return tokens[tokens.length - 1] ?? \"\";\n }\n case \"soundex\":\n return soundex(value);\n case \"metaphone\":\n return metaphone(value);\n default:\n return value;\n }\n}\n\n/** Apply a chain of transforms in order. */\nexport function applyTransforms(\n value: string | null,\n transforms: readonly string[],\n): string | null {\n let result = value;\n for (const t of transforms) {\n result = applyTransform(result, t);\n if (result === null) return null;\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Parameterized transforms\n// ---------------------------------------------------------------------------\n\n/** substring:start:end */\nfunction applySubstring(value: string, transform: string): string {\n const parts = transform.split(\":\");\n const start = parseInt(parts[1] ?? \"0\", 10);\n const end = parts[2] !== undefined ? parseInt(parts[2], 10) : undefined;\n return value.slice(start, end);\n}\n\n/** qgram:n — split into character n-grams, sorted and space-separated. */\nfunction applyQgram(value: string, transform: string): string {\n const parts = transform.split(\":\");\n const n = parseInt(parts[1] ?? \"2\", 10);\n if (n <= 0 || value.length < n) return value;\n const grams: string[] = [];\n for (let i = 0; i <= value.length - n; i++) {\n grams.push(value.slice(i, i + n));\n }\n return grams.sort().join(\" \");\n}\n\n// ---------------------------------------------------------------------------\n// Soundex — Robert Russell's algorithm\n// ---------------------------------------------------------------------------\n\nconst SOUNDEX_MAP: Record<string, string> = {\n B: \"1\", F: \"1\", P: \"1\", V: \"1\",\n C: \"2\", G: \"2\", J: \"2\", K: \"2\", Q: \"2\", S: \"2\", X: \"2\", Z: \"2\",\n D: \"3\", T: \"3\",\n L: \"4\",\n M: \"5\", N: \"5\",\n R: \"6\",\n};\n\n/**\n * American Soundex (Robert Russell, 1918).\n * 1. Keep first letter\n * 2. Map consonants to digits (B/F/P/V->1, C/G/J/K/Q/S/X/Z->2, D/T->3, L->4, M/N->5, R->6)\n * 3. Remove adjacent duplicates, vowels/H/W\n * 4. Pad/truncate to 4 chars\n *\n * H and W are transparent — they do NOT reset the duplicate suppression.\n * Vowels (A/E/I/O/U/Y) DO reset, so \"Pfister\" and \"Jackson\" work correctly.\n */\nexport function soundex(value: string): string {\n const clean = value.toUpperCase().replace(/[^A-Z]/g, \"\");\n if (clean.length === 0) return \"0000\";\n\n const firstLetter = clean[0]!;\n let code = firstLetter;\n let lastDigit = SOUNDEX_MAP[firstLetter] ?? \"0\";\n\n for (let i = 1; i < clean.length && code.length < 4; i++) {\n const ch = clean[i]!;\n const digit = SOUNDEX_MAP[ch];\n if (digit && digit !== lastDigit) {\n code += digit;\n lastDigit = digit;\n } else if (!digit) {\n // Vowel / H / W / Y — only H and W are transparent (do NOT reset)\n if (ch !== \"H\" && ch !== \"W\") {\n lastDigit = \"0\";\n }\n }\n }\n\n return (code + \"0000\").slice(0, 4);\n}\n\n// ---------------------------------------------------------------------------\n// Simplified Metaphone (Lawrence Philips, 1990)\n// ---------------------------------------------------------------------------\n\n/**\n * Simplified Metaphone.\n * Returns a phonetic code of up to 4 characters.\n */\nexport function metaphone(value: string): string {\n let word = value.toUpperCase().replace(/[^A-Z]/g, \"\");\n if (word.length === 0) return \"\";\n\n // Drop initial silent letter pairs\n const dropPrefixes = [\"AE\", \"GN\", \"KN\", \"PN\", \"WR\"];\n for (const prefix of dropPrefixes) {\n if (word.startsWith(prefix)) {\n word = word.slice(1);\n break;\n }\n }\n\n // Drop trailing MB (silent B)\n if (word.endsWith(\"MB\")) {\n word = word.slice(0, -1);\n }\n\n let code = \"\";\n let i = 0;\n\n while (i < word.length && code.length < 4) {\n const ch = word[i]!;\n const next = word[i + 1] ?? \"\";\n const prev = i > 0 ? word[i - 1]! : \"\";\n\n // Skip duplicate adjacent letters (except C)\n if (ch === prev && ch !== \"C\") {\n i++;\n continue;\n }\n\n switch (ch) {\n case \"A\":\n case \"E\":\n case \"I\":\n case \"O\":\n case \"U\":\n // Vowels only kept at the beginning\n if (i === 0) code += ch;\n break;\n\n case \"B\":\n code += \"B\";\n break;\n\n case \"C\":\n if (next === \"I\" || next === \"E\" || next === \"Y\") {\n code += \"S\";\n } else {\n code += \"K\";\n }\n break;\n\n case \"D\":\n if (next === \"G\" && \"IEY\".includes(word[i + 2] ?? \"\")) {\n code += \"J\";\n } else {\n code += \"T\";\n }\n break;\n\n case \"F\":\n code += \"F\";\n break;\n\n case \"G\":\n if (next === \"H\" && i + 2 < word.length && !\"AEIOU\".includes(word[i + 2] ?? \"\")) {\n // GH before non-vowel is silent\n i += 2;\n continue;\n } else if (i > 0 && (next === \"N\" || (next === \"N\" && word[i + 2] === \"E\" && i + 2 === word.length - 1))) {\n // GN at end is silent — skip\n } else if (prev === \"G\") {\n // Already handled double G\n } else if (next === \"I\" || next === \"E\" || next === \"Y\") {\n code += \"J\";\n } else {\n code += \"K\";\n }\n break;\n\n case \"H\":\n if (\"AEIOU\".includes(next) && !\"AEIOU\".includes(prev)) {\n code += \"H\";\n }\n break;\n\n case \"J\":\n code += \"J\";\n break;\n\n case \"K\":\n if (prev !== \"C\") code += \"K\";\n break;\n\n case \"L\":\n code += \"L\";\n break;\n\n case \"M\":\n code += \"M\";\n break;\n\n case \"N\":\n code += \"N\";\n break;\n\n case \"P\":\n if (next === \"H\") {\n code += \"F\";\n i++;\n } else {\n code += \"P\";\n }\n break;\n\n case \"Q\":\n code += \"K\";\n break;\n\n case \"R\":\n code += \"R\";\n break;\n\n case \"S\":\n if (next === \"H\" || (next === \"I\" && (word[i + 2] === \"O\" || word[i + 2] === \"A\"))) {\n code += \"X\";\n i++;\n } else if (next === \"C\" && word[i + 2] === \"H\") {\n code += \"SK\";\n i += 2;\n } else {\n code += \"S\";\n }\n break;\n\n case \"T\":\n if (next === \"H\") {\n code += \"0\"; // theta\n i++;\n } else if (next === \"I\" && (word[i + 2] === \"O\" || word[i + 2] === \"A\")) {\n code += \"X\";\n } else {\n code += \"T\";\n }\n break;\n\n case \"V\":\n code += \"F\";\n break;\n\n case \"W\":\n case \"Y\":\n if (\"AEIOU\".includes(next)) {\n code += ch;\n }\n break;\n\n case \"X\":\n code += \"KS\";\n break;\n\n case \"Z\":\n code += \"S\";\n break;\n\n default:\n break;\n }\n i++;\n }\n\n return code.slice(0, 4);\n}\n\n// ---------------------------------------------------------------------------\n// Bloom filter transform (pure TS, for PPRL) — SHA-256 parity with Python\n// ---------------------------------------------------------------------------\n\n/**\n * Security level presets for bloom filter parameters (match Python exactly).\n *\n * standard: 512-bit, 20 hash functions, 2-grams\n * high: 1024-bit, 30 hash functions, 2-grams, HMAC-SHA256 salting\n * paranoid: 2048-bit, 40 hash functions, 3-grams, HMAC-SHA256 + balanced padding\n *\n * See goldenmatch/utils/transforms.py::_bloom_filter_transform for the\n * reference algorithm we match.\n */\ninterface BloomPreset {\n readonly size: number;\n readonly k: number;\n readonly ngram: number;\n readonly hmac: boolean;\n readonly balanced: boolean;\n}\n\nconst BLOOM_PRESETS: Record<string, BloomPreset> = {\n standard: { size: 512, k: 20, ngram: 2, hmac: false, balanced: false },\n high: { size: 1024, k: 30, ngram: 2, hmac: true, balanced: false },\n paranoid: { size: 2048, k: 40, ngram: 3, hmac: true, balanced: true },\n};\n\n/** Default parameters when called as plain \"bloom_filter\" (matches Python). */\nconst BLOOM_DEFAULTS = { size: 1024, k: 20, ngram: 2 };\n\n/** Default HMAC key used by the high/paranoid presets (matches Python). */\nconst BLOOM_DEFAULT_HMAC_KEY = \"default_field_key\";\n\n/**\n * Build a CLK (Cryptographic Longterm Key) bloom filter hex string.\n *\n * Forms accepted:\n * - \"bloom_filter\" -> defaults (1024/20/2, no hmac)\n * - \"bloom_filter:standard\" -> preset\n * - \"bloom_filter:high[:customKey]\" -> preset, optional HMAC key override\n * - \"bloom_filter:paranoid[:customKey]\" -> preset, optional HMAC key override\n * - \"bloom_filter:<ngram>:<k>:<size>[:hmac_key]\" -> fully parametric\n */\nfunction applyBloomFilter(value: string, transform: string): string {\n let ngramSize = BLOOM_DEFAULTS.ngram;\n let numHashes = BLOOM_DEFAULTS.k;\n let filterSize = BLOOM_DEFAULTS.size;\n let hmacKey: string | null = null;\n let balanced = false;\n\n if (transform === \"bloom_filter\") {\n // defaults\n } else {\n const parts = transform.split(\":\");\n const maybeLevel = parts[1];\n if (maybeLevel && (maybeLevel in BLOOM_PRESETS)) {\n const preset = BLOOM_PRESETS[maybeLevel]!;\n ngramSize = preset.ngram;\n numHashes = preset.k;\n filterSize = preset.size;\n balanced = preset.balanced;\n if (preset.hmac) {\n // Allow per-field HMAC key override via bloom_filter:<level>:<key>\n hmacKey = parts[2] && parts[2].length > 0 ? parts[2] : BLOOM_DEFAULT_HMAC_KEY;\n }\n } else {\n // Parametric form: bloom_filter:<ngram>:<k>:<size>[:hmac_key]\n ngramSize = parseInt(parts[1] ?? String(BLOOM_DEFAULTS.ngram), 10);\n numHashes = parseInt(parts[2] ?? String(BLOOM_DEFAULTS.k), 10);\n filterSize = parseInt(parts[3] ?? String(BLOOM_DEFAULTS.size), 10);\n if (parts.length > 4 && parts[4]!.length > 0) {\n hmacKey = parts[4]!;\n }\n }\n }\n\n const filterBytes = Math.floor(filterSize / 8);\n const bits = new Uint8Array(filterBytes);\n\n // Match Python: value.lower().strip(), left-pad with '_' up to ngramSize.\n let padded = value.toLowerCase().trim();\n if (padded.length < ngramSize) {\n padded = padded.padEnd(ngramSize, \"_\");\n }\n\n // Balanced padding: deterministic salt append to normalize filter density.\n if (balanced && padded.length < 8) {\n const salt = sha256Hex(padded).slice(0, 8);\n padded = padded + salt;\n }\n\n // Generate character n-grams.\n const ngrams: string[] = [];\n for (let i = 0; i <= padded.length - ngramSize; i++) {\n ngrams.push(padded.slice(i, i + ngramSize));\n }\n\n // Hash each n-gram k times.\n for (const ngram of ngrams) {\n for (let k = 0; k < numHashes; k++) {\n const hex = hmacKey\n ? hmacSha256Hex(`${hmacKey}:${k}`, ngram)\n : sha256Hex(`${k}:${ngram}`);\n // bit_pos = int(h, 16) % filter_size\n const bitPos = Number(modHexBigInt(hex, filterSize));\n bits[bitPos >> 3]! |= 1 << (bitPos & 7);\n }\n }\n\n return hexEncode(bits);\n}\n\n/** Compute (BigInt hex) mod (Number) and return a non-negative Number result. */\nfunction modHexBigInt(hex: string, modulus: number): number {\n // filterSize fits comfortably in a Number; use BigInt only for the big hex.\n const big = BigInt(\"0x\" + hex);\n const mod = BigInt(modulus);\n const rem = big % mod;\n return Number(rem);\n}\n\n/** Hex-encode a Uint8Array. */\nfunction hexEncode(bytes: Uint8Array): string {\n const hex: string[] = [];\n for (let i = 0; i < bytes.length; i++) {\n hex.push(bytes[i]!.toString(16).padStart(2, \"0\"));\n }\n return hex.join(\"\");\n}\n\n// ---------------------------------------------------------------------------\n// SHA-256 — pure TS, edge-safe (no node: imports)\n// ---------------------------------------------------------------------------\n\n// FIPS 180-4 round constants.\nconst K256 = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n]);\n\nfunction rotr32(x: number, n: number): number {\n return ((x >>> n) | (x << (32 - n))) >>> 0;\n}\n\n/** UTF-8 encode a string to bytes (edge-safe — uses TextEncoder). */\nfunction utf8Encode(input: string): Uint8Array {\n return new TextEncoder().encode(input);\n}\n\n/** SHA-256 core: digest a byte array, return 32-byte digest. */\nfunction sha256Bytes(msg: Uint8Array): Uint8Array {\n // Initial hash values (FIPS 180-4).\n const H = new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,\n 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n ]);\n\n // Pre-processing: append 1 bit, k zero bits, 64-bit length (big-endian).\n const msgLen = msg.length;\n const bitLen = msgLen * 8;\n const withPadLen = ((msgLen + 9 + 63) >> 6) << 6; // round up to 64-byte block\n const padded = new Uint8Array(withPadLen);\n padded.set(msg);\n padded[msgLen] = 0x80;\n // Length in bits as 64-bit big-endian at the end.\n // High 32 bits are ~0 for any realistic input in JS; write bitLen in low 32.\n const hi = Math.floor(bitLen / 0x100000000);\n const lo = bitLen >>> 0;\n const dv = new DataView(padded.buffer);\n dv.setUint32(withPadLen - 8, hi, false);\n dv.setUint32(withPadLen - 4, lo, false);\n\n const W = new Uint32Array(64);\n\n for (let offset = 0; offset < withPadLen; offset += 64) {\n // Schedule\n for (let t = 0; t < 16; t++) {\n W[t] = dv.getUint32(offset + t * 4, false);\n }\n for (let t = 16; t < 64; t++) {\n const w15 = W[t - 15]!;\n const w2 = W[t - 2]!;\n const s0 = rotr32(w15, 7) ^ rotr32(w15, 18) ^ (w15 >>> 3);\n const s1 = rotr32(w2, 17) ^ rotr32(w2, 19) ^ (w2 >>> 10);\n W[t] = (W[t - 16]! + s0 + W[t - 7]! + s1) >>> 0;\n }\n\n let a = H[0]!, b = H[1]!, c = H[2]!, d = H[3]!;\n let e = H[4]!, f = H[5]!, g = H[6]!, h = H[7]!;\n\n for (let t = 0; t < 64; t++) {\n const S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);\n const ch = (e & f) ^ (~e & g);\n const T1 = (h + S1 + ch + K256[t]! + W[t]!) >>> 0;\n const S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22);\n const mj = (a & b) ^ (a & c) ^ (b & c);\n const T2 = (S0 + mj) >>> 0;\n h = g;\n g = f;\n f = e;\n e = (d + T1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (T1 + T2) >>> 0;\n }\n\n H[0] = (H[0]! + a) >>> 0;\n H[1] = (H[1]! + b) >>> 0;\n H[2] = (H[2]! + c) >>> 0;\n H[3] = (H[3]! + d) >>> 0;\n H[4] = (H[4]! + e) >>> 0;\n H[5] = (H[5]! + f) >>> 0;\n H[6] = (H[6]! + g) >>> 0;\n H[7] = (H[7]! + h) >>> 0;\n }\n\n const out = new Uint8Array(32);\n const outDv = new DataView(out.buffer);\n for (let i = 0; i < 8; i++) outDv.setUint32(i * 4, H[i]!, false);\n return out;\n}\n\n/**\n * SHA-256 digest of a UTF-8 string, returned as lowercase 64-char hex.\n *\n * Matches Python `hashlib.sha256(s.encode()).hexdigest()` bit-for-bit.\n */\nexport function sha256Hex(input: string): string {\n return hexEncode(sha256Bytes(utf8Encode(input)));\n}\n\n/**\n * HMAC-SHA256(key, msg) as lowercase 64-char hex.\n *\n * Matches Python `hmac.new(key.encode(), msg.encode(), hashlib.sha256).hexdigest()`.\n */\nexport function hmacSha256Hex(key: string, msg: string): string {\n const blockSize = 64;\n let keyBytes = utf8Encode(key);\n if (keyBytes.length > blockSize) {\n keyBytes = sha256Bytes(keyBytes);\n }\n const kPad = new Uint8Array(blockSize);\n kPad.set(keyBytes);\n\n const oKeyPad = new Uint8Array(blockSize);\n const iKeyPad = new Uint8Array(blockSize);\n for (let i = 0; i < blockSize; i++) {\n oKeyPad[i] = kPad[i]! ^ 0x5c;\n iKeyPad[i] = kPad[i]! ^ 0x36;\n }\n\n const msgBytes = utf8Encode(msg);\n const inner = new Uint8Array(blockSize + msgBytes.length);\n inner.set(iKeyPad);\n inner.set(msgBytes, blockSize);\n const innerHash = sha256Bytes(inner);\n\n const outer = new Uint8Array(blockSize + innerHash.length);\n outer.set(oKeyPad);\n outer.set(innerHash, blockSize);\n return hexEncode(sha256Bytes(outer));\n}\n","/**\n * scorer.ts — Fuzzy scoring module for GoldenMatch.\n * Edge-safe: no Node.js imports, pure TypeScript only.\n *\n * Ports goldenmatch/core/scorer.py. The Python version uses `rapidfuzz`\n * for vectorized NxN scoring. Here we implement all algorithms in pure TS.\n */\n\nimport type {\n Row,\n MatchkeyField,\n MatchkeyConfig,\n PairKey,\n ScoredPair,\n BlockResult,\n} from \"./types.js\";\nimport { makeScoredPair } from \"./types.js\";\nimport { pairKey } from \"./cluster.js\";\nimport { applyTransforms, soundex } from \"./transforms.js\";\n\n// ---------------------------------------------------------------------------\n// Helper: coerce unknown to string | null\n// ---------------------------------------------------------------------------\n\n/** Convert unknown value to string or null. */\nexport function asString(v: unknown): string | null {\n if (v === null || v === undefined) return null;\n if (typeof v === \"string\") return v;\n return String(v);\n}\n\n// ---------------------------------------------------------------------------\n// Scoring algorithms — pure TS\n// ---------------------------------------------------------------------------\n\n/**\n * Jaro similarity between two strings.\n *\n * matchWindow = floor(max(lenA, lenB) / 2) - 1\n * Count matches (chars within window) and transpositions.\n * jaro = (m/lenA + m/lenB + (m - t/2) / m) / 3\n */\nexport function jaro(a: string, b: string): number {\n if (a === b) return 1.0;\n const lenA = a.length;\n const lenB = b.length;\n if (lenA === 0 || lenB === 0) return 0.0;\n\n const matchWindow = Math.max(Math.floor(Math.max(lenA, lenB) / 2) - 1, 0);\n\n const aMatched = new Uint8Array(lenA); // 0 = unmatched\n const bMatched = new Uint8Array(lenB);\n let matches = 0;\n\n // Find matching characters\n for (let i = 0; i < lenA; i++) {\n const lo = Math.max(0, i - matchWindow);\n const hi = Math.min(lenB - 1, i + matchWindow);\n for (let j = lo; j <= hi; j++) {\n if (bMatched[j] !== 0 || a[i] !== b[j]) continue;\n aMatched[i] = 1;\n bMatched[j] = 1;\n matches++;\n break;\n }\n }\n\n if (matches === 0) return 0.0;\n\n // Count transpositions\n let transpositions = 0;\n let k = 0;\n for (let i = 0; i < lenA; i++) {\n if (aMatched[i] === 0) continue;\n while (bMatched[k] === 0) k++;\n if (a[i] !== b[k]) transpositions++;\n k++;\n }\n\n return (\n (matches / lenA + matches / lenB + (matches - transpositions / 2) / matches) / 3\n );\n}\n\n/**\n * Jaro-Winkler similarity.\n * Adds a bonus for a common prefix of up to 4 characters, scaling factor 0.1.\n */\nexport function jaroWinkler(a: string, b: string): number {\n const jaroSim = jaro(a, b);\n if (jaroSim === 0.0) return 0.0;\n\n // Common prefix up to 4 chars\n const maxPrefix = Math.min(4, Math.min(a.length, b.length));\n let prefix = 0;\n for (let i = 0; i < maxPrefix; i++) {\n if (a[i] === b[i]) prefix++;\n else break;\n }\n\n return jaroSim + prefix * 0.1 * (1 - jaroSim);\n}\n\n/**\n * Levenshtein edit distance (classic DP, 2-row optimization).\n */\nexport function levenshteinDistance(a: string, b: string): number {\n const lenA = a.length;\n const lenB = b.length;\n if (lenA === 0) return lenB;\n if (lenB === 0) return lenA;\n\n // Two-row DP\n let prev = new Uint32Array(lenB + 1);\n let curr = new Uint32Array(lenB + 1);\n\n for (let j = 0; j <= lenB; j++) prev[j] = j;\n\n for (let i = 1; i <= lenA; i++) {\n curr[0] = i;\n for (let j = 1; j <= lenB; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(\n prev[j]! + 1, // deletion\n curr[j - 1]! + 1, // insertion\n prev[j - 1]! + cost, // substitution\n );\n }\n // Swap rows\n [prev, curr] = [curr, prev];\n }\n\n return prev[lenB]!;\n}\n\n/**\n * Normalized Levenshtein similarity: 1 - distance / max(lenA, lenB).\n */\nexport function levenshteinSimilarity(a: string, b: string): number {\n if (a === b) return 1.0;\n const maxLen = Math.max(a.length, b.length);\n if (maxLen === 0) return 1.0;\n return 1 - levenshteinDistance(a, b) / maxLen;\n}\n\n/**\n * Indel (insertion+deletion) edit distance.\n *\n * Like Levenshtein but without substitutions — a substitution costs 2\n * (one delete + one insert) instead of 1. This matches the distance\n * metric used by rapidfuzz's Indel ratio, which underlies\n * `rapidfuzz.fuzz.token_sort_ratio` in Python.\n */\nexport function indelDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n const m = a.length;\n const n = b.length;\n let prev = new Uint32Array(n + 1);\n let curr = new Uint32Array(n + 1);\n for (let j = 0; j <= n; j++) prev[j] = j;\n for (let i = 1; i <= m; i++) {\n curr[0] = i;\n for (let j = 1; j <= n; j++) {\n if (a.charCodeAt(i - 1) === b.charCodeAt(j - 1)) {\n curr[j] = prev[j - 1]!;\n } else {\n // Only insert or delete allowed — cost 1 each. No substitution.\n curr[j] = Math.min(prev[j]! + 1, curr[j - 1]! + 1);\n }\n }\n [prev, curr] = [curr, prev];\n }\n return prev[n]!;\n}\n\n/**\n * Indel normalized similarity: `1 - d_indel / (len_a + len_b)`.\n * Matches rapidfuzz's `Indel.normalized_similarity`.\n */\nexport function indelSimilarity(a: string, b: string): number {\n const total = a.length + b.length;\n if (total === 0) return 1.0;\n return 1 - indelDistance(a, b) / total;\n}\n\n/**\n * Token sort ratio, rapidfuzz-compatible.\n *\n * Matches `rapidfuzz.fuzz.token_sort_ratio`:\n * 1. Lowercase both strings.\n * 2. Strip non-alphanumeric characters (replace with whitespace).\n * 3. Split on whitespace, drop empties, sort tokens, rejoin with single space.\n * 4. Compare via Indel normalized similarity (NOT Levenshtein).\n *\n * Python reference: for (\"John Smith\", \"Smith Johnson\") returns ~0.8571.\n */\nexport function tokenSortRatio(a: string, b: string): number {\n const normalize = (s: string): string =>\n s\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .sort()\n .join(\" \");\n return indelSimilarity(normalize(a), normalize(b));\n}\n\n/**\n * Soundex match: 1.0 if soundex codes equal, else 0.0.\n */\nexport function soundexMatch(a: string, b: string): number {\n return soundex(a) === soundex(b) ? 1.0 : 0.0;\n}\n\n// ---------------------------------------------------------------------------\n// Bloom filter / PPRL scorers\n// ---------------------------------------------------------------------------\n\n/** Convert a hex string to a Uint8Array of bytes. */\nfunction hexToBytes(hex: string): Uint8Array {\n const len = hex.length >>> 1;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/** Count the number of set bits (popcount) in a byte array. */\nfunction popcount(bytes: Uint8Array): number {\n let count = 0;\n for (let i = 0; i < bytes.length; i++) {\n let b = bytes[i]!;\n // Brian Kernighan's algorithm\n while (b !== 0) {\n b &= b - 1;\n count++;\n }\n }\n return count;\n}\n\n/** Count set bits in bitwise AND of two byte arrays. */\nfunction popcountAnd(a: Uint8Array, b: Uint8Array): number {\n const len = Math.min(a.length, b.length);\n let count = 0;\n for (let i = 0; i < len; i++) {\n let v = (a[i]! & b[i]!);\n while (v !== 0) {\n v &= v - 1;\n count++;\n }\n }\n return count;\n}\n\n/** Count set bits in bitwise OR of two byte arrays. */\nfunction popcountOr(a: Uint8Array, b: Uint8Array): number {\n const maxLen = Math.max(a.length, b.length);\n let count = 0;\n for (let i = 0; i < maxLen; i++) {\n let v = ((a[i] ?? 0) | (b[i] ?? 0));\n while (v !== 0) {\n v &= v - 1;\n count++;\n }\n }\n return count;\n}\n\n/**\n * Dice coefficient on two hex-encoded bloom filters.\n * 2 * intersection / (popcount_a + popcount_b)\n */\nexport function diceCoefficient(a: string, b: string): number {\n const bytesA = hexToBytes(a);\n const bytesB = hexToBytes(b);\n const pcA = popcount(bytesA);\n const pcB = popcount(bytesB);\n const total = pcA + pcB;\n if (total === 0) return 0.0;\n const intersection = popcountAnd(bytesA, bytesB);\n return (2 * intersection) / total;\n}\n\n/**\n * Jaccard similarity on two hex-encoded bloom filters.\n * intersection / union of bits\n */\nexport function jaccardSimilarity(a: string, b: string): number {\n const bytesA = hexToBytes(a);\n const bytesB = hexToBytes(b);\n const intersection = popcountAnd(bytesA, bytesB);\n const union = popcountOr(bytesA, bytesB);\n if (union === 0) return 0.0;\n return intersection / union;\n}\n\n// ---------------------------------------------------------------------------\n// Ensemble scorer\n// ---------------------------------------------------------------------------\n\n/**\n * Ensemble scorer: combines jaro_winkler, token_sort, and soundex_match * 0.8.\n * Takes element-wise max of all three.\n */\nexport function ensembleScore(a: string, b: string): number {\n const jw = jaroWinkler(a, b);\n const ts = tokenSortRatio(a, b);\n const sx = soundexMatch(a, b) * 0.8;\n return Math.max(jw, ts, sx);\n}\n\n// ---------------------------------------------------------------------------\n// Public: scoreField\n// ---------------------------------------------------------------------------\n\n/**\n * Score two field values using the specified scorer.\n * Returns null if either value is null.\n */\nexport function scoreField(\n valA: string | null,\n valB: string | null,\n scorer: string,\n): number | null {\n if (valA === null || valB === null) return null;\n\n switch (scorer) {\n case \"exact\":\n return valA === valB ? 1.0 : 0.0;\n case \"jaro_winkler\":\n return jaroWinkler(valA, valB);\n case \"levenshtein\":\n return levenshteinSimilarity(valA, valB);\n case \"token_sort\":\n return tokenSortRatio(valA, valB);\n case \"soundex_match\":\n return soundexMatch(valA, valB);\n case \"dice\":\n return diceCoefficient(valA, valB);\n case \"jaccard\":\n return jaccardSimilarity(valA, valB);\n case \"ensemble\":\n return ensembleScore(valA, valB);\n default:\n throw new Error(`Unknown scorer: ${JSON.stringify(scorer)}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: scorePair\n// ---------------------------------------------------------------------------\n\n/**\n * Score a pair of rows across all fields using weighted aggregation.\n * Fields that produce null scores are excluded. If all null -> 0.0.\n */\nexport function scorePair(\n rowA: Row,\n rowB: Row,\n fields: readonly MatchkeyField[],\n): number {\n let weightedSum = 0;\n let weightSum = 0;\n for (const f of fields) {\n const valA = applyTransforms(asString(rowA[f.field]), f.transforms);\n const valB = applyTransforms(asString(rowB[f.field]), f.transforms);\n const fieldScore = scoreField(valA, valB, f.scorer);\n if (fieldScore !== null) {\n weightedSum += fieldScore * f.weight;\n weightSum += f.weight;\n }\n }\n return weightSum === 0 ? 0 : weightedSum / weightSum;\n}\n\n// ---------------------------------------------------------------------------\n// NxN score matrix\n// ---------------------------------------------------------------------------\n\n/**\n * Build an NxN score matrix for a list of values using a scorer.\n * Symmetric: matrix[i][j] === matrix[j][i]. Diagonal is 0.\n */\nexport function scoreMatrix(\n values: (string | null)[],\n scorerName: string,\n): number[][] {\n const n = values.length;\n const matrix: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const s = scoreField(values[i]!, values[j]!, scorerName) ?? 0;\n matrix[i]![j] = s;\n matrix[j]![i] = s;\n }\n }\n return matrix;\n}\n\n// ---------------------------------------------------------------------------\n// Exact score matrix (hash-based grouping, O(n))\n// ---------------------------------------------------------------------------\n\nfunction exactScoreMatrix(values: (string | null)[]): number[][] {\n const n = values.length;\n const matrix: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n // Group indices by value\n const groups = new Map<string, number[]>();\n for (let i = 0; i < n; i++) {\n const v = values[i];\n if (v != null) {\n const existing = groups.get(v);\n if (existing !== undefined) {\n existing.push(i);\n } else {\n groups.set(v, [i]);\n }\n }\n }\n groups.forEach((indices) => {\n if (indices.length > 1) {\n for (let a = 0; a < indices.length; a++) {\n for (let b = a + 1; b < indices.length; b++) {\n matrix[indices[a]!]![indices[b]!] = 1.0;\n matrix[indices[b]!]![indices[a]!] = 1.0;\n }\n }\n }\n });\n return matrix;\n}\n\n/** Soundex score matrix: group by soundex code, 1.0 for same code. */\nfunction soundexScoreMatrix(values: (string | null)[]): number[][] {\n const codes = values.map((v) => (v !== null ? soundex(v) : null));\n return exactScoreMatrix(codes);\n}\n\n/** Ensemble score matrix: max of jaro_winkler, token_sort, soundex*0.8 */\nfunction ensembleScoreMatrix(values: (string | null)[]): number[][] {\n const n = values.length;\n const clean = values.map((v) => v ?? \"\");\n const jw: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n const ts: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n const sx = soundexScoreMatrix(values);\n const result: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n if (values[i] === null || values[j] === null) continue;\n jw[i]![j] = jaroWinkler(clean[i]!, clean[j]!);\n jw[j]![i] = jw[i]![j]!;\n ts[i]![j] = tokenSortRatio(clean[i]!, clean[j]!);\n ts[j]![i] = ts[i]![j]!;\n }\n }\n\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const val = Math.max(jw[i]![j]!, ts[i]![j]!, sx[i]![j]! * 0.8);\n result[i]![j] = val;\n result[j]![i] = val;\n }\n }\n return result;\n}\n\n/**\n * Build an NxN null mask: true where either value is null.\n */\nfunction buildNullMask(values: (string | null)[]): boolean[][] {\n const n = values.length;\n const mask: boolean[][] = Array.from({ length: n }, () => new Array<boolean>(n).fill(false));\n for (let i = 0; i < n; i++) {\n if (values[i] === null) {\n for (let j = 0; j < n; j++) {\n mask[i]![j] = true;\n mask[j]![i] = true;\n }\n }\n }\n return mask;\n}\n\n/**\n * Build the appropriate score matrix for a scorer name.\n */\nfunction buildScoreMatrix(values: (string | null)[], scorerName: string): number[][] {\n switch (scorerName) {\n case \"exact\":\n return exactScoreMatrix(values);\n case \"soundex_match\":\n return soundexScoreMatrix(values);\n case \"ensemble\":\n return ensembleScoreMatrix(values);\n default:\n return scoreMatrix(values, scorerName);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Get transformed values for a field from block rows\n// ---------------------------------------------------------------------------\n\nfunction getTransformedValues(\n rows: readonly Row[],\n field: MatchkeyField,\n): (string | null)[] {\n return rows.map((row) => {\n const raw = asString(row[field.field]);\n return applyTransforms(raw, field.transforms);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public: findExactMatches\n// ---------------------------------------------------------------------------\n\n/**\n * Find exact matches by grouping rows on matchkey columns.\n * Builds a composite key from all matchkey fields (with transforms applied),\n * groups rows sharing the same key, and returns all pairs with score 1.0.\n *\n * Rows must have a `__row_id__` field.\n */\nexport function findExactMatches(\n rows: readonly Row[],\n mk: MatchkeyConfig,\n): ScoredPair[] {\n if (rows.length < 2) return [];\n\n // Build composite matchkey for each row\n const groups = new Map<string, number[]>();\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i]!;\n const rowId = row[\"__row_id__\"] as number;\n // Build key from all fields\n let keyParts: (string | null)[] = [];\n let hasNull = false;\n for (const f of mk.fields) {\n const raw = asString(row[f.field]);\n const transformed = applyTransforms(raw, f.transforms);\n if (transformed === null) {\n hasNull = true;\n break;\n }\n keyParts.push(transformed);\n }\n // Skip rows with any null field (nulls don't match)\n if (hasNull) continue;\n\n const key = keyParts.join(\"\\x00\"); // null byte separator\n const existing = groups.get(key);\n if (existing !== undefined) {\n existing.push(rowId);\n } else {\n groups.set(key, [rowId]);\n }\n }\n\n // Extract pairs from groups\n const pairs: ScoredPair[] = [];\n groups.forEach((members) => {\n if (members.length < 2) return;\n for (let i = 0; i < members.length; i++) {\n for (let j = i + 1; j < members.length; j++) {\n pairs.push(makeScoredPair(members[i]!, members[j]!, 1.0));\n }\n }\n });\n return pairs;\n}\n\n// ---------------------------------------------------------------------------\n// Public: findFuzzyMatches\n// ---------------------------------------------------------------------------\n\n/**\n * Find fuzzy matches within a block of rows (NxN scoring).\n *\n * Implements early termination:\n * - Score cheap fields (exact/soundex) first\n * - Check if max possible score can reach threshold\n * - Score expensive fuzzy fields only for promising pairs\n *\n * Rows must have a `__row_id__` field.\n */\nexport function findFuzzyMatches(\n rows: readonly Row[],\n mk: MatchkeyConfig,\n excludePairs?: ReadonlySet<PairKey>,\n preScoredPairs?: readonly ScoredPair[],\n): ScoredPair[] {\n // findFuzzyMatches only runs for weighted/probabilistic matchkeys\n // (exact is handled via findExactMatches). Exact has no threshold.\n const threshold = mk.type === \"exact\" ? 1.0 : (mk.threshold ?? 0.85);\n\n // Fast path: pre-scored pairs (from ANN blocking)\n if (preScoredPairs !== undefined) {\n const results: ScoredPair[] = [];\n for (const p of preScoredPairs) {\n if (p.score < threshold) continue;\n const idA = Math.min(p.idA, p.idB);\n const idB = Math.max(p.idA, p.idB);\n const key = pairKey(idA, idB);\n if (excludePairs !== undefined && excludePairs.has(key)) continue;\n results.push(makeScoredPair(idA, idB, p.score));\n }\n return results;\n }\n\n const n = rows.length;\n if (n < 2) return [];\n\n const rowIds = rows.map((r) => r[\"__row_id__\"] as number);\n\n // Separate cheap (exact + soundex) from expensive (fuzzy) fields\n const cheapFields = mk.fields.filter(\n (f) => f.scorer === \"exact\" || f.scorer === \"soundex_match\",\n );\n const fuzzyFields = mk.fields.filter(\n (f) => f.scorer !== \"exact\" && f.scorer !== \"soundex_match\" && f.scorer !== \"record_embedding\",\n );\n\n const totalWeight = mk.fields.reduce((sum, f) => sum + f.weight, 0);\n if (totalWeight === 0) return [];\n\n // Phase 1: Score cheap fields and build null masks\n // cheapNumerator[i][j] = sum(fieldScore * weight) for cheap fields\n // cheapDenominator[i][j] = sum(weight) for non-null cheap fields\n const cheapNumerator: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n const cheapDenominator: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n\n for (const f of cheapFields) {\n const values = getTransformedValues(rows, f);\n const nullMask = buildNullMask(values);\n const scores =\n f.scorer === \"exact\"\n ? exactScoreMatrix(values)\n : soundexScoreMatrix(values);\n\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n if (!nullMask[i]![j]!) {\n cheapNumerator[i]![j]! += scores[i]![j]! * f.weight;\n cheapNumerator[j]![i]! = cheapNumerator[i]![j]!;\n cheapDenominator[i]![j]! += f.weight;\n cheapDenominator[j]![i]! = cheapDenominator[i]![j]!;\n }\n }\n }\n }\n\n // Phase 2: Early termination check\n const fuzzyTotalWeight = fuzzyFields.reduce((sum, f) => sum + f.weight, 0);\n\n // Track which pairs are impossible (can't reach threshold)\n const impossible: boolean[][] = Array.from({ length: n }, () => new Array<boolean>(n).fill(false));\n\n let combined: number[][];\n\n if (fuzzyFields.length === 0) {\n // No fuzzy fields — just use cheap scores\n combined = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n combined[i]![j] =\n cheapDenominator[i]![j]! > 0\n ? cheapNumerator[i]![j]! / cheapDenominator[i]![j]!\n : 0;\n combined[j]![i] = combined[i]![j]!;\n }\n }\n } else {\n // Check which pairs can possibly reach threshold\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const maxNum = cheapNumerator[i]![j]! + fuzzyTotalWeight;\n const maxDen = cheapDenominator[i]![j]! + fuzzyTotalWeight;\n const maxPossible = maxDen > 0 ? maxNum / maxDen : 0;\n if (maxPossible < threshold) {\n impossible[i]![j] = true;\n impossible[j]![i] = true;\n }\n }\n }\n\n // Phase 3: Score fuzzy fields with intra-field early termination\n const fuzzyNumerator: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n const fuzzyDenominator: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n\n for (let fIdx = 0; fIdx < fuzzyFields.length; fIdx++) {\n const f = fuzzyFields[fIdx]!;\n const values = getTransformedValues(rows, f);\n const nullMask = buildNullMask(values);\n const scores = buildScoreMatrix(values, f.scorer);\n\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n if (!nullMask[i]![j]!) {\n fuzzyNumerator[i]![j]! += scores[i]![j]! * f.weight;\n fuzzyNumerator[j]![i] = fuzzyNumerator[i]![j]!;\n fuzzyDenominator[i]![j]! += f.weight;\n fuzzyDenominator[j]![i] = fuzzyDenominator[i]![j]!;\n }\n }\n }\n\n // Intra-field early termination: check if any pair can still reach threshold\n const remainingWeight = fuzzyFields\n .slice(fIdx + 1)\n .reduce((sum, ff) => sum + ff.weight, 0);\n\n if (remainingWeight > 0) {\n let anyCanReach = false;\n for (let i = 0; i < n && !anyCanReach; i++) {\n for (let j = i + 1; j < n && !anyCanReach; j++) {\n if (impossible[i]![j]!) continue;\n const totalNum =\n cheapNumerator[i]![j]! + fuzzyNumerator[i]![j]! + remainingWeight;\n const totalDen =\n cheapDenominator[i]![j]! + fuzzyDenominator[i]![j]! + remainingWeight;\n const bestPossible = totalDen > 0 ? totalNum / totalDen : 0;\n if (bestPossible >= threshold) {\n anyCanReach = true;\n }\n }\n }\n if (!anyCanReach) break; // No pair can reach threshold — skip remaining fields\n }\n }\n\n // Combine cheap + fuzzy\n combined = Array.from({ length: n }, () => new Array<number>(n).fill(0));\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n if (impossible[i]![j]!) {\n combined[i]![j] = 0;\n } else {\n const totalNum = cheapNumerator[i]![j]! + fuzzyNumerator[i]![j]!;\n const totalDen = cheapDenominator[i]![j]! + fuzzyDenominator[i]![j]!;\n combined[i]![j] = totalDen > 0 ? totalNum / totalDen : 0;\n }\n combined[j]![i] = combined[i]![j]!;\n }\n }\n }\n\n // Extract upper triangle pairs above threshold\n const results: ScoredPair[] = [];\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const score = combined[i]![j]!;\n if (score < threshold) continue;\n const idA = Math.min(rowIds[i]!, rowIds[j]!);\n const idB = Math.max(rowIds[i]!, rowIds[j]!);\n const key = pairKey(idA, idB);\n if (excludePairs !== undefined && excludePairs.has(key)) continue;\n results.push(makeScoredPair(idA, idB, score));\n }\n }\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Public: scoreBlocksSequential\n// ---------------------------------------------------------------------------\n\nexport interface ScoreBlocksOptions {\n /** Filter to cross-source pairs only. */\n readonly acrossFilesOnly?: boolean;\n /** Row ID -> source name mapping (for acrossFilesOnly). */\n readonly sourceLookup?: ReadonlyMap<number, string>;\n /** Target IDs for match mode — filter to target/ref cross pairs. */\n readonly targetIds?: ReadonlySet<number>;\n}\n\n/**\n * Score all blocks sequentially.\n *\n * In JS there is no GIL, so we use sequential scoring as the default.\n * For web workers or similar concurrency, the caller can partition blocks.\n */\nexport function scoreBlocksSequential(\n blocks: readonly BlockResult[],\n mk: MatchkeyConfig,\n matchedPairs: Set<PairKey>,\n options?: ScoreBlocksOptions,\n): ScoredPair[] {\n if (blocks.length === 0) return [];\n\n const acrossFilesOnly = options?.acrossFilesOnly ?? false;\n const sourceLookup = options?.sourceLookup;\n const targetIds = options?.targetIds;\n\n const allPairs: ScoredPair[] = [];\n\n for (const block of blocks) {\n // For cross-file mode, check that block has records from multiple sources\n if (acrossFilesOnly && sourceLookup !== undefined) {\n const sourcesInBlock = new Set<string>();\n for (const row of block.rows) {\n const src = sourceLookup.get(row[\"__row_id__\"] as number);\n if (src !== undefined) sourcesInBlock.add(src);\n }\n if (sourcesInBlock.size < 2) continue;\n }\n\n // Use a frozen copy of matchedPairs for consistency\n const excludeSnapshot: ReadonlySet<PairKey> = new Set(matchedPairs);\n\n let pairs = findFuzzyMatches(\n block.rows,\n mk,\n excludeSnapshot,\n block.preScoredPairs,\n );\n\n // Cross-file filter\n if (acrossFilesOnly && sourceLookup !== undefined) {\n pairs = pairs.filter((p) => {\n const srcA = sourceLookup.get(p.idA);\n const srcB = sourceLookup.get(p.idB);\n return srcA !== srcB;\n });\n }\n\n // Target/ref cross filter for match mode\n if (targetIds !== undefined) {\n pairs = pairs.filter(\n (p) => targetIds.has(p.idA) !== targetIds.has(p.idB),\n );\n }\n\n for (const p of pairs) {\n allPairs.push(p);\n matchedPairs.add(pairKey(p.idA, p.idB));\n }\n }\n\n return allPairs;\n}\n\n// ---------------------------------------------------------------------------\n// Utility: canonicalize pair key\n// ---------------------------------------------------------------------------\n\n// Re-export pairKey from cluster.ts — single canonical source of truth.\nexport { pairKey };\n","/**\n * score-worker.ts -- piscina worker that scores a single block.\n *\n * Invoked by piscina with input { block, mk, matchedPairs }.\n * Returns the scored pairs for that block.\n *\n * Built as a separate tsup entry so it can be loaded by piscina from disk\n * at runtime. The worker runs in its own V8 isolate (worker_thread) so CPU\n * work here is truly parallel with the main thread and other workers.\n */\nimport { findFuzzyMatches } from \"../../core/scorer.js\";\nimport type {\n BlockResult,\n MatchkeyConfig,\n PairKey,\n ScoredPair,\n} from \"../../core/types.js\";\n\nexport interface ScoreWorkerInput {\n readonly block: BlockResult;\n readonly mk: MatchkeyConfig;\n /** Serialized Set<PairKey> contents -- piscina can't transfer Sets. */\n readonly matchedPairs: readonly PairKey[];\n}\n\nexport interface ScoreWorkerOutput {\n readonly pairs: readonly ScoredPair[];\n}\n\nexport default function scoreWorker(\n input: ScoreWorkerInput,\n): ScoreWorkerOutput {\n const excludeSet = new Set<PairKey>(input.matchedPairs);\n const pairs = findFuzzyMatches(\n input.block.rows,\n input.mk,\n excludeSet,\n input.block.preScoredPairs,\n );\n return { pairs };\n}\n"]}