"""Wilson score interval for a binomial rate (L3-1).

Returns (low, high) as fractions in [0, 1], not percents.
Do not write these bounds onto failures.json — they live in wilson_fail.json.
"""

from __future__ import annotations

import math


def wilson_interval(successes: int, n: int, z: float = 1.96) -> tuple[float, float]:
    """Wilson score interval for `successes` in `n` trials.

    ``z=1.96`` is the two-sided 95% normal quantile. Bounds are fractions.
    """
    n = int(n)
    if n < 1:
        raise ValueError("n must be a positive integer")
    if z <= 0:
        raise ValueError("z must be positive")
    if successes < 0 or successes > n:
        raise ValueError("successes must be in [0, n]")

    p = successes / n
    z2 = z * z
    denom = 1.0 + z2 / n
    centre = (p + z2 / (2.0 * n)) / denom
    margin = (z * math.sqrt(p * (1.0 - p) / n + z2 / (4.0 * n * n))) / denom
    low = max(0.0, centre - margin)
    high = min(1.0, centre + margin)
    return (low, high)
