"""安全段位 — the canonical security-quiz rank ladder. A single source of truth for the tier thresholds (by lifetime correct answers) shared by the personal rank chip on the 装备墙 (me_universe loadout) and the 段位擂台 page. Tiers are cumulative; ``rank_for`` returns the highest tier reached and ``progress`` adds the distance to the next. Pure functions, no DB — trivially unit-testable and import-cheap. """ from __future__ import annotations from typing import Any # (min_correct, key) ascending. ``unranked`` is the implicit floor (0). RANK_LADDER: list[tuple[int, str]] = [ (1, "bronze"), (5, "silver"), (15, "gold"), (30, "platinum"), (60, "diamond"), (100, "master"), ] # Tier order incl. the unranked floor — index drives the progress bar. TIER_ORDER: list[str] = ["unranked", "bronze", "silver", "gold", "platinum", "diamond", "master"] def rank_for(total_correct: int) -> str: """Highest tier reached for ``total_correct`` lifetime correct answers.""" tier = "unranked" for threshold, key in RANK_LADDER: if total_correct >= threshold: tier = key else: break return tier def progress(total_correct: int) -> dict[str, Any]: """Rank + progress toward the next tier. Returns ``tier``, ``tier_index``, ``next_tier`` (None at master), ``next_threshold``, ``current`` (correct so far), and ``pct_to_next`` (0–100, the fill of the progress bar to the next tier; 100 at master). """ correct = max(0, int(total_correct)) tier = rank_for(correct) idx = TIER_ORDER.index(tier) # Lower bound of the current tier (0 for unranked) and the next threshold. lower = 0 if tier == "unranked" else next(t for t, k in RANK_LADDER if k == tier) next_tier: str | None = None next_threshold: int | None = None if idx + 1 < len(TIER_ORDER): next_tier = TIER_ORDER[idx + 1] next_threshold = next(t for t, k in RANK_LADDER if k == next_tier) if next_threshold is None: # master — maxed out 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 { "tier": tier, "tier_index": idx, "next_tier": next_tier, "next_threshold": next_threshold, "current": correct, "pct_to_next": pct, } __all__ = ["RANK_LADDER", "TIER_ORDER", "rank_for", "progress"]