"""Tests on Lab 17's script 01, from its recorded run (October 2026): 16 systems, the hand-checked
answer key (55 system-model pairs), and what each of the three sources listed. Run:
    pytest test_inventory_practice.py"""
from inventory_practice import is_dated, left_for_people, scan, score, systems_using, totals

# The answer key: every model each system's code calls, checked by hand (after fixing tts-1).
KEY = {
    'support sorter': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'internal Q&A': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'FAQ search': ['text-embedding-3-small'],
    'help assistant': ['cross-encoder/ms-marco-MiniLM-L-6-v2', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'answer grader': ['gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'chat with memory': ['gpt-5.4-nano', 'text-embedding-3-small'],
    'employee assistant': ['gpt-5.4-nano'],
    'voice assistant': ['gpt-4o-mini-transcribe', 'gpt-4o-mini-tts', 'gpt-4o-transcribe', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-realtime-mini', 'openai/clip-vit-base-patch32', 'text-embedding-3-small', 'tts-1', 'whisper-1'],
    'refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'support agent team': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'ticket triage': ['Qwen/Qwen3-0.6B', 'Qwen/Qwen3-1.7B', 'gpt-5.4-mini', 'gpt-5.4-nano'],
    'self-hosted models': ['Qwen/Qwen3-0.6B', 'Qwen/Qwen3-1.7B', 'Qwen/Qwen3-4B', 'Qwen/Qwen3-8B', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'hardened refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'docs assistant': ['gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'email assistant': ['gpt-4o-mini', 'gpt-5.4-mini', 'gpt-5.4-nano', 'protectai/deberta-v3-base-prompt-injection-v2', 'text-embedding-3-small'],
    'HR documents assistant': ['gpt-4o-mini', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
}

# Source 1: what gpt-5.4-mini listed after reading each system's README.
DOCS = {
    'support sorter': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'internal Q&A': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'FAQ search': ['text-embedding-3-small'],
    'help assistant': ['gpt-5.4-nano', 'gpt-5.4-mini', 'text-embedding-3-small'],
    'answer grader': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'chat with memory': ['gpt-5.4-nano', 'text-embedding-3-small', 'gpt-5.4-mini', 'gpt-4o-mini'],
    'employee assistant': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'voice assistant': ['gpt-5.4-nano', 'gpt-5.4-mini', 'text-embedding-3-small', 'openai/clip-vit-base-patch32', 'whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe', 'gpt-4o-mini-tts', 'tts-1', 'gpt-realtime-mini'],
    'refund agent': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'support agent team': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'ticket triage': ['gpt-5.4-nano', 'gpt-5.4-mini', 'Qwen3-0.6B', 'Qwen3-1.7B'],
    'self-hosted models': ['gpt-5.4-nano', 'gpt-5.4-mini', 'Qwen3-0.6B', 'Qwen3-1.7B', 'Qwen3-4B', 'Qwen3-8B'],
    'hardened refund agent': ['gpt-5.4-nano', 'gpt-5.4-mini'],
    'docs assistant': ['gpt-5.4-nano', 'gpt-5.4-mini', 'text-embedding-3-small'],
    'email assistant': ['gpt-5.4-nano', 'gpt-4o-mini', 'gpt-5.4-mini', 'text-embedding-3-small', 'protectai/deberta-v3-base-prompt-injection-v2'],
    'HR documents assistant': ['gpt-5.4-nano', 'gpt-5.4-mini', 'gpt-4o-mini', 'text-embedding-3-small'],
}

# Source 2: the code scanner's matches.
CODE = {
    'support sorter': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'internal Q&A': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'FAQ search': ['text-embedding-3-small'],
    'help assistant': ['cross-encoder/ms-marco-MiniLM-L-6-v2', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'answer grader': ['gpt-5', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'chat with memory': ['gpt-5.4-nano', 'text-embedding-3-small'],
    'employee assistant': ['gpt-5.4-nano'],
    'voice assistant': ['gpt-4o-mini-transcribe', 'gpt-4o-mini-tts', 'gpt-4o-transcribe', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-realtime-mini', 'text-embedding-3-small', 'tts-1', 'tts_first_hedged', 'tts_first_stream', 'tts_whole_file', 'whisper-1'],
    'refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'support agent team': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'ticket triage': ['Qwen/Qwen3-0.6B', 'Qwen/Qwen3-1.7B', 'gpt-5.4-mini', 'gpt-5.4-nano'],
    'self-hosted models': ['Qwen/Qwen3-0.6B', 'Qwen/Qwen3-1.7B', 'Qwen/Qwen3-4B', 'Qwen/Qwen3-8B', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'hardened refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'docs assistant': ['gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
    'email assistant': ['gpt-4o-mini', 'gpt-5', 'gpt-5.4-mini', 'gpt-5.4-nano', 'protectai/deberta-v3-base-prompt-injection-v2', 'text-embedding-3-small'],
    'HR documents assistant': ['gpt-4o', 'gpt-4o-mini', 'gpt-5.4-mini', 'gpt-5.4-nano', 'text-embedding-3-small'],
}

# Source 3: models named in saved answers and run logs. None: the system keeps no such record.
RUNTIME = {
    'support sorter': None,
    'internal Q&A': None,
    'FAQ search': None,
    'help assistant': None,
    'answer grader': None,
    'chat with memory': None,
    'employee assistant': None,
    'voice assistant': None,
    'refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'support agent team': ['gpt-5.4-nano'],
    'ticket triage': None,
    'self-hosted models': None,
    'hardened refund agent': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'docs assistant': ['gpt-5.4-mini', 'gpt-5.4-nano'],
    'email assistant': ['gpt-4o-mini', 'gpt-5.4-mini', 'gpt-5.4-nano'],
    'HR documents assistant': ['gpt-4o-mini', 'gpt-5.4-mini', 'gpt-5.4-nano'],
}


def test_scan_finds_models_and_look_alikes():
    code = """
MODEL = "gpt-5.4-nano"
EMBED = 'text-embedding-3-small'
variants = ["tts_whole_file", "tts_first_stream"]   # timing-test labels, not models
if name.startswith("gpt-5"):                         # a check, not a call
    pass
reranker = "cross-encoder/ms-marco-MiniLM-L-6-v2"
clip = "openai/clip-vit-base-patch32"                # no pattern fits
"""
    assert scan(code) == {"gpt-5.4-nano", "text-embedding-3-small", "tts_whole_file", "tts_first_stream",
                          "gpt-5", "cross-encoder/ms-marco-MiniLM-L-6-v2"}


def test_score_one_system():
    # The voice assistant: the scanner found 9 of its 10 models, missed CLIP, added three labels.
    s = score(CODE["voice assistant"], KEY["voice assistant"])
    assert s == {"found": 9, "wrong": ["tts_first_hedged", "tts_first_stream", "tts_whole_file"],
                 "missed": ["openai/clip-vit-base-patch32"]}
    # Names compare like a reviewer would: "Qwen3-0.6B" in a README is "Qwen/Qwen3-0.6B" in the code.
    assert score(DOCS["ticket triage"], KEY["ticket triage"])["found"] == 4
    # No record at all: everything missed.
    assert score(None, KEY["support sorter"]) == {"found": 0, "wrong": [], "missed": ["gpt-5.4-mini", "gpt-5.4-nano"]}


def test_answer_key_size():
    assert len(KEY) == 16 and sum(len(v) for v in KEY.values()) == 55


def test_three_sources_scored():
    assert totals(DOCS, KEY) == {"found": 51, "wrong": 3, "missed": 4}
    assert totals(CODE, KEY) == {"found": 54, "wrong": 6, "missed": 1}
    assert totals(RUNTIME, KEY) == {"found": 13, "wrong": 0, "missed": 42}


def test_which_systems_use_a_retiring_model():
    both = ["HR documents assistant", "email assistant"]
    assert systems_using(CODE, "gpt-4o-mini") == both
    assert systems_using(RUNTIME, "gpt-4o-mini") == both
    # The README reading adds one that only mentions it.
    assert systems_using(DOCS, "gpt-4o-mini") == ["HR documents assistant", "chat with memory", "email assistant"]
    # No record names an embedding call.
    assert systems_using(RUNTIME, "text-embedding-3-small") == []


def test_dated_versions():
    assert is_dated("gpt-5.4-nano-2026-03-17")
    assert is_dated("gpt-4o-mini-2024-07-18")
    assert not is_dated("gpt-5.4-nano")
    assert not is_dated("Qwen/Qwen3-0.6B")


def test_registry_fields_left_for_people():
    # The email assistant's drafted card (trimmed): the AI filled 7 fields, the prompt left 5 empty.
    entry = {"purpose": "Measures what guardrails actually stop and what they cost ...",
             "users": "Lab users / practitioners running the scripts in this lab",
             "data_in": ["emails", "calendar data", "cloud drive data"], "data_sensitivity": "personal",
             "affects_people": False, "acts": False,
             "known_weaknesses": ["An attack set that already fails without any guard cannot rank guards."],
             "owner": None, "business_unit": None, "risk_level": None, "last_review": None, "next_review": None}
    assert left_for_people(entry) == ["business_unit", "last_review", "next_review", "owner", "risk_level"]
