"""
Practice: handle an AI incident the way Lab 17's scripts 04 and 05 do. Rate its severity from
facts with the policy's table, score two rating methods against the policy, start the
notification clocks, and turn a vendor's questionnaire answers into a decision.

Solution.
"""
from datetime import datetime, timedelta

RANK = {"P1": 1, "P2": 2, "P3": 3, "P4": 4}


def severity(f: dict) -> str:
    """The company's severity policy, applied to an incident's facts (a dict with the keys
    physical_harm, discrimination, personal_data_exposed, special_or_financial_data,
    people_affected, money_usd, wrong_commitment, public, reached_users):
      P1  physical_harm, or discrimination, or special_or_financial_data, or more than 10,000 people
      P2  personal_data_exposed, or more than $10,000, or wrong_commitment, or public,
          or more than 1,000 people
      P3  reached_users, or any money lost
      P4  anything else (nothing reached users)
    The first level whose condition holds wins."""
    if f["physical_harm"] or f["discrimination"] or f["special_or_financial_data"] or f["people_affected"] > 10_000:
        return "P1"
    if (f["personal_data_exposed"] or f["money_usd"] > 10_000 or f["wrong_commitment"] or f["public"]
            or f["people_affected"] > 1_000):
        return "P2"
    if f["reached_users"] or f["money_usd"] > 0:
        return "P3"
    return "P4"


def score(ratings: list[str], gold: list[str]) -> dict:
    """ratings and gold: one severity per report, same order. Count:
      right     ratings equal to the policy's level
      too_low   ratings less severe than the policy's (P2 for a P1): the people who must know don't
      too_high  ratings more severe than the policy's (P1 for a P2): an extra phone call
      p1_found  P1 reports rated P1"""
    pairs = list(zip(ratings, gold))
    return {
        "right": sum(a == b for a, b in pairs),
        "too_low": sum(RANK[a] > RANK[b] for a, b in pairs),
        "too_high": sum(RANK[a] < RANK[b] for a, b in pairs),
        "p1_found": sum(a == "P1" and b == "P1" for a, b in pairs),
    }


def changed_across_tries(runs: list[list[str]]) -> int:
    """runs: the same reports rated on several tries (one list per try). How many reports got
    more than one different rating across the tries?"""
    return sum(len(set(r)) > 1 for r in zip(*runs))


def add_business_days(d: datetime, n: int) -> datetime:
    """n business days after d: step one day at a time, counting only Monday to Friday.
    The time of day stays the same. Public holidays aren't modeled."""
    while n:
        d += timedelta(days=1)
        if d.weekday() < 5:
            n -= 1
    return d


def clocks(aware: datetime) -> dict:
    """A breach of EU customers' personal data at a US-listed company, decided material when
    the company became aware. Return:
      gdpr  the EU supervisory authority: 72 hours after aware, weekend included
      sec   the SEC's Form 8-K: 4 business days after aware"""
    return {"gdpr": aware + timedelta(hours=72), "sec": add_business_days(aware, 4)}


def decide(answers: dict, rules: dict) -> tuple[str, list[str], list[str]]:
    """answers: question -> the vendor's answer. rules: question -> {answer: "blocker" | "condition"};
    an answer not listed is fine. Return (decision, blockers, conditions), with the questions in
    the answers' order:
      "reject" if any blocker, else "approve with conditions" if any condition, else "approve"."""
    blockers = [q for q, a in answers.items() if rules[q].get(a) == "blocker"]
    conditions = [q for q, a in answers.items() if rules[q].get(a) == "condition"]
    return ("reject" if blockers else "approve with conditions" if conditions else "approve"), blockers, conditions
