"""Pure score arithmetic. No model types, game rules, I/O, or policy thresholds. Invalid or incomplete inputs raise ValueError. Callers own evidence checks and fallbacks; missing evidence must never become a neutral score. """ from __future__ import annotations import math from collections.abc import Mapping def _finite(value: float) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value) def normalize_score(value: float, minimum: float, neutral: float, maximum: float) -> float: """Map an ordered scale to [-1, 1], with neutral at zero. Each side is linear. Equal score intervals are a modeling assumption, not a calibrated measure of game value. Values outside the scale are rejected. """ if not all(_finite(v) for v in (value, minimum, neutral, maximum)): raise ValueError("score and scale must be finite numbers") if not minimum < neutral < maximum: raise ValueError("scale must satisfy minimum < neutral < maximum") if not minimum <= value <= maximum: raise ValueError("score is outside the scale") span = maximum - neutral if value >= neutral else neutral - minimum return (value - neutral) / span def weighted_utility(components: Mapping[str, float], weights: Mapping[str, float]) -> float: """Combine complete normalized components using explicit nonnegative weights. Weights must sum to one. Do not silently remove missing components or renormalize weights. Confidence is not a component or a utility multiplier. """ if not weights or components.keys() != weights.keys(): raise ValueError("components must exactly match nonempty weights") if any(not _finite(w) or w < 0 for w in weights.values()): raise ValueError("weights must be finite and nonnegative") if not math.isclose(sum(weights.values()), 1.0, rel_tol=0, abs_tol=1e-9): raise ValueError("weights must sum to one") if any(not _finite(v) or not -1 <= v <= 1 for v in components.values()): raise ValueError("components must be finite normalized scores") return sum(weights[axis] * components[axis] for axis in weights) def rank_candidates(utilities: Mapping[str, float]) -> list[str]: """Rank highest utility first; ties retain input order. Empty input is valid.""" if any(not _finite(value) for value in utilities.values()): raise ValueError("candidate utilities must be finite numbers") return sorted(utilities, key=utilities.__getitem__, reverse=True)