fix(rank): round weighted scores without binary drift (#588)

* fix(rank): round weighted scores without binary drift

* docs(changelog): record fix for #588

* changelog: place the #588 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-21 07:25:26 +02:00
1 parent 6fcf0f045f
commit 934a5f6fea
3 files changed
+62 -3

No files matched your search

+7 -3
View File
@@ -45,6 +45,7 @@ import sys
import tempfile
import unicodedata
from datetime import date, datetime, timedelta, timezone
from fractions import Fraction
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
@@ -282,15 +283,18 @@ def cmd_sweep(args) -> int:
def overall_score(scores: dict) -> int:
if not isinstance(scores, dict):
raise ValueError("scores must be an object")
total = 0.0
total = Fraction(0)
for dim, weight in WEIGHTS.items():
value = scores.get(dim)
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"missing or non-numeric score '{dim}'")
if not 0 <= value <= 100:
raise ValueError(f"score '{dim}' must be finite and between 0 and 100")
total += float(value) * weight
return int(total + 0.5)
# Scores and documented percentage weights are decimal quantities.
# Binary float summation can turn 59.5 into 59.49999999999999 and drop
# an otherwise valid result into the lower verdict band.
total += Fraction(str(value)) * Fraction(str(weight))
return int(total + Fraction(1, 2))
def band(score: int) -> str: