"""跨业务模块复用的排名归一化工具。""" from __future__ import annotations from collections.abc import Hashable from typing import TypeVar KeyT = TypeVar("KeyT", bound=Hashable) def rank_with_scores(items: list[tuple[KeyT, float]]) -> dict[KeyT, dict[str, float | int]]: """按值降序排名,并返回同分平均名次与 ``(0, 1]`` 排名分。 不同量纲的数据应先分别调用本函数,不能先把原始值相加。排名分公式与 ``category_tree_weight`` 的四维全局排名口径一致: ``score = (n - avg_rank + 1) / n``。 """ if not items: return {} sorted_items = sorted(items, key=lambda item: (-item[1], str(item[0]))) total = len(sorted_items) result: dict[KeyT, dict[str, float | int]] = {} start = 0 while start < total: end = start value = sorted_items[start][1] while end < total and sorted_items[end][1] == value: end += 1 average_rank = (start + 1 + end) / 2.0 normalized_score = (total - average_rank + 1) / total for index in range(start, end): result[sorted_items[index][0]] = { "rank": average_rank, "total": total, "normalized_score": normalized_score, } start = end return result def rank_to_scores(items: list[tuple[KeyT, float]]) -> dict[KeyT, float]: """兼容原调用方:只返回排名归一分。""" return { key: float(position["normalized_score"]) for key, position in rank_with_scores(items).items() }