#!/usr/bin/env python3
"""Create deterministic, mechanically scored fixtures for the Kingy.ai 24GB test."""

from __future__ import annotations

import json
import random
import textwrap
from pathlib import Path

SEED = 20260816
ROOT = Path(__file__).resolve().parent / "fixtures"


def dump(path: Path, value: object) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def instructions() -> list[dict]:
    out = []
    groups = [
        ["cobalt", "amber", "birch", "delta"],
        ["quartz", "ember", "atlas", "violet"],
        ["cedar", "maple", "ash", "willow"],
        ["north", "east", "south", "west"],
        ["tensor", "cache", "token", "kernel"],
    ]
    for i, words in enumerate(groups, 1):
        out.append({"id": f"if_sort_{i:02d}", "prompt": f"Sort alphabetically and output only one line joined by |: {', '.join(words)}", "check": {"type": "exact", "value": " | ".join(sorted(words))}})
        out.append({"id": f"if_reverse_{i:02d}", "prompt": f"Reverse the word order. Output only the words separated by one space: {' '.join(words)}", "check": {"type": "exact", "value": " ".join(reversed(words))}})
    rows = [("Ada", 17, "blue"), ("Lin", 23, "green"), ("Mira", 31, "amber"), ("Oren", 19, "silver"), ("Zia", 29, "violet")]
    for i, (name, score, color) in enumerate(rows, 1):
        out.append({"id": f"if_json_{i:02d}", "prompt": f"Return one compact JSON object with keys name, score, color in that order. name={name}; score={score} as a number; color={color}. No markdown.", "check": {"type": "json_exact", "value": {"name": name, "score": score, "color": color}}})
        out.append({"id": f"if_csv_{i:02d}", "prompt": f"Output exactly two CSV lines. Header name,score,color. Data {name},{score},{color}. No code fence.", "check": {"type": "exact", "value": f"name,score,color\n{name},{score},{color}"}})
    facts = [
        ("The sensor sampled every 8 seconds. It logged 45 readings. No readings were missing.", 16, ["8", "45", "No"]),
        ("The build used CUDA 11.8. It completed in 6 minutes. All 14 checks passed.", 17, ["11.8", "6", "14"]),
        ("Mira shipped 12 crates on Tuesday. Each crate weighed 7 kg. None was damaged.", 17, ["12", "Tuesday", "7"]),
        ("The archive contains 24 files. The checksum is valid. Retrieval took 3 seconds.", 15, ["24", "valid", "3"]),
        ("Aster has 9 nodes across 3 zones. Quorum requires 5 nodes. The test passed.", 16, ["9", "3", "5"]),
    ]
    for i, (source, limit, must) in enumerate(facts, 1):
        out.append({"id": f"if_compress_{i:02d}", "prompt": f"Compress to at most {limit} words while preserving every fact: {source}", "check": {"type": "word_limit_contains", "max_words": limit, "contains": must}})
        out.append({"id": f"if_bullets_{i:02d}", "prompt": f"Rewrite as exactly three bullet lines beginning with '- '. Preserve every fact: {source}", "check": {"type": "bullet_count_contains", "count": 3, "contains": must}})
    logic = [
        ("A=true, B=false. Output YES only if A and not B; otherwise NO.", "YES"),
        ("x=14. Output EVEN if x is divisible by 2, otherwise ODD.", "EVEN"),
        ("p=9, q=12. Output P if p>q, Q if q>p, TIE otherwise.", "Q"),
        ("temperature=-3. Output FREEZE if below 0, otherwise THAW.", "FREEZE"),
        ("items=0. Output EMPTY if there are no items, otherwise NONEMPTY.", "EMPTY"),
        ("A=false, B=false. Output YES only if exactly one is true; otherwise NO.", "NO"),
        ("x=27. Output THREE if divisible by 3, otherwise OTHER.", "THREE"),
        ("p=41, q=41. Output P if p>q, Q if q>p, TIE otherwise.", "TIE"),
        ("temperature=0. Output FREEZE if below 0, otherwise THAW.", "THAW"),
        ("items=8. Output EMPTY if there are no items, otherwise NONEMPTY.", "NONEMPTY"),
    ]
    for i, (prompt, answer) in enumerate(logic, 1):
        out.append({"id": f"if_logic_{i:02d}", "prompt": prompt + " Output only that label.", "check": {"type": "exact", "value": answer}})
    extras = [
        ("Output these numbers in descending order, comma-separated only: 4, 19, 7, 12", "19, 12, 7, 4"),
        ("Output only the uppercase initials of: graphics processing unit", "GPU"),
        ("Replace each space with an underscore and output only the result: local model audit", "local_model_audit"),
        ("Output exactly the hexadecimal representation of decimal 255 with lowercase 0x prefix.", "0xff"),
        ("Output only the number of vowels in the lowercase word quantization.", "6"),
        ("Convert 2 hours 7 minutes to minutes. Output only the integer.", "127"),
        ("Output only the last three characters of benchmark.", "ark"),
        ("Deduplicate while preserving first appearance and join by comma: a,b,a,c,b,d", "a,b,c,d"),
        ("Output only the ISO date for August 16, 2026.", "2026-08-16"),
        ("Output exactly four characters: pad integer 37 with leading zeroes.", "0037"),
    ]
    for i, (prompt, answer) in enumerate(extras, 1):
        out.append({"id": f"if_extra_{i:02d}", "prompt": prompt, "check": {"type": "exact", "value": answer}})
    assert len(out) == 50
    return out


