feat: unified cross-detector confidence calibration (0.8.0)#58
Conversation
Confidence was computed three incompatible ways — 1−p (dist/mv), logistic-over-threshold (point/ctx/coll/psi), linear (cadence) — so a 0.9 meant different things per detector and severity couldn't rank across detectors. Now every detector routes through one shared ax_detect::calibrate module: confidence = logistic(STEEPNESS · relative excess past the firing threshold) from_exceedance(stat, thr) for "fires above" (modz, CUSUM shift, PSI, null-rate); from_undercut(stat, thr) for "fires below" (p-value < alpha, CV < max, BH cutoff). 0.5 at the threshold, →1 as it gets more extreme; a finding "2× past threshold" earns the same confidence on any detector. Removed the duplicated/divergent shift_confidence, psi_confidence, robustz::confidence helpers. Recalibrates every published confidence and severity (and point FDR mode now keys confidence off the BH cutoff). config_version bumped anomalyx-cfg/7 → /8 so agents see the change; envelope shape and PROTOCOL unchanged. Structural schema-diff findings keep their fixed priors (binary facts, not statistics); its null-rate finding joins the unified scale. Gates: proptest + cargo-mutants 0-missed on calibrate.rs (32/32) and all touched detector files (point/ctx/coll/cadence/structural/config/robustz 178 caught; dist/mv 193 caught + 10 loop-bound timeouts = detected). calibrate has exact two-x-past-threshold comparability + boundary/NaN tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR implements unified confidence calibration across all detectors in the anomaly detection suite. A new ChangesUnified Confidence Calibration
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/ax-detect/src/dist.rs (1)
731-753:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winStale test name and comment contradict the new calibrated confidence.
The name
chi2_detector_confidence_is_one_minus_pvalueand the commentconfidence≈0.954describe the removed1 − pbehavior. Withcalibrate::from_undercut(p, dist_alpha), this case (χ²=4.0, p≈0.0455, α=0.05) now yields confidence ≈ 0.549, not 0.954. The survivingconfidence < 0.99assertion still passes but no longer validates anything meaningful. Consider mirroringks_detector_confidence_is_calibrated_from_pvalueand asserting against the calibrated value.💚 Rename and correct the comment (optionally tighten the assertion)
- #[test] - fn chi2_detector_confidence_is_one_minus_pvalue() { - // 50/50 → 40/60 gives χ²=4.0, p≈0.0455 (flagged), confidence≈0.954. + #[test] + fn chi2_detector_confidence_is_calibrated_from_pvalue() { + // 50/50 → 40/60 gives χ²=4.0, p≈0.0455 (flagged); confidence is the + // unified undercut mapping of (p vs alpha), ≈0.549 here — distinguishable + // from the old 1−p value.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ax-detect/src/dist.rs` around lines 731 - 753, Rename and update the test chi2_detector_confidence_is_one_minus_pvalue to reflect calibrated behavior (e.g., chi2_detector_confidence_is_calibrated_from_pvalue), update the inline comment to describe that χ²=4.0 ⇒ p≈0.0455 and with dist_alpha=0.05 the confidence is calibrated via calibrate::from_undercut(p, dist_alpha) (≈0.549), and replace the weak assertion (out.findings[0].confidence < 0.99) with a tighter check that the reported confidence from Chi2Detector (invoked via Chi2Detector.detect with ScanContext::compared and DetectConfig::default) approximately equals the calibrated value (use a small epsilon tolerance); keep the existing assertion that out.absent.is_empty().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/ax-detect/src/dist.rs`:
- Around line 731-753: Rename and update the test
chi2_detector_confidence_is_one_minus_pvalue to reflect calibrated behavior
(e.g., chi2_detector_confidence_is_calibrated_from_pvalue), update the inline
comment to describe that χ²=4.0 ⇒ p≈0.0455 and with dist_alpha=0.05 the
confidence is calibrated via calibrate::from_undercut(p, dist_alpha) (≈0.549),
and replace the weak assertion (out.findings[0].confidence < 0.99) with a
tighter check that the reported confidence from Chi2Detector (invoked via
Chi2Detector.detect with ScanContext::compared and DetectConfig::default)
approximately equals the calibrated value (use a small epsilon tolerance); keep
the existing assertion that out.absent.is_empty().
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ba71cc34-4141-4bc2-b6d1-912d85c1ccaa
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
CHANGELOG.mdCargo.tomlcrates/ax-detect/src/cadence.rscrates/ax-detect/src/calibrate.rscrates/ax-detect/src/coll.rscrates/ax-detect/src/config.rscrates/ax-detect/src/ctx.rscrates/ax-detect/src/dist.rscrates/ax-detect/src/lib.rscrates/ax-detect/src/mv.rscrates/ax-detect/src/point.rscrates/ax-detect/src/robustz.rscrates/ax-detect/src/structural.rsdocs/src/envelope.md
💤 Files with no reviewable changes (1)
- crates/ax-detect/src/robustz.rs
What
Confidence was computed three incompatible ways —
1−p(dist/mv), logistic-over-threshold (point/ctx/coll/psi), linear (cadence) — so a0.9meant different things per detector andseverity/--top/--min-severitycouldn't rank across detectors. Now every detector routes through one sharedax_detect::calibrate:from_exceedance(stat, threshold)— "fires above" (modified z, CUSUM shift, PSI, null-rate)from_undercut(stat, threshold)— "fires below" (p-value < alpha, CV < max, point FDR's BH cutoff)0.5at the threshold, rising toward1.0; a finding "2× past threshold" earns the same confidence on any detector (unit test pins this exactly). Removed the duplicated/divergentshift_confidence,psi_confidence,robustz::confidencehelpers.Why it's better
The old
1−psaturated to ~1 (everything "critical"); the logistic gives a real gradient and comparability, so severity ranking across detector families is now meaningful — which is what makes the 0.6.0--top/--min-severitytrustworthy.Impact / contract
Recalibrates every published confidence and severity.
config_versionbumpedanomalyx-cfg/7 → /8so agents see it. Envelope shape andPROTOCOLunchanged. Structural schema-diff findings keep their fixed priors (binary facts, not statistics); the null-rate finding joins the unified scale.Gate
proptest+cargo-mutants0 missed:calibrate.rs32/32 caught; touched detectorspoint/ctx/coll/cadence/structural/config/robustz178 caught/6 unviable;dist/mv193 caught/5 unviable + 10 loop-bound timeouts (detected, per the gate rule).calibratehas exact at-threshold, monotonicity, 2×-comparability, and boundary/NaN tests.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Changed
Documentation