/** * Score boosts (Task 308). * * Two post-fetch score adjustments that re-rank hybrid hits using * properties that the typed-edge hook (Task 305) and compiled-truth * migration (Task 306) populate. Both are no-ops on the current corpus * until those tasks land — the boost reads a missing property as zero * and adds nothing. * * Boosts are pure functions over the scored hit array. They never query * Neo4j; the data they read is already on each hit's `properties` map. */ export interface ScoredHit { nodeId: string; properties: Record; score: number; } /** * Add a flat fractional bump to any hit whose node carries a non-null * `compiledTruth` property. Authoritative summaries beat noisy timeline * fragments — this puts compiled rows ahead when the score is close. * * Default weight 0.15 (+15%); the brief reserves room to tune. */ export function applyCompiledTruthBoost( hits: T[], weight = 0.15, ): T[] { if (weight < 0 || weight > 1) { throw new Error(`applyCompiledTruthBoost: weight must be in [0,1], got ${weight}`); } return hits.map((hit) => { const compiled = hit.properties["compiledTruth"]; if (compiled === null || compiled === undefined || compiled === "") return hit; return { ...hit, score: hit.score * (1 + weight) }; }); } /** * Log-weighted boost scaled by inbound MENTIONS edge count. * * Formula: `bump = clamp(0.05 + 0.05 * log10(count), 0.05, 0.25)` when * count > 0; zero otherwise. So 1 backlink → +5%, 10 → +10%, 100 → +15%, * 1000 → +20%, 10000+ → capped at +25%. Backlink-rich nodes (the * graph's natural authorities) surface higher. * * Reads from `hit.properties.backlinkCount` — a property Task 305's * post-write hook maintains. Until 305 lands the count is absent and * the boost is identity. */ export function applyBacklinkBoost(hits: T[]): T[] { return hits.map((hit) => { const raw = hit.properties["backlinkCount"]; const count = typeof raw === "number" ? raw : Number(raw); if (!Number.isFinite(count) || count <= 0) return hit; const bump = Math.max(0.05, Math.min(0.25, 0.05 + 0.05 * Math.log10(count))); return { ...hit, score: hit.score * (1 + bump) }; }); }