def reasoning() -> list[dict]:
    pairs = [
        ("A shop packs 17 boxes with 24 bolts each, then removes 39 defective bolts. How many remain?", "369"),
        ("A train covers 315 km in 5 equal legs. What is the distance per leg?", "63"),
        ("A cache has 8 shards with 125 entries each. It evicts 18% of all entries. How many remain?", "820"),
        ("A 480-page scan is 35% complete. How many pages remain?", "312"),
        ("Three jobs take 18, 27, and 45 minutes. If run sequentially from 09:10, when do they finish? Use HH:MM.", "10:40"),
        ("A model emits 42 tokens/s for 75 seconds, then 30 tokens/s for 25 seconds. Total tokens?", "3900"),
        ("A 24 GiB card reserves 1.5 GiB and loads 15.75 GiB of weights. How many GiB remain?", "6.75"),
        ("Four workers process 28 files each; 17 fail and are retried. How many distinct files were processed?", "112"),
        ("A price drops 20% from 85, then rises 25% from the reduced price. Final price?", "85"),
        ("A sequence starts at 11 and adds 3, 5, 7, and 9. Final value?", "35"),
        ("Solve for x: 7x - 19 = 86.", "15"),
        ("Solve for y: 3(y + 8) = 57.", "11"),
        ("If 2a + 3b = 31 and a = 5, find b.", "7"),
        ("The average of 14, 19, x, and 27 is 21. Find x.", "24"),
        ("A rectangle has perimeter 54 and length 17. Find its width.", "10"),
        ("A ratio is 5:8 and the smaller quantity is 35. Find the larger quantity.", "56"),
        ("Solve: x/4 + 9 = 16.", "28"),
        ("If f(n)=n^2-3n+4, find f(9).", "58"),
        ("A number increased by 40% equals 98. Find the original.", "70"),
        ("Two consecutive integers sum to 73. Larger integer?", "37"),
        ("Find the next term: 3, 8, 15, 24, 35, ?", "48"),
        ("Find the next term: 128, 64, 32, 16, ?", "8"),
        ("Find the missing term: 2, 6, 12, 20, ?, 42", "30"),
        ("Find the next letter: B, E, H, K, ?", "N"),
        ("A code maps CAT to 3-1-20. Encode DOG with hyphens.", "4-15-7"),
        ("Start facing north. Turn right, right, then left. Which direction?", "east"),
        ("Ana sits immediately left of Bo. Bo is in seat 4. Ana's seat?", "3"),
        ("Every red object is metal. No metal object is light. Can any red object be light? yes/no", "no"),
        ("If Monday is day 1, what weekday is day 45?", "Wednesday"),
        ("How many 1 bits are in 101101?", "4"),
        ("Exactly one of A or B is true. A is false. Is B true? true/false", "true"),
        ("All lorps are nims. Some nims are zets. Must some lorps be zets? yes/no", "no"),
        ("Kai is older than Mira. Mira is older than Sol. Who is youngest?", "Sol"),
        ("A key opens one of doors 1,2,3. Not 1 or 3. Which door?", "2"),
        ("There are 3 blue, 4 green, 5 red balls. Minimum blind draws to guarantee two same-color?", "4"),
        ("If P implies Q and Q is false, what must P be? true/false", "false"),
        ("A task runs every 6 hours from 02:00. Which run comes after 20:00? HH:MM", "02:00"),
        ("Noor finished ahead of Ivo but behind Mei. First to last, comma-separated.", "Mei, Noor, Ivo"),
        ("A fair die is rolled. Probability of a result greater than 4? Reduced fraction.", "1/3"),
        ("A 9 by 7 grid has how many cells?", "63"),
    ]
    assert len(pairs) == 40
    return [{"id": f"rs_{i:02d}", "prompt": p, "answer": a} for i, (p, a) in enumerate(pairs, 1)]


def tool_data() -> dict:
    fn = lambda name, desc, props, req: {"type": "function", "function": {"name": name, "description": desc, "parameters": {"type": "object", "properties": props, "required": req, "additionalProperties": False}}}
    sprop = lambda: {"type": "string"}
    nprop = lambda: {"type": "number"}
    iprop = lambda: {"type": "integer"}
    tools = [
        fn("lookup_price", "Get unit price for an SKU.", {"sku": sprop()}, ["sku"]),
        fn("calculate_total", "Calculate taxed quantity total.", {"unit_price": nprop(), "quantity": iprop(), "tax_rate": nprop()}, ["unit_price", "quantity", "tax_rate"]),
        fn("lookup_distance", "Get road distance in kilometres.", {"origin": sprop(), "destination": sprop()}, ["origin", "destination"]),
        fn("convert_distance", "Convert kilometres to miles.", {"km": nprop()}, ["km"]),
        fn("get_inventory", "Get available stock for an SKU.", {"sku": sprop()}, ["sku"]),
        fn("reserve_item", "Reserve stock.", {"sku": sprop(), "quantity": iprop()}, ["sku", "quantity"]),
        fn("lookup_rate", "Get currency exchange rate.", {"base": sprop(), "quote": sprop()}, ["base", "quote"]),
        fn("convert_currency", "Convert amount at supplied rate.", {"amount": nprop(), "rate": nprop()}, ["amount", "rate"]),
    ]
    singles = []
    skus = ["AX-17", "BR-04", "CZ-91", "DN-33", "EP-20"]
    for i, sku in enumerate(skus, 1):
        singles.append({"id": f"tool_price_{i}", "prompt": f"What is the unit price for {sku}? Use a tool.", "expected_tool": "lookup_price", "expected_args": {"sku": sku}})
    routes = [("Vancouver", "Victoria"), ("Calgary", "Banff"), ("Seattle", "Portland"), ("Toronto", "Ottawa"), ("Kelowna", "Kamloops")]
    for i, (a, b) in enumerate(routes, 1):
        singles.append({"id": f"tool_distance_{i}", "prompt": f"Look up road distance from {a} to {b}.", "expected_tool": "lookup_distance", "expected_args": {"origin": a, "destination": b}})
    for i, sku in enumerate(skus, 1):
        singles.append({"id": f"tool_stock_{i}", "prompt": f"Check available stock for {sku}.", "expected_tool": "get_inventory", "expected_args": {"sku": sku}})
    for i, (sku, qty) in enumerate(zip(skus, [2, 7, 1, 5, 3]), 1):
        singles.append({"id": f"tool_reserve_{i}", "prompt": f"Reserve {qty} units of {sku}.", "expected_tool": "reserve_item", "expected_args": {"sku": sku, "quantity": qty}})
    fx = [("USD", "CAD"), ("CAD", "USD"), ("EUR", "CAD"), ("GBP", "USD"), ("JPY", "CAD")]
    for i, (base, quote) in enumerate(fx, 1):
        singles.append({"id": f"tool_rate_{i}", "prompt": f"Look up {base} to {quote} rate.", "expected_tool": "lookup_rate", "expected_args": {"base": base, "quote": quote}})
    totals = [(12.5, 3, .05), (8.25, 4, .12), (19.0, 2, .07), (5.75, 9, 0.0), (31.4, 2, .13)]
    for i, (price, qty, tax) in enumerate(totals, 1):
        singles.append({"id": f"tool_total_{i}", "prompt": f"Calculate total for {qty} at {price} with tax rate {tax}.", "expected_tool": "calculate_total", "expected_args": {"unit_price": price, "quantity": qty, "tax_rate": tax}})
    multi = [
        ("multi_01", "Find AX-17 price, then total for 4 with 5% tax.", [["lookup_price", {"sku": "AX-17"}], ["calculate_total", {"unit_price": 12.5, "quantity": 4, "tax_rate": .05}]], ["52.5"]),
        ("multi_02", "Find BR-04 price, then total for 6 with 12% tax.", [["lookup_price", {"sku": "BR-04"}], ["calculate_total", {"unit_price": 8.25, "quantity": 6, "tax_rate": .12}]], ["55.44"]),
        ("multi_03", "Find Vancouver to Victoria road distance, then convert to miles.", [["lookup_distance", {"origin": "Vancouver", "destination": "Victoria"}], ["convert_distance", {"km": 115}]], ["71.46"]),
        ("multi_04", "Find Calgary to Banff road distance, then convert to miles.", [["lookup_distance", {"origin": "Calgary", "destination": "Banff"}], ["convert_distance", {"km": 128}]], ["79.54"]),
        ("multi_05", "Check AX-17 stock. If at least 3, reserve 3.", [["get_inventory", {"sku": "AX-17"}], ["reserve_item", {"sku": "AX-17", "quantity": 3}]], ["reserved"]),
        ("multi_06", "Check DN-33 stock. If at least 2, reserve 2.", [["get_inventory", {"sku": "DN-33"}], ["reserve_item", {"sku": "DN-33", "quantity": 2}]], ["reserved"]),
        ("multi_07", "Look up USD to CAD, then convert 80.", [["lookup_rate", {"base": "USD", "quote": "CAD"}], ["convert_currency", {"amount": 80, "rate": 1.36}]], ["108.8"]),
        ("multi_08", "Look up EUR to CAD, then convert 50.", [["lookup_rate", {"base": "EUR", "quote": "CAD"}], ["convert_currency", {"amount": 50, "rate": 1.49}]], ["74.5"]),
        ("multi_09", "Find CZ-91 price, then total for 2 with 7% tax.", [["lookup_price", {"sku": "CZ-91"}], ["calculate_total", {"unit_price": 19.0, "quantity": 2, "tax_rate": .07}]], ["40.66"]),
        ("multi_10", "Look up GBP to USD, then convert 25.", [["lookup_rate", {"base": "GBP", "quote": "USD"}], ["convert_currency", {"amount": 25, "rate": 1.31}]], ["32.75"]),
    ]
    multis = [{"id": i, "prompt": p, "steps": s, "final_contains": c} for i, p, s, c in multi]
    assert len(singles) == 30 and len(multis) == 10
    return {"tools": tools, "singles": singles, "multis": multis}


def writing() -> list[dict]:
    raw = [
        ("Rewrite in one sentence of at most 18 words, preserving 24GB, Q4_K_M, 32K: The test used a 24GB card. The file was Q4_K_M. Context was 32K.", 18, ["24GB", "Q4_K_M", "32K"], [], 1, 0),
        ("Edit for clarity without adding facts. Exactly two sentences: The cache was full, and latency rose. We restarted the server, and latency returned to normal.", 0, ["cache", "latency", "restarted"], [], 2, 0),
        ("Neutral update in at most 25 words: 14 checks ran; 13 passed; vision failed; no data was lost.", 25, ["14", "13", "vision", "no data"], [], 0, 0),
        ("Rewrite without hype in at most 20 words: This revolutionary model destroys every rival and changes everything forever.", 20, [], ["revolutionary", "destroys", "everything forever"], 0, 0),
        ("Copyedit and preserve all numbers: Decode rose from 31.2 to 38.7 tokens/s; VRAM rose from 18.4 to 19.1 GiB.", 0, ["31.2", "38.7", "18.4", "19.1"], [], 0, 0),
        ("Write a caveat of at most 30 words: one RTX 4090 result does not generalize to every 24GB GPU.", 30, ["RTX 4090", "24GB"], [], 0, 0),
        ("Exactly three bullet lines: weights use VRAM; KV cache grows with context; projector is optional for text.", 0, ["weights", "KV", "projector"], [], 0, 3),
        ("Direct recommendation, at most 22 words: coding/tool users should choose completed-task reliability over pretty prose.", 22, ["coding", "tool"], [], 0, 0),
        ("One sentence preserving the phrases vendor-reported and not directly comparable: Vendors ran the scores with different harnesses.", 0, ["vendor-reported", "not directly comparable"], [], 1, 0),
        ("Two short sentences explaining a model can load but still fail a long session. Include loads and usable.", 0, ["loads", "usable"], [], 2, 0),
        ("At most 24 words: equal Q4 compares model behaviour; best-fit compares what each sustains within 24GB.", 24, ["equal Q4", "best-fit", "24GB"], [], 0, 0),
        ("Factual caption, at most 20 words, containing Qwen3.8, Qwen3.6, Gemma 4, RTX 4090. Claim no winner.", 20, ["Qwen3.8", "Qwen3.6", "Gemma 4", "RTX 4090"], ["winner", "best"], 0, 0),
    ]
    return [{"id": f"wr_{i:02d}", "prompt": p, "max_words": m, "contains": c, "forbidden": f, "sentence_count": s, "bullet_count": b} for i, (p, m, c, f, s, b) in enumerate(raw, 1)]


def multilingual() -> list[dict]:
    raw = [
        ("zh", "翻译成中文，只输出译文：The model used 18.4 GiB of VRAM.", ["18.4", "GiB", "显存"]),
        ("zh", "日志显示14项检查中有13项通过。只回答通过数量。", ["13"]),
        ("zh", "按原顺序用中文逗号连接：模型，缓存，令牌。", ["模型，缓存，令牌"]),
        ("zh", "用不超过20个汉字说明：Q4文件能加载，但32K上下文仍需测试。", ["Q4", "32K"]),
        ("zh", "只输出 日期 | 版本：发布于2026年8月16日，版本b10453。", ["2026年8月16日", "b10453"]),
        ("zh", "只输出是或否：24GB大于16GB。", ["是"]),
        ("es", "Traduce al español sin añadir datos: The test used one RTX 4090 with 24GB of VRAM.", ["RTX 4090", "24GB"]),
        ("es", "Responde solo el número: 27 de 30 pruebas pasaron. ¿Cuántas fallaron?", ["3"]),
        ("es", "Ordena y une con |: zafiro, ámbar, cobre.", ["ámbar | cobre | zafiro"]),
        ("es", "Resume en 15 palabras o menos: El archivo cargó, pero la sesión de 64K se quedó sin memoria.", ["64K"]),
        ("es", "Extrae solo fecha | versión: La revisión b10453 se publicó el 16-08-2026.", ["16-08-2026", "b10453"]),
        ("es", "Responde solo SÍ o NO: 32 es divisible por 4.", ["SÍ"]),
        ("fr", "Traduisez en français sans ajouter de faits: The projector adds 1.1 GiB.", ["1.1", "GiB"]),
        ("fr", "Répondez seulement par le nombre: 18 tests sur 20 ont réussi. Combien ont échoué?", ["2"]),
        ("fr", "Triez et joignez avec |: violet, ambre, cobalt.", ["ambre | cobalt | violet"]),
        ("fr", "Résumez en 15 mots maximum: Le modèle charge à 8K mais échoue à 64K.", ["8K", "64K"]),
        ("fr", "Extrayez seulement date | version: La version b10453 date du 16/08/2026.", ["16/08/2026", "b10453"]),
        ("fr", "Répondez seulement OUI ou NON: 45 est divisible par 5.", ["OUI"]),
    ]
    return [{"id": f"ml_{lang}_{i:02d}", "language": lang, "prompt": p, "contains": c} for i, (lang, p, c) in enumerate(raw, 1)]


