"""
Practice: the decisions inside Capstone B's release pipeline (pipeline.yaml, stages.py).

Fill in the four TODOs, then run the tests:
    pytest test_release_practice.py        (locally)
or press "Run tests" on the chapter page.
"""


def signal_alarm(stable: float, canary: float, z: float, margin: float,
                 z_line: float = 3.5, relative: bool = False, both_ways: bool = False) -> bool:
    """One reference-free canary signal, canary vs stable, in one segment.

    It alarms only if BOTH hold:
      - the change is bigger than `margin`: canary - stable > margin, or, if `relative`,
        canary / stable - 1 > margin (if stable is 0, a relative change can't be computed: False)
      - the evidence is strong: z > z_line
    With `both_ways`, a change in either direction counts: compare the absolute change with
    the margin, and |z| with z_line.

    The margin ignores differences too small to matter; the strict z keeps repeated checks
    from turning noise into alarms.

    TODO 1
    """
    return False


def probe_alarms(stable_wer: float, canary_wer: float, must_pass_failures: list[str],
                 margin: float = 2.0) -> list[str]:
    """Known-answer probes, sent through BOTH versions at every check. Return, in this order:
      - "probe WER" if canary_wer > stable_wer + margin
      - "must-pass" if any must-pass phrase failed on the canary

    TODO 2
    """
    return []


def canary_decision(alarms: list[str], per_segment: dict[str, int], requests: int,
                    min_per_segment: int = 40, max_requests: int = 900) -> str:
    """The decision after one canary check.
      - "rollback" if any alarm fired (the first alarm is enough)
      - "promote"  if every segment has at least min_per_segment canary requests
      - "rollback" if `requests` (all traffic so far) reached max_requests: no evidence, no release
      - otherwise "continue"

    TODO 3
    """
    return "continue"


def smallest_detectable(half_width: float) -> float:
    """The smallest change a paired comparison reliably detects (5% two-sided, 80% power):
    2.8 standard errors, where the standard error is the 95% half-width / 1.96.

    TODO 4a
    """
    return 0.0


def clips_needed(clips: int, detectable: float, tolerance: float) -> float:
    """The detectable change shrinks with the square root of the number of clips. How many
    clips would make `detectable` as small as `tolerance`?  clips * (detectable / tolerance)²

    TODO 4b
    """
    return 0.0
