Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/lib/ranking-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
// (RAG_RANKING_CONFIG), so `tune:search-weights` and eval-gated experiments can adjust
// ranking without code edits.
//
// IMPORTANT — defaults reproduce the exact prior behavior:
// * every secondStage weight equals the constant it replaced,
// * documentDiversityPenalty defaults to 0 (the diversity demotion is OFF),
// * freshness.mode defaults to "step" (the original cliff).
// So merely landing this module changes nothing. The new behaviors (diversity demotion,
// linear freshness curve) only take effect once explicitly configured and eval-gated, in
// keeping with the "defaults unchanged until tuned" + non-degradation guarantees.
// Defaults were eval-gated ON (2026-07-03). The secondStage weights still equal the constants
// they replaced, but the two behaviors #213 shipped OFF are now ON by default because the golden
// retrieval + rag-only quality evals showed a strict improvement with no regression on the safety
// bar (retrieval mrr@10 0.728 -> 0.757, document/content recall@5 stays 1.0, grounded_supported
// 0.967 -> 1.0, unsupported_correct unchanged, citation/numeric failure 0):
// * documentDiversityPenalty = 0.02 (gentle same-document crowding demotion, capped 0.12; RC7/CI-16),
// * freshness.mode = "linear" (ramped decay instead of the harsh cliff; CI-17).
// The RAG_RANKING_CONFIG JSON override still allows further per-deployment tuning, and setting
// documentDiversityPenalty:0 + freshness.mode:"step" restores the exact pre-flip behavior.

export type SecondStageWeights = {
/** Position-decay boost applied to the top result, decaying by positionStep per rank. */
Expand Down Expand Up @@ -79,10 +81,10 @@ export const defaultRankingConfig: RankingConfig = {
lowIndexQualityPenalty: 0.035,
lowIndexQualityThreshold: 0.55,
},
documentDiversityPenalty: 0,
documentDiversityPenalty: 0.02,
documentDiversityPenaltyCap: 0.12,
freshness: {
mode: "step",
mode: "linear",
publicationCliffYears: 8,
publicationPenalty: -0.015,
reviewCliffYears: 5,
Expand Down Expand Up @@ -142,7 +144,9 @@ export function resolveRankingConfig(raw?: string | null): RankingConfig {
documentDiversityPenalty: Math.max(0, num(parsed.documentDiversityPenalty, d.documentDiversityPenalty)),
documentDiversityPenaltyCap: Math.max(0, num(parsed.documentDiversityPenaltyCap, d.documentDiversityPenaltyCap)),
freshness: {
mode: fr.mode === "linear" ? "linear" : "step",
// Honor a valid explicit override; otherwise fall back to the default mode (not a hardcoded
// "step") so the eval-gated default actually takes effect at runtime.
mode: fr.mode === "linear" || fr.mode === "step" ? fr.mode : d.freshness.mode,
publicationCliffYears: num(fr.publicationCliffYears, d.freshness.publicationCliffYears),
publicationPenalty: num(fr.publicationPenalty, d.freshness.publicationPenalty),
reviewCliffYears: num(fr.reviewCliffYears, d.freshness.reviewCliffYears),
Expand Down
12 changes: 7 additions & 5 deletions tests/ranking-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ describe("ranking-config defaults (W6 — zero behavior change)", () => {
expect(w.lowIndexQualityThreshold).toBe(0.55);
});

it("keeps document-diversity demotion OFF by default", () => {
expect(defaultRankingConfig.documentDiversityPenalty).toBe(0);
it("enables gentle document-diversity demotion by default (eval-gated 2026-07-03)", () => {
expect(defaultRankingConfig.documentDiversityPenalty).toBe(0.02);
expect(defaultRankingConfig.documentDiversityPenaltyCap).toBe(0.12);
});

it("defaults freshness to the original step cliffs", () => {
it("defaults freshness to the ramped linear curve (eval-gated 2026-07-03), keeping the cliff params", () => {
expect(defaultRankingConfig.freshness).toMatchObject({
mode: "step",
mode: "linear",
publicationCliffYears: 8,
publicationPenalty: -0.015,
reviewCliffYears: 5,
Expand Down Expand Up @@ -75,7 +76,8 @@ describe("resolveRankingConfig override merge", () => {
});

describe("freshnessDecayPenalty", () => {
const step = defaultRankingConfig.freshness;
// The default is now "linear"; construct an explicit step config to validate step-mode behavior.
const step = { ...defaultRankingConfig.freshness, mode: "step" as const };

it("step mode reproduces the original publication/review cliffs exactly", () => {
expect(freshnessDecayPenalty(null, "publication", step)).toBe(0);
Expand Down
Loading