"""Canonical score version, brackets, and assessment semantics."""

SCORE_VERSION = "accumulation-score-v2"

# Half-open intervals [low, high), except the final bracket includes 100.
# Keep labels canonical because they are persisted in live and backtest output.
SCORE_BRACKETS = [
    (0, 20, "EXTREME CAUTION"),
    (20, 35, "CAUTION — OVERHEATED"),
    (35, 50, "NEUTRAL"),
    (50, 65, "MODERATE OPPORTUNITY"),
    (65, 80, "STRONG ACCUMULATION ZONE"),
    (80, 100, "EXTREME ACCUMULATION ZONE"),
]


def score_in_bracket(score, bracket):
    """Return whether a 0-100 score belongs to a canonical bracket."""
    low, high, _ = bracket
    if not 0 <= score <= 100:
        return False
    return low <= score < high or (high == 100 and score == 100)


def bracket_for_score(score):
    """Return the one canonical bracket for a 0-100 score."""
    for bracket in SCORE_BRACKETS:
        if score_in_bracket(score, bracket):
            return bracket
    raise ValueError(f"score must be between 0 and 100, got {score!r}")


def assessment_for_score(score):
    """Return the canonical assessment label for a score."""
    return bracket_for_score(score)[2]
