spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1/** Pure part of entity search (§294, §312): tier → match label and the final ordering. */23export type SearchType = 'cancer' | 'gene' | 'variant' | 'drug' | 'trial' | 'publication' | 'source';4export type SearchMatch = 'exact' | 'alias' | 'prefix' | 'trigram' | 'identifier';56export interface RankedHit {7 type: SearchType;8 title: string;9 /** 0 exact · 1 alias/identifier · 2 prefix · 3 trigram */10 tier: number;11 /** pg_trgm similarity (0..1), higher is better */12 score: number;13}1415const IDENTIFIER_TYPES: ReadonlySet<SearchType> = new Set(['gene', 'trial', 'publication', 'variant']);1617/** Tier 1 means "matched an identifier" for code-like entities and "matched an alias" for the rest. */18export function matchFromTier(tier: number, type: SearchType): SearchMatch {19 if (tier <= 0) return 'exact';20 if (tier === 1) return IDENTIFIER_TYPES.has(type) ? 'identifier' : 'alias';21 if (tier === 2) return 'prefix';22 return 'trigram';23}2425/** Ordering: exact > alias/identifier > prefix > trigram, then similarity desc, then title A→Z. */26export function compareHits(a: RankedHit, b: RankedHit): number {27 if (a.tier !== b.tier) return a.tier - b.tier;28 if (a.score !== b.score) return b.score - a.score;29 return a.title.localeCompare(b.title, 'en', { sensitivity: 'base' });30}3132export function sortHits<T extends RankedHit>(hits: T[]): T[] {33 return [...hits].sort(compareHits);34}35