"""
Practice: alerts, scored (solution). Lab 14's script 04 (04_alerts.py) and alerts.py.
"""
import math

MIN_N = 5  # don't judge an hour with fewer requests


def over_threshold(k, n, threshold=0.30, min_n=MIN_N):
    """The threshold rule: more than `threshold` of the hour's answers were "I don't know"."""
    if n < min_n:
        return False
    return k / n > threshold


def binom_sf(k, n, p):
    """P(X >= k) for X ~ Binomial(n, p)."""
    if k <= 0:
        return 1.0
    return min(1.0, sum(math.comb(n, i) * p**i * (1 - p) ** (n - i) for i in range(k, n + 1)))


def above_baseline(k, n, base, alpha=0.001, min_n=MIN_N):
    """The baseline rule: this many, this unlikely by chance at the normal rate."""
    if n < min_n:
        return False
    return binom_sf(k, n, base) < alpha


def bar(n, base, alpha=0.001):
    """The smallest k out of n that the baseline rule alerts on."""
    for k in range(n + 1):
        if binom_sf(k, n, base) < alpha:
            return k
    return n + 1


def fired(hours, rule):
    """The hours where rule(k, n) is True."""
    return [h for h, n, k in hours if rule(k, n)]


def score(alerts, incident, size=1):
    """(minutes to the first alert inside the incident, alerts outside it)."""
    start, end = incident
    inside = [w for w in alerts if w * size < end and (w + 1) * size > start]
    delay = ((inside[0] + 1) * size - start) * 60 if inside else None
    return (delay, len(alerts) - len(inside))


def by_day(hours, per_day=24):
    """Hourly counts added up into days."""
    days = {}
    for h, n, k in hours:
        d = days.setdefault(h // per_day, [0, 0])
        d[0] += n
        d[1] += k
    return [(d, n, k) for d, (n, k) in sorted(days.items())]
