fix(ndcg): correct the DCG discount off-by-one (rank 2 no longer scores as rank 1)#229
Conversation
`_dcg` discounted by `log2(i + 1)` over a 0-indexed `i` with an `i == 0` special case, so rank 2 (`i == 1`) received discount `log2(2) = 1.0` — identical to rank 1, i.e. no position penalty. Every position was one slot too generous and NDCG failed to distinguish ranks 1 and 2. Discount by the standard `log2(rank + 1)` with 1-indexed rank, i.e. `log2(i + 2)` here. This yields `log2(2) = 1.0` at rank 1 (no special case needed) and a strictly increasing discount thereafter. IDCG normalization uses the same `_dcg`, so a perfectly ranked result still scores exactly 1.0. Regression: a single relevant doc among 3 (k=3) now scores 1.0 / 0.6309 / 0.5 by rank (strictly decreasing) instead of 1.0 / 1.0 / 0.6309. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
/oc review pr |
|
Review: Approved. ✅ Bug
Code quality
Nit (optional)Line 29: Ship it. |
|
@Nitjsefnie Nit (optional) plz fix it |
|
/oc work on Nit (optional) |
|
remote: Permission to Nitjsefnie-OSC/openagent-eval.git denied to opencode-agent[bot]. |
|
🎉 Congratulations @Nitjsefnie! Your pull request has been successfully merged into main. 🚀 Thank you for contributing to OpenAgentHQ and helping improve the project. We truly appreciate your contribution and hope to see you back with more amazing PRs! Happy Open Sourcing! ❤️ |

Fixes #223
_dcg's discount waslog2(i + 1)with 0-indexediand ani == 0special case, giving rank 2 the same discount as rank 1. Now the standardlog2(rank + 1)— i.e.rel / _log2(i + 2), no special case needed sincelog2(2) = 1.Hand-derived expectations locked by new tests (single relevant doc of 3, k=3; IDCG = 1.0 so NDCG = the doc's discount): rank 1 → 1.0, rank 2 → 1/log2(3) = 0.6309…, rank 3 → 1/log2(4) = 0.5, strictly decreasing; plus a perfect-ranking test proving IDCG normalization still yields exactly 1.0. Both value tests fail on current main (rank 2 returns 1.0; the monotonicity test hits
assert 1.0 > 1.0) and pass with the fix.One honest note: none of the four existing NDCG tests encoded the shifted values (they assert 1.0/0.0/open ranges that hold under both formulas — which is exactly how the bug survived), so no existing expectations changed; the full suite is green untouched: 962 passed, 4 skipped. The few ruff findings in
ndcg.py/test_retrieval.pyare pre-existing on main (verified) — new code adds none.Generated by Claude Fable 5 (brief, review), Claude Opus 4.8 (implementation)