In scripts/correct_recovery.py, the two headline numbers are macro-averaged over different class sets:
apparent_recovery = np.nanmean(...) over all 7 classes (line 64)
corrected_recovery = np.mean(corrected_vals) where corrected_vals only collects classes for which rogan_gladen returned a finite value (lines 62, 69)
rogan_gladen returns nan whenever a class is uninvertible (sensitivity + specificity <= 1) or its sensitivity is nan (no validation items of that class). So an uninvertible class is counted in apparent but dropped from corrected, making the side-by-side "apparent vs corrected" an average over non-matching denominators.
Confirmed on the committed pilot: apparent=0.2143 (7 classes) vs corrected=0.1651 (5 classes). The dropped classes are neutral (apparent=1.0, sensitivity=nan — no true-neutral validation items) and surprise (0.0135 + 0.9693 <= 1). Because the highest-apparent class (neutral=1.0) is silently excluded from the corrected macro, the direction of distortion depends entirely on which classes are uninvertible — here it lowers corrected (conservative, reinforces the null), but had a low-apparent class been the uninvertible one, dropping it would inflate corrected above apparent and manufacture a spurious "correction lifted recovery" effect.
Fix: average apparent and corrected over the same class set (restrict apparent to classes where the correction is defined), and surface the excluded classes in the output JSON so the two macros can't be read as like-for-like.
In
scripts/correct_recovery.py, the two headline numbers are macro-averaged over different class sets:apparent_recovery = np.nanmean(...)over all 7 classes (line 64)corrected_recovery = np.mean(corrected_vals)wherecorrected_valsonly collects classes for whichrogan_gladenreturned a finite value (lines 62, 69)rogan_gladenreturnsnanwhenever a class is uninvertible (sensitivity + specificity <= 1) or its sensitivity isnan(no validation items of that class). So an uninvertible class is counted inapparentbut dropped fromcorrected, making the side-by-side "apparent vs corrected" an average over non-matching denominators.Confirmed on the committed pilot:
apparent=0.2143(7 classes) vscorrected=0.1651(5 classes). The dropped classes are neutral (apparent=1.0, sensitivity=nan — no true-neutral validation items) and surprise (0.0135 + 0.9693 <= 1). Because the highest-apparent class (neutral=1.0) is silently excluded from the corrected macro, the direction of distortion depends entirely on which classes are uninvertible — here it lowers corrected (conservative, reinforces the null), but had a low-apparent class been the uninvertible one, dropping it would inflate corrected above apparent and manufacture a spurious "correction lifted recovery" effect.Fix: average
apparentandcorrectedover the same class set (restrict apparent to classes where the correction is defined), and surface the excluded classes in the output JSON so the two macros can't be read as like-for-like.