@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
22import {
33 computeGateVerdictCompositeCalibrationScore ,
44 computeFindingSeverityCompositeCalibrationScore ,
5+ computePairwiseCalibrationScore ,
56} from "../../packages/loopover-engine/src/index" ;
67
78// Converges gate-verdict + finding-severity calibration with reviewer-consensus-calibration.ts's already-correct
@@ -75,3 +76,63 @@ describe("gate-verdict/finding-severity calibration convergence (#6170)", () =>
7576 ] ) ;
7677 } ) ;
7778} ) ;
79+
80+ // Extends the #6170 all-zero-weight pattern to pairwise-calibration.ts (#7443). Vitest coverage is what
81+ // Codecov grades; the engine package's node:test suite mirrors the same assertions.
82+ describe ( "pairwise calibration zero-weight convergence (#7443)" , ( ) => {
83+ it ( "explicit all-zero weights fall back to objective-only even when pairwiseJudgeScore is present" , ( ) => {
84+ const result = computePairwiseCalibrationScore ( {
85+ objectiveAnchor : 0.42 ,
86+ samples : [ { attempts : [ { replayFirst : "replay_better" , revealedFirst : "revealed_better" } ] } ] ,
87+ weights : { objectiveAnchor : 0 , pairwiseJudge : 0 } ,
88+ } ) ;
89+ expect ( result . pairwiseJudgeScore ) . toBe ( 1 ) ;
90+ expect ( result . weights ) . toEqual ( { objectiveAnchor : 1 , pairwiseJudge : 0 } ) ;
91+ expect ( result . compositeScore ) . toBe ( 0.42 ) ;
92+ } ) ;
93+
94+ it ( "NaN/negative weights still recover to the 50/50 default (not the objective-only fallback)" , ( ) => {
95+ const result = computePairwiseCalibrationScore ( {
96+ objectiveAnchor : 1 ,
97+ samples : [ { attempts : [ { replayFirst : "revealed_better" , revealedFirst : "replay_better" } ] } ] ,
98+ weights : { objectiveAnchor : Number . NaN , pairwiseJudge : - 1 } ,
99+ } ) ;
100+ expect ( result . weights ) . toEqual ( { objectiveAnchor : 0.5 , pairwiseJudge : 0.5 } ) ;
101+ expect ( result . compositeScore ) . toBe ( 0.5 ) ;
102+ } ) ;
103+
104+ it ( "non-zero weights take the normalized usable path (covers usableTotal > 0)" , ( ) => {
105+ const result = computePairwiseCalibrationScore ( {
106+ objectiveAnchor : 0.55 ,
107+ samples : [
108+ { attempts : [ { replayFirst : "replay_better" , revealedFirst : "revealed_better" } ] } ,
109+ { attempts : [ { replayFirst : "tie" , revealedFirst : "tie" } ] } ,
110+ ] ,
111+ weights : { objectiveAnchor : 1 , pairwiseJudge : 3 } ,
112+ } ) ;
113+ expect ( result . weights ) . toEqual ( { objectiveAnchor : 0.25 , pairwiseJudge : 0.75 } ) ;
114+ expect ( result . compositeScore ) . toBe ( 0.7 ) ;
115+ } ) ;
116+
117+ it ( "missing pairwise signal zeros that component then falls back to objective-only when usable total is empty" , ( ) => {
118+ const result = computePairwiseCalibrationScore ( {
119+ objectiveAnchor : 0.42 ,
120+ samples : [ { attempts : [ { replayFirst : "incomparable" , revealedFirst : "incomparable" } ] } ] ,
121+ weights : { objectiveAnchor : 0 , pairwiseJudge : 0 } ,
122+ } ) ;
123+ expect ( result . pairwiseJudgeScore ) . toBeNull ( ) ;
124+ expect ( result . weights ) . toEqual ( { objectiveAnchor : 1 , pairwiseJudge : 0 } ) ;
125+ expect ( result . compositeScore ) . toBe ( 0.42 ) ;
126+ } ) ;
127+
128+ it ( "missing pairwise signal with non-zero weights renormalizes to objective-only (covers usableTotal > 0 + null pairwise)" , ( ) => {
129+ const result = computePairwiseCalibrationScore ( {
130+ objectiveAnchor : 0.42 ,
131+ samples : [ { attempts : [ { replayFirst : "incomparable" , revealedFirst : "incomparable" } ] } ] ,
132+ weights : { objectiveAnchor : 1 , pairwiseJudge : 1 } ,
133+ } ) ;
134+ expect ( result . pairwiseJudgeScore ) . toBeNull ( ) ;
135+ expect ( result . weights ) . toEqual ( { objectiveAnchor : 1 , pairwiseJudge : 0 } ) ;
136+ expect ( result . compositeScore ) . toBe ( 0.42 ) ;
137+ } ) ;
138+ } ) ;
0 commit comments