///
import { type Cell, Default, handler, lift, recipe } from "commontools";
interface SearchResult {
id: string;
title: string;
textScore: number;
clickRate: number;
freshness: number;
}
interface SearchWeights {
text: number;
clicks: number;
freshness: number;
}
interface ScoreContributions {
text: number;
clicks: number;
freshness: number;
}
const DEFAULT_WEIGHTS: SearchWeights = {
text: 0.6,
clicks: 0.3,
freshness: 0.1,
};
type WeightDefaults = {
text: 0.6;
clicks: 0.3;
freshness: 0.1;
};
interface SearchRelevanceArgs {
results: Default;
weights: Default;
}
interface WeightAdjustEvent {
text?: number;
clicks?: number;
freshness?: number;
textDelta?: number;
clicksDelta?: number;
freshnessDelta?: number;
reset?: boolean;
}
interface ResultMetricEvent {
id?: string;
textScore?: number;
clickRate?: number;
freshness?: number;
textDelta?: number;
clickDelta?: number;
freshnessDelta?: number;
}
interface ScoredResult {
id: string;
title: string;
score: number;
contributions: ScoreContributions;
}
const roundValue = (value: number, digits = 3): number => {
const factor = 10 ** digits;
return Math.round(value * factor) / factor;
};
const sanitizeWeightValue = (
value: unknown,
fallback: number,
): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return fallback;
}
if (value <= 0) return 0;
return roundValue(value);
};
const sanitizeWeights = (input: SearchWeights | undefined): SearchWeights => {
const source = input ?? DEFAULT_WEIGHTS;
const text = sanitizeWeightValue(source.text, DEFAULT_WEIGHTS.text);
const clicks = sanitizeWeightValue(source.clicks, DEFAULT_WEIGHTS.clicks);
const freshness = sanitizeWeightValue(
source.freshness,
DEFAULT_WEIGHTS.freshness,
);
const total = text + clicks + freshness;
if (!Number.isFinite(total) || total <= 0) {
return { ...DEFAULT_WEIGHTS };
}
return { text, clicks, freshness };
};
const normalizeWeights = (weights: SearchWeights): SearchWeights => {
const total = weights.text + weights.clicks + weights.freshness;
if (!Number.isFinite(total) || total <= 0) {
return { ...DEFAULT_WEIGHTS };
}
const textPortion = roundValue(weights.text / total);
const clicksPortion = roundValue(weights.clicks / total);
let freshnessPortion = roundValue(weights.freshness / total);
const sum = roundValue(textPortion + clicksPortion + freshnessPortion);
if (sum !== 1) {
freshnessPortion = roundValue(1 - textPortion - clicksPortion);
}
return {
text: Math.max(0, Math.min(1, textPortion)),
clicks: Math.max(0, Math.min(1, clicksPortion)),
freshness: Math.max(0, Math.min(1, freshnessPortion)),
};
};
const clampFraction = (value: unknown, fallback: number): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return fallback;
}
const bounded = Math.max(0, Math.min(1, value));
return roundValue(bounded);
};
const sanitizeResultEntry = (
entry: Partial | undefined,
index: number,
): SearchResult => {
const safeId = typeof entry?.id === "string" && entry.id.trim()
? entry.id.trim()
: `result-${index}`;
const safeTitle = typeof entry?.title === "string" && entry.title.trim()
? entry.title.trim()
: `Result ${index + 1}`;
return {
id: safeId,
title: safeTitle,
textScore: clampFraction(entry?.textScore, 0),
clickRate: clampFraction(entry?.clickRate, 0),
freshness: clampFraction(entry?.freshness, 0),
};
};
const sanitizeResults = (
entries: readonly SearchResult[] | undefined,
): SearchResult[] => {
if (!Array.isArray(entries)) return [];
return entries.map((entry, index) => sanitizeResultEntry(entry, index));
};
const scoreResult = (
result: SearchResult,
weights: SearchWeights,
): ScoredResult => {
const text = roundValue(result.textScore * weights.text);
const clicks = roundValue(result.clickRate * weights.clicks);
const freshness = roundValue(result.freshness * weights.freshness);
const score = roundValue(text + clicks + freshness);
return {
id: result.id,
title: result.title,
score,
contributions: { text, clicks, freshness },
};
};
const tuneWeightsHandler = handler(
(
event: WeightAdjustEvent | undefined,
context: { weights: Cell },
) => {
if (event?.reset) {
context.weights.set({ ...DEFAULT_WEIGHTS });
return;
}
const current = sanitizeWeights(context.weights.get());
const next = { ...current };
const applyAbsolute = (
key: keyof SearchWeights,
value: number | undefined,
) => {
if (typeof value !== "number" || !Number.isFinite(value)) return;
next[key] = sanitizeWeightValue(value, next[key]);
};
const applyDelta = (
key: keyof SearchWeights,
value: number | undefined,
) => {
if (typeof value !== "number" || !Number.isFinite(value)) return;
next[key] = sanitizeWeightValue(next[key] + value, next[key]);
};
applyAbsolute("text", event?.text);
applyAbsolute("clicks", event?.clicks);
applyAbsolute("freshness", event?.freshness);
applyDelta("text", event?.textDelta);
applyDelta("clicks", event?.clicksDelta);
applyDelta("freshness", event?.freshnessDelta);
context.weights.set(sanitizeWeights(next));
},
);
const updateResultMetricsHandler = handler(
(
event: ResultMetricEvent | undefined,
context: { results: Cell },
) => {
const list = sanitizeResults(context.results.get());
if (list.length === 0) return;
const fallbackId = list[0].id;
const targetId = typeof event?.id === "string" && event.id.trim()
? event.id.trim()
: fallbackId;
const index = list.findIndex((entry) => entry.id === targetId);
if (index < 0) return;
const next = { ...list[index] };
const applyAbsolute = (
key: keyof Pick,
value: number | undefined,
) => {
if (typeof value !== "number" || !Number.isFinite(value)) return;
next[key] = clampFraction(value, next[key]);
};
const applyDelta = (
key: keyof Pick,
value: number | undefined,
) => {
if (typeof value !== "number" || !Number.isFinite(value)) return;
next[key] = clampFraction(next[key] + value, next[key]);
};
applyAbsolute("textScore", event?.textScore);
applyAbsolute("clickRate", event?.clickRate);
applyAbsolute("freshness", event?.freshness);
applyDelta("textScore", event?.textDelta);
applyDelta("clickRate", event?.clickDelta);
applyDelta("freshness", event?.freshnessDelta);
const updated = list.map((entry, position) =>
position === index ? next : entry
);
context.results.set(updated);
},
);
export const searchRelevanceTuning = recipe(
"Search Relevance Tuning Pattern",
({ results, weights }) => {
const sanitizedResults = lift(sanitizeResults)(results);
const sanitizedWeights = lift(sanitizeWeights)(weights);
const normalizedWeights = lift(normalizeWeights)(sanitizedWeights);
const scoringInputs = {
results: sanitizedResults,
weights: normalizedWeights,
};
const rankedResults = lift((input: {
results: SearchResult[];
weights: SearchWeights;
}): ScoredResult[] => {
const scored = input.results.map((entry) =>
scoreResult(entry, input.weights)
);
return scored.sort((a, b) => {
if (b.score === a.score) return a.id.localeCompare(b.id);
return b.score - a.score;
});
})(scoringInputs);
const relevanceOrder = lift((entries: ScoredResult[]) =>
entries.map((entry) => entry.id)
)(rankedResults);
const scoreSample = lift((entries: ScoredResult[]) =>
entries.map((entry) => `${entry.title}: ${entry.score.toFixed(3)}`)
)(rankedResults);
const topResult = lift((entries: ScoredResult[]) => entries[0] ?? null)(
rankedResults,
);
const topTitle = lift((entry: ScoredResult | null) =>
entry?.title ?? "(none)"
)(topResult);
const topScore = lift((entry: ScoredResult | null) =>
entry ? entry.score.toFixed(3) : "0.000"
)(topResult);
const weightSummary = lift((values: SearchWeights) => {
const textPortion = values.text.toFixed(3);
const clicksPortion = values.clicks.toFixed(3);
const freshnessPortion = values.freshness.toFixed(3);
return "Weights text " + textPortion + " | clicks " + clicksPortion +
" | freshness " + freshnessPortion;
})(normalizedWeights);
const contributionSummary = lift((entry: ScoredResult | null) => {
if (!entry) return "text 0.000 | clicks 0.000 | freshness 0.000";
const parts = entry.contributions;
return "text " + parts.text.toFixed(3) + " | clicks " +
parts.clicks.toFixed(3) + " | freshness " +
parts.freshness.toFixed(3);
})(topResult);
const scoreSummary = lift((input: {
title: string;
score: string;
weights: string;
}) => `${input.title} leads at ${input.score} with ${input.weights}`)({
title: topTitle,
score: topScore,
weights: weightSummary,
});
return {
results,
weights,
sanitizedResults,
sanitizedWeights,
normalizedWeights,
rankedResults,
relevanceOrder,
scoreSample,
topResult,
topTitle,
topScore,
weightSummary,
contributionSummary,
scoreSummary,
tuneWeights: tuneWeightsHandler({ weights }),
updateResult: updateResultMetricsHandler({ results }),
};
},
);