"""科举 title ladder — the "how knowledgeable are you" rank for 知识竞速. A single source of truth for the imperial-examination title tiers, by lifetime correct trivia answers. Directly answers the user's ask ("谁知识 最渊博") with culturally-themed titles (白丁 → 状元). Pure functions, no DB — trivially unit-testable, shared by the leaderboard service and the arcade home. Per-category "达人" champion titles (历史达人 …) are NOT here — those are live rank-1 standings computed by the leaderboard service, not a ladder. """ from __future__ import annotations from typing import Any # (min_correct, key) ascending. ``baiding`` (白丁 — commoner) is the floor. TITLE_LADDER: list[tuple[int, str]] = [ (0, "baiding"), # 白丁 (10, "xuetong"), # 学童 (30, "xiucai"), # 秀才 (60, "juren"), # 举人 (120, "gongshi"), # 贡士 (200, "jinshi"), # 进士 (320, "tanhua"), # 探花 (480, "bangyan"), # 榜眼 (680, "zhuangyuan"), # 状元 ] TITLE_ORDER: list[str] = [k for _, k in TITLE_LADDER] def title_for(total_correct: int) -> str: """Highest title reached for ``total_correct`` lifetime correct answers.""" correct = max(0, int(total_correct)) key = TITLE_ORDER[0] for threshold, k in TITLE_LADDER: if correct >= threshold: key = k else: break return key def progress(total_correct: int) -> dict[str, Any]: """Title + progress toward the next tier (mirrors security_quiz.ranks).""" correct = max(0, int(total_correct)) key = title_for(correct) idx = TITLE_ORDER.index(key) lower = next(t for t, k in TITLE_LADDER if k == key) next_key: str | None = None next_threshold: int | None = None if idx + 1 < len(TITLE_ORDER): next_key = TITLE_ORDER[idx + 1] next_threshold = next(t for t, k in TITLE_LADDER if k == next_key) if next_threshold is None: # 状元 — maxed pct = 100.0 else: span = max(1, next_threshold - lower) pct = round(100.0 * (correct - lower) / span, 1) pct = max(0.0, min(100.0, pct)) return { "title": key, "title_index": idx, "next_title": next_key, "next_threshold": next_threshold, "current": correct, "pct_to_next": pct, } __all__ = ["TITLE_LADDER", "TITLE_ORDER", "title_for", "progress"]