def documents() -> list[dict]:
    rng = random.Random(SEED)
    filler = ["archive", "signal", "matrix", "ledger", "kernel", "harbor", "vector", "cobalt", "maple", "quartz", "delta", "raven"]
    cases = []
    for tier, sections, repeats in [("8k", 24, 100), ("32k", 72, 140)]:
        parts, facts = [], []
        for i in range(1, sections + 1):
            sid = f"S{i:03d}"
            project = f"Project-{chr(65 + ((i - 1) % 26))}{i:02d}"
            value = 1000 + i * 37
            code = f"{rng.choice(['AX','BR','CZ','DN'])}-{i:03d}"
            pad = " ".join(rng.choice(filler) for _ in range(repeats))
            parts.append(f"[{sid}] {pad}. Audited project: {project}. Retained value: {value}. Control code: {code}. {pad}.")
            facts.append((sid, project, value, code))
        name = f"document_pack_{tier}.txt"
        (ROOT / name).write_text("\n\n".join(parts) + "\n", encoding="utf-8")
        picks = [2, 5, 9, 13, 17, 21, sections - 1, sections, sections // 3, sections // 2, sections * 2 // 3, sections * 5 // 6]
        for j, p in enumerate(picks, 1):
            sid, project, value, code = facts[p - 1]
            field = (j - 1) % 3
            if field == 0:
                q, a = f"What audited project is named in section {sid}?", project
            elif field == 1:
                q, a = f"What retained value is in section {sid}?", str(value)
            else:
                q, a = f"What control code is in section {sid}?", code
            cases.append({"id": f"doc_{tier}_{j:02d}", "tier": tier, "pack": name, "question": q, "answer": a, "citation": sid})
    assert len(cases) == 24
    return cases


CODING = [
    ("slug", "Fix normalize_slug: lowercase, trim, replace runs of non-alphanumeric characters with one hyphen, strip edge hyphens.", "def normalize_slug(text):\n    return text.replace(' ', '-')\n", """import unittest
from app import normalize_slug
class T(unittest.TestCase):
 def test_all(self):
  self.assertEqual(normalize_slug('  Hello, 24GB GPU!  '),'hello-24gb-gpu')
  self.assertEqual(normalize_slug('A__B---C'),'a-b-c')
  self.assertEqual(normalize_slug('***'),'')
if __name__=='__main__': unittest.main()
"""),
    ("intervals", "Fix merge_intervals: merge overlapping or touching integer intervals, sort, and do not mutate input.", "def merge_intervals(items):\n    return sorted(items)\n", """import unittest
from app import merge_intervals
class T(unittest.TestCase):
 def test_all(self):
  src=[[5,7],[1,3],[3,4],[10,10],[9,9]]
  self.assertEqual(merge_intervals(src),[[1,7],[9,10]])
  self.assertEqual(src,[[5,7],[1,3],[3,4],[10,10],[9,9]])
  self.assertEqual(merge_intervals([]),[])
if __name__=='__main__': unittest.main()
"""),
    ("duration", "Fix parse_duration: accept ordered optional h/m/s components; return seconds; reject invalid or repeated units.", "def parse_duration(text):\n    return int(text)\n", """import unittest
from app import parse_duration
class T(unittest.TestCase):
 def test_valid(self):
  self.assertEqual(parse_duration('2h 5m 9s'),7509)
  self.assertEqual(parse_duration('45m'),2700)
  self.assertEqual(parse_duration('7s'),7)
 def test_bad(self):
  for x in ['', '5m 2h', '1h 2h', 'abc', '4x']:
   with self.assertRaises(ValueError): parse_duration(x)
if __name__=='__main__': unittest.main()
"""),
    ("backoff", "Fix retry_delays: count delays starting at base, doubling, capped at maximum; validate inputs.", "def retry_delays(count, base=1, maximum=60):\n    return [base] * count\n", """import unittest
from app import retry_delays
class T(unittest.TestCase):
 def test_all(self):
  self.assertEqual(retry_delays(6,3,20),[3,6,12,20,20,20])
  self.assertEqual(retry_delays(0),[])
  for a in [(-1,1,3),(2,0,3),(2,4,0)]:
   with self.assertRaises(ValueError): retry_delays(*a)
if __name__=='__main__': unittest.main()
"""),
    ("aggregate", "Fix aggregate_by_key: sum numeric value fields by key, ignore incomplete rows, return a new sorted-key dict.", "def aggregate_by_key(records):\n    return {}\n", """import unittest
from app import aggregate_by_key
class T(unittest.TestCase):
 def test_all(self):
  rows=[{'key':'b','value':2},{'key':'a','value':1.5},{'key':'b','value':3},{'key':'x'},{'value':9}]
  self.assertEqual(aggregate_by_key(rows),{'a':1.5,'b':5})
  self.assertEqual(aggregate_by_key([]),{})
if __name__=='__main__': unittest.main()
"""),
    ("toposort", "Fix topo_sort for {node:[dependencies]}; deterministic dependency-first alphabetical order; raise ValueError on cycles.", "def topo_sort(graph):\n    return list(graph)\n", """import unittest
from app import topo_sort
class T(unittest.TestCase):
 def test_all(self):
  self.assertEqual(topo_sort({'build':['lint','test'],'test':['compile'],'lint':[],'compile':[]}),['compile','lint','test','build'])
  self.assertEqual(topo_sort({'b':['a']}),['a','b'])
  with self.assertRaises(ValueError): topo_sort({'a':['b'],'b':['a']})
if __name__=='__main__': unittest.main()
"""),
    ("links", "Fix markdown_links: return label,url tuples for inline Markdown links, exclude images, allow balanced parentheses in URLs.", "def markdown_links(text):\n    return []\n", """import unittest
from app import markdown_links
class T(unittest.TestCase):
 def test_all(self):
  s='See [Docs](https://x.test/a_(b)) and ![Logo](img.png) plus [Home](/home).'
  self.assertEqual(markdown_links(s),[('Docs','https://x.test/a_(b)'),('Home','/home')])
  self.assertEqual(markdown_links('none'),[])
if __name__=='__main__': unittest.main()
"""),
    ("semver", "Fix compare_semver for MAJOR.MINOR.PATCH and optional prerelease. Return -1/0/1; release newer than prerelease; numeric prerelease identifiers compare numerically.", "def compare_semver(a,b):\n    return (a>b)-(a<b)\n", """import unittest
from app import compare_semver
class T(unittest.TestCase):
 def test_all(self):
  for a,b,x in [('1.2.0','1.10.0',-1),('2.0.0','2.0.0-beta',1),('1.0.0-alpha.2','1.0.0-alpha.10',-1),('1.0.0','1.0.0',0)]:
   self.assertEqual(compare_semver(a,b),x)
if __name__=='__main__': unittest.main()
"""),
    ("window", "Fix rolling_average: every complete window, ints/floats, reject window<=0, [] if window too large.", "def rolling_average(values, window):\n    return values\n", """import unittest
from app import rolling_average
class T(unittest.TestCase):
 def test_all(self):
  self.assertEqual(rolling_average([1,2,3,4],2),[1.5,2.5,3.5])
  self.assertEqual(rolling_average([2,5],3),[])
  with self.assertRaises(ValueError): rolling_average([1],0)
if __name__=='__main__': unittest.main()
"""),
    ("chunks", "Fix chunked: yield lists of size n from any iterable with shorter final list; reject n<=0; do not eagerly consume.", "def chunked(items, n):\n    return [list(items)]\n", """import unittest
from app import chunked
class T(unittest.TestCase):
 def test_all(self):
  self.assertEqual(list(chunked((x for x in range(7)),3)),[[0,1,2],[3,4,5],[6]])
  self.assertEqual(list(chunked([],2)),[])
  with self.assertRaises(ValueError): list(chunked([1],0))
if __name__=='__main__': unittest.main()
"""),
    ("flatten", "Fix flatten_dict: dot-separated nested keys, preserve empty dicts, do not mutate.", "def flatten_dict(value):\n    return value\n", """import unittest
from app import flatten_dict
class T(unittest.TestCase):
 def test_all(self):
  src={'a':{'b':2,'c':{'d':3}},'x':{},'z':1}
  self.assertEqual(flatten_dict(src),{'a.b':2,'a.c.d':3,'x':{},'z':1})
  self.assertEqual(src,{'a':{'b':2,'c':{'d':3}},'x':{},'z':1})
if __name__=='__main__': unittest.main()
"""),
    ("redact", "Fix redact_secrets: recursively replace values whose key contains password, token, or secret case-insensitively; do not mutate.", "def redact_secrets(value):\n    return value\n", """import unittest
from app import redact_secrets
class T(unittest.TestCase):
 def test_all(self):
  src={'user':'a','apiToken':'x','nested':[{'Password':'p','ok':1}],'secret_note':'n'}
  out={'user':'a','apiToken':'[REDACTED]','nested':[{'Password':'[REDACTED]','ok':1}],'secret_note':'[REDACTED]'}
  self.assertEqual(redact_secrets(src),out)
  self.assertEqual(src['apiToken'],'x')
if __name__=='__main__': unittest.main()
"""),
]


def coding() -> None:
    for i, (name, prompt, app, tests) in enumerate(CODING, 1):
        ident = f"code_{i:02d}_{name}"
        base = ROOT / "coding" / ident
        base.mkdir(parents=True, exist_ok=True)
        (base / "app.py").write_text(app, encoding="utf-8")
        (base / "test_app.py").write_text(textwrap.dedent(tests), encoding="utf-8")
        (base / "TASK.md").write_text(prompt + "\n", encoding="utf-8")
        dump(base / "task.json", {"id": ident, "prompt": prompt})


def vision() -> list[dict]:
    from PIL import Image, ImageDraw, ImageFont
    out = ROOT / "vision"
    out.mkdir(parents=True, exist_ok=True)
    regular = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 27)
    bold = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 31)
    cases = []

    def save(ident, prompt, contains, painter):
        im = Image.new("RGB", (900, 620), "#f7f4ed")
        painter(ImageDraw.Draw(im))
        path = out / f"{ident}.png"
        im.save(path)
        cases.append({"id": ident, "image": str(path.relative_to(ROOT)), "prompt": prompt, "contains": contains})

    charts = [
        ({"Qwen3.8":47,"Qwen3.6":39,"Gemma 4":44}, "Which model has value 47?", ["Qwen3.8","47"]),
        ({"Alpha":18,"Beta":31,"Gamma":24}, "Which bar is tallest and its value?", ["Beta","31"]),
        ({"8K":19,"16K":20,"32K":22}, "What value is shown for 32K?", ["22"]),
        ({"Load":7,"Prefill":52,"Decode":36}, "Which stage is lowest and its value?", ["Load","7"]),
        ({"Pass":28,"Fail":2,"Timeout":1}, "Failures plus timeouts?", ["3"]),
    ]
    for idx, (data, prompt, contains) in enumerate(charts, 1):
        def paint(d, data=data):
            d.text((35,25),"MEASURED TASK COUNT",font=bold,fill="#17212b")
            m=max(data.values())
            for j,(label,val) in enumerate(data.items()):
                y=120+j*110
                d.text((45,y+10),label,font=regular,fill="#17212b")
                w=int(500*val/m)
                d.rectangle((260,y,260+w,y+54),fill=["#1f6f78","#d47a36","#5c6f91"][j])
                d.text((275+w,y+9),str(val),font=bold,fill="#17212b")
        save(f"vision_chart_{idx:02d}",prompt,contains,paint)

    tables = [
        ([("Model","VRAM"),("Qwen3.8","19.2 GiB"),("Qwen3.6","18.9 GiB"),("Gemma 4","21.1 GiB")],"Which model uses 21.1 GiB?",["Gemma 4"]),
        ([("Run","Status"),("A-17","PASS"),("B-04","FAIL"),("C-91","PASS")],"Which run failed?",["B-04"]),
        ([("SKU","Qty"),("AX-17","12"),("BR-04","7"),("CZ-91","19")],"Quantity for CZ-91?",["19"]),
        ([("Date","Build"),("2026-08-16","b10453"),("2026-08-15","b10421")],"Build for 2026-08-16?",["b10453"]),
        ([("Tier","Limit"),("Chat","8K"),("Code","32K"),("Archive","64K")],"Limit for Code?",["32K"]),
    ]
    for idx,(rows,prompt,contains) in enumerate(tables,1):
        def paint(d,rows=rows):
            d.text((45,25),"AUDIT LEDGER",font=bold,fill="#17212b")
            for r,row in enumerate(rows):
                y=105+r*100
                d.rectangle((70,y,830,y+90),fill="#dbe8e8" if r==0 else "#ffffff",outline="#44515c",width=2)
                d.line((430,y,430,y+90),fill="#44515c",width=2)
                d.text((95,y+25),row[0],font=bold if r==0 else regular,fill="#17212b")
                d.text((465,y+25),row[1],font=bold if r==0 else regular,fill="#17212b")
        save(f"vision_table_{idx:02d}",prompt,contains,paint)

    uis=[
        ("Deploy model","RTX 4090","Ready","What GPU and status?",["RTX 4090","Ready"]),
        ("Benchmark run","Q4_K_M","Running","What quant and status?",["Q4_K_M","Running"]),
        ("Context profile","32K","Comfortable","What context and fit label?",["32K","Comfortable"]),
        ("Vision lane","mmproj-F16","Passed","What projector and status?",["mmproj-F16","Passed"]),
        ("Artifact audit","SHA-256","Verified","What checksum type and status?",["SHA-256","Verified"]),
    ]
    for idx,(title,field,status,prompt,contains) in enumerate(uis,1):
        def paint(d,title=title,field=field,status=status):
            d.rectangle((70,70,830,550),fill="white",outline="#263746",width=4)
            d.rectangle((70,70,830,145),fill="#173e4f")
            d.text((105,90),title,font=bold,fill="white")
            d.text((120,220),"Configuration",font=regular,fill="#5a6570")
            d.rounded_rectangle((115,265,785,345),14,fill="#e8f0ef")
            d.text((145,285),field,font=bold,fill="#173e4f")
            d.text((120,400),"Status",font=regular,fill="#5a6570")
            d.rounded_rectangle((300,390,680,480),18,fill="#d7eddb")
            d.text((355,415),status,font=bold,fill="#17542d")
        save(f"vision_ui_{idx:02d}",prompt,contains,paint)

    flows=[
        (["Weights","KV cache","Output"],"Middle step?",["KV cache"]),
        (["Prompt","Model","Tool call"],"What follows Model?",["Tool call"]),
        (["Download","Hash","Run"],"What comes immediately before Run?",["Hash"]),
        (["8K","32K","64K"],"Final context label?",["64K"]),
        (["Draft","QA","Publish"],"What is between Draft and Publish?",["QA"]),
    ]
    for idx,(nodes,prompt,contains) in enumerate(flows,1):
        def paint(d,nodes=nodes):
            d.text((45,35),"PROCESS FLOW",font=bold,fill="#17212b")
            for j,(x,label) in enumerate(zip([70,345,620],nodes)):
                d.rounded_rectangle((x,235,x+210,360),20,fill=["#d9e9ec","#f2dfca","#dce5d5"][j],outline="#263746",width=3)
                d.text((x+22,278),label,font=bold,fill="#17212b")
                if j<2:
                    d.line((x+210,297,x+255,297),fill="#263746",width=5)
                    d.polygon([(x+255,297),(x+235,286),(x+235,308)],fill="#263746")
        save(f"vision_flow_{idx:02d}",prompt,contains,paint)

    assert len(cases)==20
    return cases


def main() -> None:
    ROOT.mkdir(parents=True, exist_ok=True)
    suite = {
        "suite_version": "2026-08-16.1",
        "seed": SEED,
        "instruction": instructions(),
        "reasoning": reasoning(),
        "tools": tool_data(),
        "writing": writing(),
        "multilingual": multilingual(),
        "documents": documents(),
    }
    coding()
    suite["vision"] = vision()
    dump(ROOT / "suite.json", suite)
    print(ROOT)
    print({k: len(v) if isinstance(v, list) else None for k,v in suite.items()})


if __name__ == "__main__":
    main()
